From 5aaecdeac6cc5037555f33db0e056067edf8cb67 Mon Sep 17 00:00:00 2001 From: anth64 Date: Mon, 6 Apr 2026 13:27:44 -0400 Subject: [PATCH] initial commit --- .gitignore | 16 ++ LICENSE | 373 ++++++++++++++++++++++++++++++++ README.md | 4 + bmake.mk | 178 +++++++++++++++ build.bat | 10 + build.sh | 51 +++++ compile_flags.txt | 6 + config.mk | 13 ++ example/client/main.c | 82 +++++++ example/gl_client/main.c | 68 ++++++ example/server/main.c | 38 ++++ gmake.mk | 222 +++++++++++++++++++ include/client/blit.h | 16 ++ include/client/input.h | 132 +++++++++++ include/client/palette.h | 21 ++ include/client/video.h | 38 ++++ include/clock.h | 18 ++ include/skele.h | 39 ++++ src/platform/client/input/sdl.c | 366 +++++++++++++++++++++++++++++++ src/platform/client/video/gl.c | 179 +++++++++++++++ src/platform/client/video/sdl.c | 253 ++++++++++++++++++++++ src/platform/clock/posix.c | 35 +++ src/platform/clock/win32.c | 37 ++++ src/skele.c | 51 +++++ 24 files changed, 2246 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 bmake.mk create mode 100644 build.bat create mode 100755 build.sh create mode 100644 compile_flags.txt create mode 100644 config.mk create mode 100644 example/client/main.c create mode 100644 example/gl_client/main.c create mode 100644 example/server/main.c create mode 100644 gmake.mk create mode 100644 include/client/blit.h create mode 100644 include/client/input.h create mode 100644 include/client/palette.h create mode 100644 include/client/video.h create mode 100644 include/clock.h create mode 100644 include/skele.h create mode 100644 src/platform/client/input/sdl.c create mode 100644 src/platform/client/video/gl.c create mode 100644 src/platform/client/video/sdl.c create mode 100644 src/platform/clock/posix.c create mode 100644 src/platform/clock/win32.c create mode 100644 src/skele.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45739bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +*.d +*.o +*.a +*.lib +*.dll +*.so +*.so.* +*.dylib +*.exe +*.out +*.dSYM/ +*.su +*.idb +*.pdb +obj/ +bin/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d0a1fa1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c262a2c --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# skele + +Generic game framework written in C99. + diff --git a/bmake.mk b/bmake.mk new file mode 100644 index 0000000..21e697f --- /dev/null +++ b/bmake.mk @@ -0,0 +1,178 @@ +CLOCK_SRC = src/platform/clock/posix.c +INPUT_SRC = src/platform/client/input/sdl.c + +.include "config.mk" + +ENGINE_SRCS = src/skele.c ${CLOCK_SRC} +SOFT_PLAT_SRCS = src/platform/client/video/sdl.c ${INPUT_SRC} +GL_PLAT_SRCS = src/platform/client/video/gl.c ${INPUT_SRC} +SOFT_CLIENT_SRCS = example/client/main.c +GL_CLIENT_SRCS = example/gl_client/main.c +SERVER_EXAMPLE = example/server/main.c + +LIBSKELE_SRCS = ${ENGINE_SRCS} ${SOFT_PLAT_SRCS} +LIBGLSKELE_SRCS = ${ENGINE_SRCS} ${GL_PLAT_SRCS} +LIBSKELE_SRV_SRCS = ${ENGINE_SRCS} + +LIBSKELE = ${BIN_DIR}/debug/libskele.a +LIBGLSKELE = ${BIN_DIR}/debug/libglskele.a +LIBSKELE_SRV = ${BIN_DIR}/debug/libskeleserver.a +SKELE_BIN = ${BIN_DIR}/debug/skele_client +GLSKELE_BIN = ${BIN_DIR}/debug/glskele_client +SERVER_BIN = ${BIN_DIR}/debug/skele_server + +LIBSKELE_R = ${BIN_DIR}/release/libskele.a +LIBGLSKELE_R = ${BIN_DIR}/release/libglskele.a +LIBSKELE_SRV_R = ${BIN_DIR}/release/libskeleserver.a +SKELE_BIN_R = ${BIN_DIR}/release/skele_client +GLSKELE_BIN_R = ${BIN_DIR}/release/glskele_client +SERVER_BIN_R = ${BIN_DIR}/release/skele_server + +LDFLAGS_PLAT = -L/usr/local/lib +CFLAGS_PLAT = -I/usr/local/include +CFLAGS_BASE = -Wall -Wpedantic -I${.CURDIR}/${INC_DIR} -std=c99 ${CFLAGS_PLAT} ${DEBUG_FLAGS} \ + -DGL_MAJOR=${GL_MAJOR} -DGL_MINOR=${GL_MINOR} +LINK_STK = -Wl,-Bstatic -lstk -Wl,-Bdynamic + +LIBSKELE_DEBUG_OBJS = ${LIBSKELE_SRCS:S/.c$/.o/:S/^/${.CURDIR}\/obj\/debug\/soft\//} +LIBGLSKELE_DEBUG_OBJS = ${LIBGLSKELE_SRCS:S/.c$/.o/:S/^/${.CURDIR}\/obj\/debug\/gl\//} +LIBSKELE_SRV_DEBUG_OBJS = ${LIBSKELE_SRV_SRCS:S/.c$/.o/:S/^/${.CURDIR}\/obj\/debug\/server\//} +SOFT_CLIENT_DEBUG_OBJS = ${SOFT_CLIENT_SRCS:S/.c$/.o/:S/^/${.CURDIR}\/obj\/debug\/soft\//} +GL_CLIENT_DEBUG_OBJS = ${GL_CLIENT_SRCS:S/.c$/.o/:S/^/${.CURDIR}\/obj\/debug\/gl\//} +SERVER_DEBUG_OBJS = ${SERVER_EXAMPLE:S/.c$/.o/:S/^/${.CURDIR}\/obj\/debug\/server\//} + +LIBSKELE_RELEASE_OBJS = ${LIBSKELE_SRCS:S/.c$/.o/:S/^/${.CURDIR}\/obj\/release\/soft\//} +LIBGLSKELE_RELEASE_OBJS = ${LIBGLSKELE_SRCS:S/.c$/.o/:S/^/${.CURDIR}\/obj\/release\/gl\//} +LIBSKELE_SRV_RELEASE_OBJS = ${LIBSKELE_SRV_SRCS:S/.c$/.o/:S/^/${.CURDIR}\/obj\/release\/server\//} +SOFT_CLIENT_RELEASE_OBJS = ${SOFT_CLIENT_SRCS:S/.c$/.o/:S/^/${.CURDIR}\/obj\/release\/soft\//} +GL_CLIENT_RELEASE_OBJS = ${GL_CLIENT_SRCS:S/.c$/.o/:S/^/${.CURDIR}\/obj\/release\/gl\//} +SERVER_RELEASE_OBJS = ${SERVER_EXAMPLE:S/.c$/.o/:S/^/${.CURDIR}\/obj\/release\/server\//} + +ALL_SOFT_SRCS = ${LIBSKELE_SRCS} ${SOFT_CLIENT_SRCS} +ALL_GL_SRCS = ${LIBGLSKELE_SRCS} ${GL_CLIENT_SRCS} +ALL_SERVER_SRCS = ${LIBSKELE_SRV_SRCS} ${SERVER_EXAMPLE} + +PREFIX ?= /usr/local +GL_MAJOR ?= 3 +GL_MINOR ?= 3 + +.PHONY: all debug release skele glskele server libs install uninstall clean run run_gl + +all: debug + +debug: ${.CURDIR}/${LIBSKELE} ${.CURDIR}/${LIBGLSKELE} ${.CURDIR}/${LIBSKELE_SRV} \ + ${.CURDIR}/${SKELE_BIN} ${.CURDIR}/${GLSKELE_BIN} ${.CURDIR}/${SERVER_BIN} + +release: ${.CURDIR}/${LIBSKELE_R} ${.CURDIR}/${LIBGLSKELE_R} ${.CURDIR}/${LIBSKELE_SRV_R} \ + ${.CURDIR}/${SKELE_BIN_R} ${.CURDIR}/${GLSKELE_BIN_R} ${.CURDIR}/${SERVER_BIN_R} + +libs: ${.CURDIR}/${LIBSKELE} ${.CURDIR}/${LIBGLSKELE} ${.CURDIR}/${LIBSKELE_SRV} +skele: ${.CURDIR}/${LIBSKELE} ${.CURDIR}/${SKELE_BIN} +glskele: ${.CURDIR}/${LIBGLSKELE} ${.CURDIR}/${GLSKELE_BIN} +server: ${.CURDIR}/${LIBSKELE_SRV} ${.CURDIR}/${SERVER_BIN} + +run: skele + ${.CURDIR}/${SKELE_BIN} + +run_gl: glskele + ${.CURDIR}/${GLSKELE_BIN} + +${.CURDIR}/${LIBSKELE}: ${LIBSKELE_DEBUG_OBJS} + @mkdir -p ${.TARGET:H} + ar rcs ${.TARGET} ${.ALLSRC} + +${.CURDIR}/${LIBGLSKELE}: ${LIBGLSKELE_DEBUG_OBJS} + @mkdir -p ${.TARGET:H} + ar rcs ${.TARGET} ${.ALLSRC} + +${.CURDIR}/${LIBSKELE_SRV}: ${LIBSKELE_SRV_DEBUG_OBJS} + @mkdir -p ${.TARGET:H} + ar rcs ${.TARGET} ${.ALLSRC} + +${.CURDIR}/${LIBSKELE_R}: ${LIBSKELE_RELEASE_OBJS} + @mkdir -p ${.TARGET:H} + ar rcs ${.TARGET} ${.ALLSRC} + +${.CURDIR}/${LIBGLSKELE_R}: ${LIBGLSKELE_RELEASE_OBJS} + @mkdir -p ${.TARGET:H} + ar rcs ${.TARGET} ${.ALLSRC} + +${.CURDIR}/${LIBSKELE_SRV_R}: ${LIBSKELE_SRV_RELEASE_OBJS} + @mkdir -p ${.TARGET:H} + ar rcs ${.TARGET} ${.ALLSRC} + +${.CURDIR}/${SKELE_BIN}: ${SOFT_CLIENT_DEBUG_OBJS} ${.CURDIR}/${LIBSKELE} + @mkdir -p ${.TARGET:H} + ${CC} -o ${.TARGET} ${SOFT_CLIENT_DEBUG_OBJS} -L${.CURDIR}/${BIN_DIR}/debug -lskele ${LINK_STK} -lSDL3 ${LDFLAGS_PLAT} + +${.CURDIR}/${GLSKELE_BIN}: ${GL_CLIENT_DEBUG_OBJS} ${.CURDIR}/${LIBGLSKELE} + @mkdir -p ${.TARGET:H} + ${CC} -o ${.TARGET} ${GL_CLIENT_DEBUG_OBJS} -L${.CURDIR}/${BIN_DIR}/debug -lglskele ${LINK_STK} -lSDL3 -lGL ${LDFLAGS_PLAT} + +${.CURDIR}/${SERVER_BIN}: ${SERVER_DEBUG_OBJS} ${.CURDIR}/${LIBSKELE_SRV} + @mkdir -p ${.TARGET:H} + ${CC} -o ${.TARGET} ${SERVER_DEBUG_OBJS} -L${.CURDIR}/${BIN_DIR}/debug -lskeleserver ${LINK_STK} ${LDFLAGS_PLAT} + +${.CURDIR}/${SKELE_BIN_R}: ${SOFT_CLIENT_RELEASE_OBJS} ${.CURDIR}/${LIBSKELE_R} + @mkdir -p ${.TARGET:H} + ${CC} -s -o ${.TARGET} ${SOFT_CLIENT_RELEASE_OBJS} -L${.CURDIR}/${BIN_DIR}/release -lskele ${LINK_STK} -lSDL3 ${LDFLAGS_PLAT} + +${.CURDIR}/${GLSKELE_BIN_R}: ${GL_CLIENT_RELEASE_OBJS} ${.CURDIR}/${LIBGLSKELE_R} + @mkdir -p ${.TARGET:H} + ${CC} -s -o ${.TARGET} ${GL_CLIENT_RELEASE_OBJS} -L${.CURDIR}/${BIN_DIR}/release -lglskele ${LINK_STK} -lSDL3 -lGL ${LDFLAGS_PLAT} + +${.CURDIR}/${SERVER_BIN_R}: ${SERVER_RELEASE_OBJS} ${.CURDIR}/${LIBSKELE_SRV_R} + @mkdir -p ${.TARGET:H} + ${CC} -s -o ${.TARGET} ${SERVER_RELEASE_OBJS} -L${.CURDIR}/${BIN_DIR}/release -lskeleserver ${LINK_STK} ${LDFLAGS_PLAT} + +.for _src in ${ALL_SOFT_SRCS} +${.CURDIR}/obj/debug/soft/${_src:S/.c$/.o/}: ${_src} + @mkdir -p ${.TARGET:H} + ${CC} ${CFLAGS_BASE} -g -O0 -MMD -MP -MT ${.TARGET} -MF ${.TARGET:S/.o$/.d/} -c ${.ALLSRC} -o ${.TARGET} +${.CURDIR}/obj/release/soft/${_src:S/.c$/.o/}: ${_src} + @mkdir -p ${.TARGET:H} + ${CC} ${CFLAGS_BASE} -O2 -MMD -MP -MT ${.TARGET} -MF ${.TARGET:S/.o$/.d/} -c ${.ALLSRC} -o ${.TARGET} +.endfor + +.for _src in ${ALL_GL_SRCS} +${.CURDIR}/obj/debug/gl/${_src:S/.c$/.o/}: ${_src} + @mkdir -p ${.TARGET:H} + ${CC} ${CFLAGS_BASE} -g -O0 -MMD -MP -MT ${.TARGET} -MF ${.TARGET:S/.o$/.d/} -c ${.ALLSRC} -o ${.TARGET} +${.CURDIR}/obj/release/gl/${_src:S/.c$/.o/}: ${_src} + @mkdir -p ${.TARGET:H} + ${CC} ${CFLAGS_BASE} -O2 -MMD -MP -MT ${.TARGET} -MF ${.TARGET:S/.o$/.d/} -c ${.ALLSRC} -o ${.TARGET} +.endfor + +.for _src in ${ALL_SERVER_SRCS} +${.CURDIR}/obj/debug/server/${_src:S/.c$/.o/}: ${_src} + @mkdir -p ${.TARGET:H} + ${CC} ${CFLAGS_BASE} -g -O0 -MMD -MP -MT ${.TARGET} -MF ${.TARGET:S/.o$/.d/} -c ${.ALLSRC} -o ${.TARGET} +${.CURDIR}/obj/release/server/${_src:S/.c$/.o/}: ${_src} + @mkdir -p ${.TARGET:H} + ${CC} ${CFLAGS_BASE} -O2 -MMD -MP -MT ${.TARGET} -MF ${.TARGET:S/.o$/.d/} -c ${.ALLSRC} -o ${.TARGET} +.endfor + +.-include "obj/debug/soft/*.d" +.-include "obj/debug/gl/*.d" +.-include "obj/debug/server/*.d" +.-include "obj/release/soft/*.d" +.-include "obj/release/gl/*.d" +.-include "obj/release/server/*.d" + +install: release + install -d ${PREFIX}/lib + install -d ${PREFIX}/include/skele + install -m 644 ${.CURDIR}/${LIBSKELE_R} ${PREFIX}/lib/libskele.a + install -m 644 ${.CURDIR}/${LIBGLSKELE_R} ${PREFIX}/lib/libglskele.a + install -m 644 ${.CURDIR}/${LIBSKELE_SRV_R} ${PREFIX}/lib/libskeleserver.a + cp -r ${.CURDIR}/include/ ${PREFIX}/include/skele/ + install -d ${PREFIX}/bin + +uninstall: + rm -f ${PREFIX}/lib/libskele.a + rm -f ${PREFIX}/lib/libglskele.a + rm -f ${PREFIX}/lib/libskeleserver.a + rm -rf ${PREFIX}/include/skele + +clean: + rm -rf ${.CURDIR}/${OBJ_DIR} ${.CURDIR}/${BIN_DIR} diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..0096ffb --- /dev/null +++ b/build.bat @@ -0,0 +1,10 @@ +@echo off +if "%1"=="run" ( + if "%2"=="" ( + make -f gmake.mk run MODE=debug + ) else ( + make -f gmake.mk run MODE=%2 + ) +) else ( + make -f gmake.mk %* +) diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..a80e2bb --- /dev/null +++ b/build.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +OS=$(uname -s | tr '[:upper:]' '[:lower:]') + +if [ "$OS" = "freebsd" ]; then + MK_FILE="bmake.mk" +else + MK_FILE="gmake.mk" +fi + +if grep -q "Raspberry Pi 5" /proc/device-tree/model 2>/dev/null; then + GL_MAJOR=3 + GL_MINOR=1 +else + GL_MAJOR=3 + GL_MINOR=3 +fi + +HAS_INSTALL=0 +HAS_UNINSTALL=0 +for arg in "$@"; do + case "$arg" in + install) HAS_INSTALL=1 ;; + uninstall) HAS_UNINSTALL=1 ;; + esac +done + +if [ "$HAS_INSTALL" = "1" ] || [ "$HAS_UNINSTALL" = "1" ]; then + if [ "$HAS_INSTALL" = "1" ]; then + make -f "$MK_FILE" release GL_MAJOR=$GL_MAJOR GL_MINOR=$GL_MINOR + fi + + if [ "$(id -u)" = "0" ]; then + PRIV="" + elif command -v doas > /dev/null 2>&1; then + PRIV="doas" + elif command -v sudo > /dev/null 2>&1; then + PRIV="sudo" + else + echo "error: install requires root. neither doas nor sudo found." >&2 + exit 1 + fi + + $PRIV make -f "$MK_FILE" "$@" GL_MAJOR=$GL_MAJOR GL_MINOR=$GL_MINOR +elif [ "$1" = "run" ]; then + make -f "$MK_FILE" run GL_MAJOR=$GL_MAJOR GL_MINOR=$GL_MINOR +elif [ "$1" = "run_gl" ]; then + make -f "$MK_FILE" run_gl GL_MAJOR=$GL_MAJOR GL_MINOR=$GL_MINOR +else + make -f "$MK_FILE" "$@" GL_MAJOR=$GL_MAJOR GL_MINOR=$GL_MINOR +fi diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..ba73758 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,6 @@ +-std=c99 +-Wall +-Wpedantic +-Iinclude +-I/usr/local/include +-I/usr/include diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..f03cbb1 --- /dev/null +++ b/config.mk @@ -0,0 +1,13 @@ +SRC_DIR = src +INC_DIR = include +OBJ_DIR = obj +BIN_DIR = bin + +GL_MAJOR ?= 3 +GL_MINOR ?= 3 + +ENGINE_SRCS = \ + src/skele.c \ + $(CLOCK_SRC) + +DEBUG_FLAGS ?= diff --git a/example/client/main.c b/example/client/main.c new file mode 100644 index 0000000..ce5d969 --- /dev/null +++ b/example/client/main.c @@ -0,0 +1,82 @@ +#include "client/blit.h" +#include "client/input.h" +#include "client/video.h" +#include "clock.h" +#include "skele.h" +#include +#include +#include + +static uint8_t running = 1; + +static void on_signal(void) { running = 0; } + +int main(int argc, char *argv[]) +{ + skele_video_config_t video_cfg; + uint64_t last, now, elapsed; + uint8_t *pixels; + uint32_t total; + + (void)argc; + (void)argv; + + skele_clock_init(on_signal); + + if (skele_stk_setup() != SKELE_INIT_SUCCESS) + return 1; + + if (skele_init() != SKELE_INIT_SUCCESS) { + skele_stk_teardown(); + return 1; + } + + video_cfg.render_width = SKELE_DEFAULT_RENDER_WIDTH; + video_cfg.render_height = SKELE_DEFAULT_RENDER_HEIGHT; + video_cfg.window_width = 0; + video_cfg.window_height = 0; + video_cfg.flags = 0; + + if (skele_video_init(video_cfg) != SKELE_INIT_SUCCESS) { + skele_stk_teardown(); + skele_shutdown(); + return 1; + } + + total = (uint32_t)(video_cfg.render_width * video_cfg.render_height); + pixels = malloc(total); + if (!pixels) { + skele_video_shutdown(); + skele_stk_teardown(); + skele_shutdown(); + return 1; + } + + memset(pixels, 0, total); + + last = skele_time_ns(); + + while (running) { + if (!skele_input_poll()) + break; + + stk_poll(); + + now = skele_time_ns(); + elapsed = now - last; + + if (elapsed >= skele_tick_ns) { + skele_tick(); + last = now; + } + + skele_video_blit(pixels); + skele_video_present(); + } + + free(pixels); + skele_video_shutdown(); + skele_stk_teardown(); + skele_shutdown(); + return 0; +} diff --git a/example/gl_client/main.c b/example/gl_client/main.c new file mode 100644 index 0000000..09df186 --- /dev/null +++ b/example/gl_client/main.c @@ -0,0 +1,68 @@ +#include "client/input.h" +#include "client/video.h" +#include "clock.h" +#include "skele.h" +#include +#include +#include + +static uint8_t running = 1; + +static void on_signal(void) { running = 0; } + +int main(int argc, char *argv[]) +{ + skele_video_config_t video_cfg; + uint64_t last; + uint64_t now; + uint64_t elapsed; + + skele_clock_init(on_signal); + + if (skele_stk_setup() != SKELE_INIT_SUCCESS) + return 1; + + if (skele_init() != SKELE_INIT_SUCCESS) { + skele_stk_teardown(); + return 1; + } + + video_cfg.render_width = SKELE_DEFAULT_RENDER_WIDTH; + video_cfg.render_height = SKELE_DEFAULT_RENDER_HEIGHT; + video_cfg.window_width = 0; + video_cfg.window_height = 0; + video_cfg.flags = 0; + + if (skele_video_init(video_cfg) != SKELE_INIT_SUCCESS) { + skele_stk_teardown(); + skele_shutdown(); + return 1; + } + + last = skele_time_ns(); + + while (running) { + if (!skele_input_poll()) + break; + + stk_poll(); + + now = skele_time_ns(); + elapsed = now - last; + + if (elapsed >= skele_tick_ns) { + skele_tick(); + last = now; + } + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + skele_video_present(); + } + + skele_video_shutdown(); + skele_stk_teardown(); + skele_shutdown(); + return 0; +} diff --git a/example/server/main.c b/example/server/main.c new file mode 100644 index 0000000..33b54c7 --- /dev/null +++ b/example/server/main.c @@ -0,0 +1,38 @@ +#include "clock.h" +#include "skele.h" +#include + +static uint8_t running = 1; + +static void on_signal(void) { running = 0; } + +int main(int argc, char *argv[]) +{ + uint64_t tick_start; + uint64_t elapsed; + + skele_clock_init(on_signal); + + if (skele_stk_setup() != SKELE_INIT_SUCCESS) + return 1; + + if (skele_init() != SKELE_INIT_SUCCESS) { + skele_stk_teardown(); + return 1; + } + + while (running) { + tick_start = skele_time_ns(); + + stk_poll(); + skele_tick(); + + elapsed = skele_time_ns() - tick_start; + if (elapsed < skele_tick_ns) + skele_sleep_ns(skele_tick_ns - elapsed); + } + + skele_stk_teardown(); + skele_shutdown(); + return 0; +} diff --git a/gmake.mk b/gmake.mk new file mode 100644 index 0000000..db65f74 --- /dev/null +++ b/gmake.mk @@ -0,0 +1,222 @@ +ifeq ($(OS),Windows_NT) + CLOCK_SRC := src/platform/clock/win32.c + INPUT_SRC := src/platform/client/input/sdl.c +else + CLOCK_SRC := src/platform/clock/posix.c + INPUT_SRC := src/platform/client/input/sdl.c +endif + +include config.mk + +ENGINE_SRCS := src/skele.c $(CLOCK_SRC) +SOFT_PLAT_SRCS := src/platform/client/video/sdl.c $(INPUT_SRC) +GL_PLAT_SRCS := src/platform/client/video/gl.c $(INPUT_SRC) +SOFT_CLIENT_SRCS := example/client/main.c +GL_CLIENT_SRCS := example/gl_client/main.c +SERVER_EXAMPLE := example/server/main.c + +LIBSKELE_SRCS := $(ENGINE_SRCS) $(SOFT_PLAT_SRCS) +LIBGLSKELE_SRCS := $(ENGINE_SRCS) $(GL_PLAT_SRCS) +LIBSKELE_SRV_SRCS := $(ENGINE_SRCS) + +ifeq ($(OS),Windows_NT) + SHELL := cmd.exe + LIBSKELE := $(BIN_DIR)/debug/libskele.a + LIBGLSKELE := $(BIN_DIR)/debug/libglskele.a + LIBSKELE_SRV := $(BIN_DIR)/debug/libskeleserver.a + SKELE_BIN := $(BIN_DIR)/debug/skele_client.exe + GLSKELE_BIN := $(BIN_DIR)/debug/glskele_client.exe + SERVER_BIN := $(BIN_DIR)/debug/skele_server.exe + LIBSKELE_R := $(BIN_DIR)/release/libskele.a + LIBGLSKELE_R := $(BIN_DIR)/release/libglskele.a + LIBSKELE_SRV_R := $(BIN_DIR)/release/libskeleserver.a + SKELE_BIN_R := $(BIN_DIR)/release/skele_client.exe + GLSKELE_BIN_R := $(BIN_DIR)/release/glskele_client.exe + SERVER_BIN_R := $(BIN_DIR)/release/skele_server.exe + LDFLAGS_PLAT := + CFLAGS_PLAT := + MKDIR = if not exist $(subst /,\,$(1)) mkdir $(subst /,\,$(1)) + RMDIR = if exist $(subst /,\,$(1)) rd /s /q $(subst /,\,$(1)) +else + LIBSKELE := $(BIN_DIR)/debug/libskele.a + LIBGLSKELE := $(BIN_DIR)/debug/libglskele.a + LIBSKELE_SRV := $(BIN_DIR)/debug/libskeleserver.a + SKELE_BIN := $(BIN_DIR)/debug/skele_client + GLSKELE_BIN := $(BIN_DIR)/debug/glskele_client + SERVER_BIN := $(BIN_DIR)/debug/skele_server + LIBSKELE_R := $(BIN_DIR)/release/libskele.a + LIBGLSKELE_R := $(BIN_DIR)/release/libglskele.a + LIBSKELE_SRV_R := $(BIN_DIR)/release/libskeleserver.a + SKELE_BIN_R := $(BIN_DIR)/release/skele_client + GLSKELE_BIN_R := $(BIN_DIR)/release/glskele_client + SERVER_BIN_R := $(BIN_DIR)/release/skele_server + LDFLAGS_PLAT := + CFLAGS_PLAT := + MKDIR = mkdir -p $(1) + RMDIR = rm -rf $(1) +endif + +ifeq ($(OS),Windows_NT) + GL_LIB := -lopengl32 + LDFLAGS_PLAT := + CFLAGS_PLAT := +else ifeq ($(shell uname),FreeBSD) + GL_LIB := -lGL + LDFLAGS_PLAT := -L/usr/local/lib + CFLAGS_PLAT := -I/usr/local/include +else + GL_LIB := -lGL + LDFLAGS_PLAT := + CFLAGS_PLAT := +endif +LINK_STK := -Wl,-Bstatic -lstk -Wl,-Bdynamic +RELEASE_LDFLAGS := -s +CFLAGS_BASE := -Wall -Wpedantic -I$(INC_DIR) -std=c99 $(CFLAGS_PLAT) $(DEBUG_FLAGS) \ + -DGL_MAJOR=$(GL_MAJOR) -DGL_MINOR=$(GL_MINOR) + +LIBSKELE_DEBUG_OBJS := $(LIBSKELE_SRCS:%.c=obj/debug/soft/%.o) +LIBGLSKELE_DEBUG_OBJS := $(LIBGLSKELE_SRCS:%.c=obj/debug/gl/%.o) +LIBSKELE_SRV_DEBUG_OBJS := $(LIBSKELE_SRV_SRCS:%.c=obj/debug/server/%.o) +SOFT_CLIENT_DEBUG_OBJS := $(SOFT_CLIENT_SRCS:%.c=obj/debug/soft/%.o) +GL_CLIENT_DEBUG_OBJS := $(GL_CLIENT_SRCS:%.c=obj/debug/gl/%.o) +SERVER_DEBUG_OBJS := $(SERVER_EXAMPLE:%.c=obj/debug/server/%.o) + +LIBSKELE_RELEASE_OBJS := $(LIBSKELE_SRCS:%.c=obj/release/soft/%.o) +LIBGLSKELE_RELEASE_OBJS := $(LIBGLSKELE_SRCS:%.c=obj/release/gl/%.o) +LIBSKELE_SRV_RELEASE_OBJS := $(LIBSKELE_SRV_SRCS:%.c=obj/release/server/%.o) +SOFT_CLIENT_RELEASE_OBJS := $(SOFT_CLIENT_SRCS:%.c=obj/release/soft/%.o) +GL_CLIENT_RELEASE_OBJS := $(GL_CLIENT_SRCS:%.c=obj/release/gl/%.o) +SERVER_RELEASE_OBJS := $(SERVER_EXAMPLE:%.c=obj/release/server/%.o) + +PREFIX ?= /usr +MODE ?= debug + +.PHONY: all debug release skele glskele server libs \ + install uninstall clean run run_gl + +all: debug + +debug: \ + $(LIBSKELE) \ + $(LIBGLSKELE) \ + $(LIBSKELE_SRV) \ + $(SKELE_BIN) \ + $(GLSKELE_BIN) \ + $(SERVER_BIN) + +release: \ + $(LIBSKELE_R) \ + $(LIBGLSKELE_R) \ + $(LIBSKELE_SRV_R) \ + $(SKELE_BIN_R) \ + $(GLSKELE_BIN_R) \ + $(SERVER_BIN_R) + +libs: $(LIBSKELE) $(LIBGLSKELE) $(LIBSKELE_SRV) +skele: $(LIBSKELE) $(SKELE_BIN) +glskele: $(LIBGLSKELE) $(GLSKELE_BIN) +server: $(LIBSKELE_SRV) $(SERVER_BIN) + +run: skele + $(SKELE_BIN) + +run_gl: glskele + $(GLSKELE_BIN) + +$(LIBSKELE): $(LIBSKELE_DEBUG_OBJS) + @$(call MKDIR,$(@D)) + $(AR) rcs $@ $^ + +$(LIBGLSKELE): $(LIBGLSKELE_DEBUG_OBJS) + @$(call MKDIR,$(@D)) + $(AR) rcs $@ $^ + +$(LIBSKELE_SRV): $(LIBSKELE_SRV_DEBUG_OBJS) + @$(call MKDIR,$(@D)) + $(AR) rcs $@ $^ + +$(LIBSKELE_R): $(LIBSKELE_RELEASE_OBJS) + @$(call MKDIR,$(@D)) + $(AR) rcs $@ $^ + +$(LIBGLSKELE_R): $(LIBGLSKELE_RELEASE_OBJS) + @$(call MKDIR,$(@D)) + $(AR) rcs $@ $^ + +$(LIBSKELE_SRV_R): $(LIBSKELE_SRV_RELEASE_OBJS) + @$(call MKDIR,$(@D)) + $(AR) rcs $@ $^ + +$(SKELE_BIN): $(SOFT_CLIENT_DEBUG_OBJS) $(LIBSKELE) + @$(call MKDIR,$(@D)) + $(CC) -o $@ $(SOFT_CLIENT_DEBUG_OBJS) -L$(BIN_DIR)/debug -lskele $(LINK_STK) -lSDL3 $(LDFLAGS_PLAT) + +$(GLSKELE_BIN): $(GL_CLIENT_DEBUG_OBJS) $(LIBGLSKELE) + @$(call MKDIR,$(@D)) + $(CC) -o $@ $(GL_CLIENT_DEBUG_OBJS) -L$(BIN_DIR)/debug -lglskele $(LINK_STK) -lSDL3 $(GL_LIB) $(LDFLAGS_PLAT) + +$(SERVER_BIN): $(SERVER_DEBUG_OBJS) $(LIBSKELE_SRV) + @$(call MKDIR,$(@D)) + $(CC) -o $@ $(SERVER_DEBUG_OBJS) -L$(BIN_DIR)/debug -lskeleserver $(LINK_STK) $(LDFLAGS_PLAT) + +$(SKELE_BIN_R): $(SOFT_CLIENT_RELEASE_OBJS) $(LIBSKELE_R) + @$(call MKDIR,$(@D)) + $(CC) $(RELEASE_LDFLAGS) -o $@ $(SOFT_CLIENT_RELEASE_OBJS) -L$(BIN_DIR)/release -lskele $(LINK_STK) -lSDL3 $(LDFLAGS_PLAT) + +$(GLSKELE_BIN_R): $(GL_CLIENT_RELEASE_OBJS) $(LIBGLSKELE_R) + @$(call MKDIR,$(@D)) + $(CC) $(RELEASE_LDFLAGS) -o $@ $(GL_CLIENT_RELEASE_OBJS) -L$(BIN_DIR)/release -lglskele $(LINK_STK) -lSDL3 $(GL_LIB) $(LDFLAGS_PLAT) + +$(SERVER_BIN_R): $(SERVER_RELEASE_OBJS) $(LIBSKELE_SRV_R) + @$(call MKDIR,$(@D)) + $(CC) $(RELEASE_LDFLAGS) -o $@ $(SERVER_RELEASE_OBJS) -L$(BIN_DIR)/release -lskeleserver $(LINK_STK) $(LDFLAGS_PLAT) + +obj/debug/soft/%.o: %.c + @$(call MKDIR,$(@D)) + $(CC) $(CFLAGS_BASE) -g -O0 -MMD -MP -c $< -o $@ + +obj/debug/gl/%.o: %.c + @$(call MKDIR,$(@D)) + $(CC) $(CFLAGS_BASE) -g -O0 -MMD -MP -c $< -o $@ + +obj/debug/server/%.o: %.c + @$(call MKDIR,$(@D)) + $(CC) $(CFLAGS_BASE) -g -O0 -MMD -MP -c $< -o $@ + +obj/release/soft/%.o: %.c + @$(call MKDIR,$(@D)) + $(CC) $(CFLAGS_BASE) -O2 -MMD -MP -c $< -o $@ + +obj/release/gl/%.o: %.c + @$(call MKDIR,$(@D)) + $(CC) $(CFLAGS_BASE) -O2 -MMD -MP -c $< -o $@ + +obj/release/server/%.o: %.c + @$(call MKDIR,$(@D)) + $(CC) $(CFLAGS_BASE) -O2 -MMD -MP -c $< -o $@ + +-include $(wildcard obj/debug/soft/*.d) +-include $(wildcard obj/debug/gl/*.d) +-include $(wildcard obj/debug/server/*.d) +-include $(wildcard obj/release/soft/*.d) +-include $(wildcard obj/release/gl/*.d) +-include $(wildcard obj/release/server/*.d) + +install: release + install -d $(PREFIX)/lib + install -d $(PREFIX)/include/skele + install -m 644 $(LIBSKELE_R) $(PREFIX)/lib/libskele.a + install -m 644 $(LIBGLSKELE_R) $(PREFIX)/lib/libglskele.a + install -m 644 $(LIBSKELE_SRV_R) $(PREFIX)/lib/libskeleserver.a + cp -r include/* $(PREFIX)/include/skele/ + install -d $(PREFIX)/bin + +uninstall: + rm -f $(PREFIX)/lib/libskele.a + rm -f $(PREFIX)/lib/libglskele.a + rm -f $(PREFIX)/lib/libskeleserver.a + rm -rf $(PREFIX)/include/skele + +clean: + @$(call RMDIR,$(OBJ_DIR)) + @$(call RMDIR,$(BIN_DIR)) diff --git a/include/client/blit.h b/include/client/blit.h new file mode 100644 index 0000000..066e6a0 --- /dev/null +++ b/include/client/blit.h @@ -0,0 +1,16 @@ +#ifndef SKELE_BLIT_H +#define SKELE_BLIT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void skele_video_blit(uint8_t *pixels); + +#ifdef __cplusplus +} +#endif + +#endif /* SKELE_BLIT_H */ diff --git a/include/client/input.h b/include/client/input.h new file mode 100644 index 0000000..eff6245 --- /dev/null +++ b/include/client/input.h @@ -0,0 +1,132 @@ +#ifndef SKELE_INPUT_H +#define SKELE_INPUT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + SKELE_KEY_UNKNOWN = 0, + + SKELE_KEY_A, + SKELE_KEY_B, + SKELE_KEY_C, + SKELE_KEY_D, + SKELE_KEY_E, + SKELE_KEY_F, + SKELE_KEY_G, + SKELE_KEY_H, + SKELE_KEY_I, + SKELE_KEY_J, + SKELE_KEY_K, + SKELE_KEY_L, + SKELE_KEY_M, + SKELE_KEY_N, + SKELE_KEY_O, + SKELE_KEY_P, + SKELE_KEY_Q, + SKELE_KEY_R, + SKELE_KEY_S, + SKELE_KEY_T, + SKELE_KEY_U, + SKELE_KEY_V, + SKELE_KEY_W, + SKELE_KEY_X, + SKELE_KEY_Y, + SKELE_KEY_Z, + + SKELE_KEY_0, + SKELE_KEY_1, + SKELE_KEY_2, + SKELE_KEY_3, + SKELE_KEY_4, + SKELE_KEY_5, + SKELE_KEY_6, + SKELE_KEY_7, + SKELE_KEY_8, + SKELE_KEY_9, + + SKELE_KEY_F1, + SKELE_KEY_F2, + SKELE_KEY_F3, + SKELE_KEY_F4, + SKELE_KEY_F5, + SKELE_KEY_F6, + SKELE_KEY_F7, + SKELE_KEY_F8, + SKELE_KEY_F9, + SKELE_KEY_F10, + SKELE_KEY_F11, + SKELE_KEY_F12, + + SKELE_KEY_SPACE, + SKELE_KEY_ENTER, + SKELE_KEY_ESCAPE, + SKELE_KEY_TAB, + SKELE_KEY_BACKSPACE, + SKELE_KEY_LSHIFT, + SKELE_KEY_RSHIFT, + SKELE_KEY_LCTRL, + SKELE_KEY_RCTRL, + SKELE_KEY_LALT, + SKELE_KEY_RALT, + + SKELE_KEY_UP, + SKELE_KEY_DOWN, + SKELE_KEY_LEFT, + SKELE_KEY_RIGHT, + + SKELE_KEY_COUNT +} skele_key_t; + +typedef enum { + SKELE_PAD_A, + SKELE_PAD_B, + SKELE_PAD_X, + SKELE_PAD_Y, + SKELE_PAD_LB, + SKELE_PAD_RB, + SKELE_PAD_LT, + SKELE_PAD_RT, + SKELE_PAD_LSTICK, + SKELE_PAD_RSTICK, + SKELE_PAD_START, + SKELE_PAD_BACK, + SKELE_PAD_DPAD_UP, + SKELE_PAD_DPAD_DOWN, + SKELE_PAD_DPAD_LEFT, + SKELE_PAD_DPAD_RIGHT, + SKELE_PAD_BUTTON_COUNT +} skele_pad_button_t; + +typedef enum { + SKELE_PAD_AXIS_LX, + SKELE_PAD_AXIS_LY, + SKELE_PAD_AXIS_RX, + SKELE_PAD_AXIS_RY, + SKELE_PAD_AXIS_LT, + SKELE_PAD_AXIS_RT, + SKELE_PAD_AXIS_COUNT +} skele_pad_axis_t; + +#define SKELE_MAX_PADS 4 + +uint8_t skele_input_poll(void); + +uint8_t skele_key_down(skele_key_t key); +uint8_t skele_key_held(skele_key_t key); + +uint8_t skele_pad_connected(uint8_t pad); +uint8_t skele_pad_button_down(uint8_t pad, skele_pad_button_t btn); +uint8_t skele_pad_button_held(uint8_t pad, skele_pad_button_t btn); +float skele_pad_axis(uint8_t pad, skele_pad_axis_t axis); + +void skele_mouse_delta(int32_t *dx, int32_t *dy); + +#ifdef __cplusplus +} +#endif + +#endif /* SKELE_INPUT_H */ diff --git a/include/client/palette.h b/include/client/palette.h new file mode 100644 index 0000000..ddd5d8b --- /dev/null +++ b/include/client/palette.h @@ -0,0 +1,21 @@ +#ifndef SKELE_PALETTE_H +#define SKELE_PALETTE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SKELE_PALETTE_COLORS 256 + +typedef uint32_t skele_palette_t[SKELE_PALETTE_COLORS]; + +void skele_palette_set(skele_palette_t pal); +void skele_palette_set_index(uint8_t index, uint32_t color); + +#ifdef __cplusplus +} +#endif + +#endif /* SKELE_PALETTE_H */ diff --git a/include/client/video.h b/include/client/video.h new file mode 100644 index 0000000..7893443 --- /dev/null +++ b/include/client/video.h @@ -0,0 +1,38 @@ +#ifndef SKELE_VIDEO_H +#define SKELE_VIDEO_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SKELE_VIDEO_FULLSCREEN 0x01 +#define SKELE_VIDEO_BORDERLESS 0x02 +#define SKELE_VIDEO_RESIZABLE 0x04 +#define SKELE_VIDEO_HIGHDPI 0x08 + +#define SKELE_DEFAULT_RENDER_WIDTH 320 +#define SKELE_DEFAULT_RENDER_HEIGHT 200 + +typedef struct { + uint16_t render_width; + uint16_t render_height; + uint16_t window_width; + uint16_t window_height; + uint8_t flags; +} skele_video_config_t; + +uint8_t skele_video_init(skele_video_config_t cfg); +void skele_video_shutdown(void); +void skele_video_present(void); +void skele_video_set_title(const char *title); +void skele_video_toggle_fullscreen(void); +void skele_video_cycle_scale(void); +void skele_video_set_mouse_grab(uint8_t grab); + +#ifdef __cplusplus +} +#endif + +#endif /* SKELE_VIDEO_H */ diff --git a/include/clock.h b/include/clock.h new file mode 100644 index 0000000..ecce0a7 --- /dev/null +++ b/include/clock.h @@ -0,0 +1,18 @@ +#ifndef SKELE_CLOCK_H +#define SKELE_CLOCK_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void skele_clock_init(void (*on_signal)(void)); +uint64_t skele_time_ns(void); +void skele_sleep_ns(uint64_t ns); + +#ifdef __cplusplus +} +#endif + +#endif /* SKELE_CLOCK_H */ diff --git a/include/skele.h b/include/skele.h new file mode 100644 index 0000000..41b1d51 --- /dev/null +++ b/include/skele.h @@ -0,0 +1,39 @@ +#ifndef SKELE_H +#define SKELE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SKELE_VERSION_MAJOR 0 +#define SKELE_VERSION_MINOR 0 +#define SKELE_VERSION_PATCH 0 + +#define SKELE_STR(x) #x +#define SKELE_XSTR(x) SKELE_STR(x) +#define SKELE_VERSION \ + SKELE_XSTR(SKELE_VERSION_MAJOR) \ + "." SKELE_XSTR(SKELE_VERSION_MINOR) "." SKELE_XSTR(SKELE_VERSION_PATCH) + +#define SKELE_INIT_SUCCESS 1 +#define SKELE_INIT_FAILURE 0 + +#define SKELE_DEFAULT_TICK_RATE 35 + +extern uint64_t skele_tick_ns; + +void skele_set_tick_rate(uint8_t rate); +uint8_t skele_init(void); +void skele_shutdown(void); +void skele_tick(void); + +uint8_t skele_stk_setup(void); +void skele_stk_teardown(void); + +#ifdef __cplusplus +} +#endif + +#endif /* SKELE_H */ diff --git a/src/platform/client/input/sdl.c b/src/platform/client/input/sdl.c new file mode 100644 index 0000000..28fb3b9 --- /dev/null +++ b/src/platform/client/input/sdl.c @@ -0,0 +1,366 @@ +#include "client/input.h" +#include "client/video.h" +#include +#include + +static uint8_t key_held[SKELE_KEY_COUNT]; +static uint8_t key_down[SKELE_KEY_COUNT]; + +static SDL_Gamepad *pads[SKELE_MAX_PADS]; +static uint8_t pad_btn_held[SKELE_MAX_PADS][SKELE_PAD_BUTTON_COUNT]; +static uint8_t pad_btn_down[SKELE_MAX_PADS][SKELE_PAD_BUTTON_COUNT]; +static float pad_axes[SKELE_MAX_PADS][SKELE_PAD_AXIS_COUNT]; + +static int32_t mouse_dx = 0; +static int32_t mouse_dy = 0; + +static skele_key_t scancode_to_key(SDL_Scancode sc) +{ + switch (sc) { + case SDL_SCANCODE_A: + return SKELE_KEY_A; + case SDL_SCANCODE_B: + return SKELE_KEY_B; + case SDL_SCANCODE_C: + return SKELE_KEY_C; + case SDL_SCANCODE_D: + return SKELE_KEY_D; + case SDL_SCANCODE_E: + return SKELE_KEY_E; + case SDL_SCANCODE_F: + return SKELE_KEY_F; + case SDL_SCANCODE_G: + return SKELE_KEY_G; + case SDL_SCANCODE_H: + return SKELE_KEY_H; + case SDL_SCANCODE_I: + return SKELE_KEY_I; + case SDL_SCANCODE_J: + return SKELE_KEY_J; + case SDL_SCANCODE_K: + return SKELE_KEY_K; + case SDL_SCANCODE_L: + return SKELE_KEY_L; + case SDL_SCANCODE_M: + return SKELE_KEY_M; + case SDL_SCANCODE_N: + return SKELE_KEY_N; + case SDL_SCANCODE_O: + return SKELE_KEY_O; + case SDL_SCANCODE_P: + return SKELE_KEY_P; + case SDL_SCANCODE_Q: + return SKELE_KEY_Q; + case SDL_SCANCODE_R: + return SKELE_KEY_R; + case SDL_SCANCODE_S: + return SKELE_KEY_S; + case SDL_SCANCODE_T: + return SKELE_KEY_T; + case SDL_SCANCODE_U: + return SKELE_KEY_U; + case SDL_SCANCODE_V: + return SKELE_KEY_V; + case SDL_SCANCODE_W: + return SKELE_KEY_W; + case SDL_SCANCODE_X: + return SKELE_KEY_X; + case SDL_SCANCODE_Y: + return SKELE_KEY_Y; + case SDL_SCANCODE_Z: + return SKELE_KEY_Z; + case SDL_SCANCODE_0: + return SKELE_KEY_0; + case SDL_SCANCODE_1: + return SKELE_KEY_1; + case SDL_SCANCODE_2: + return SKELE_KEY_2; + case SDL_SCANCODE_3: + return SKELE_KEY_3; + case SDL_SCANCODE_4: + return SKELE_KEY_4; + case SDL_SCANCODE_5: + return SKELE_KEY_5; + case SDL_SCANCODE_6: + return SKELE_KEY_6; + case SDL_SCANCODE_7: + return SKELE_KEY_7; + case SDL_SCANCODE_8: + return SKELE_KEY_8; + case SDL_SCANCODE_9: + return SKELE_KEY_9; + case SDL_SCANCODE_F1: + return SKELE_KEY_F1; + case SDL_SCANCODE_F2: + return SKELE_KEY_F2; + case SDL_SCANCODE_F3: + return SKELE_KEY_F3; + case SDL_SCANCODE_F4: + return SKELE_KEY_F4; + case SDL_SCANCODE_F5: + return SKELE_KEY_F5; + case SDL_SCANCODE_F6: + return SKELE_KEY_F6; + case SDL_SCANCODE_F7: + return SKELE_KEY_F7; + case SDL_SCANCODE_F8: + return SKELE_KEY_F8; + case SDL_SCANCODE_F9: + return SKELE_KEY_F9; + case SDL_SCANCODE_F10: + return SKELE_KEY_F10; + case SDL_SCANCODE_F11: + return SKELE_KEY_F11; + case SDL_SCANCODE_F12: + return SKELE_KEY_F12; + case SDL_SCANCODE_SPACE: + return SKELE_KEY_SPACE; + case SDL_SCANCODE_RETURN: + return SKELE_KEY_ENTER; + case SDL_SCANCODE_ESCAPE: + return SKELE_KEY_ESCAPE; + case SDL_SCANCODE_TAB: + return SKELE_KEY_TAB; + case SDL_SCANCODE_BACKSPACE: + return SKELE_KEY_BACKSPACE; + case SDL_SCANCODE_LSHIFT: + return SKELE_KEY_LSHIFT; + case SDL_SCANCODE_RSHIFT: + return SKELE_KEY_RSHIFT; + case SDL_SCANCODE_LCTRL: + return SKELE_KEY_LCTRL; + case SDL_SCANCODE_RCTRL: + return SKELE_KEY_RCTRL; + case SDL_SCANCODE_LALT: + return SKELE_KEY_LALT; + case SDL_SCANCODE_RALT: + return SKELE_KEY_RALT; + case SDL_SCANCODE_UP: + return SKELE_KEY_UP; + case SDL_SCANCODE_DOWN: + return SKELE_KEY_DOWN; + case SDL_SCANCODE_LEFT: + return SKELE_KEY_LEFT; + case SDL_SCANCODE_RIGHT: + return SKELE_KEY_RIGHT; + default: + return SKELE_KEY_UNKNOWN; + } +} + +static skele_pad_button_t sdl_btn_to_pad(SDL_GamepadButton btn) +{ + switch (btn) { + case SDL_GAMEPAD_BUTTON_SOUTH: + return SKELE_PAD_A; + case SDL_GAMEPAD_BUTTON_EAST: + return SKELE_PAD_B; + case SDL_GAMEPAD_BUTTON_WEST: + return SKELE_PAD_X; + case SDL_GAMEPAD_BUTTON_NORTH: + return SKELE_PAD_Y; + case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: + return SKELE_PAD_LB; + case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: + return SKELE_PAD_RB; + case SDL_GAMEPAD_BUTTON_LEFT_STICK: + return SKELE_PAD_LSTICK; + case SDL_GAMEPAD_BUTTON_RIGHT_STICK: + return SKELE_PAD_RSTICK; + case SDL_GAMEPAD_BUTTON_START: + return SKELE_PAD_START; + case SDL_GAMEPAD_BUTTON_BACK: + return SKELE_PAD_BACK; + case SDL_GAMEPAD_BUTTON_DPAD_UP: + return SKELE_PAD_DPAD_UP; + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: + return SKELE_PAD_DPAD_DOWN; + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: + return SKELE_PAD_DPAD_LEFT; + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: + return SKELE_PAD_DPAD_RIGHT; + default: + return SKELE_PAD_BUTTON_COUNT; + } +} + +static int8_t pad_slot(SDL_JoystickID id) +{ + uint8_t i; + for (i = 0; i < SKELE_MAX_PADS; i++) + if (pads[i] && SDL_GetGamepadID(pads[i]) == id) + return (int8_t)i; + return -1; +} + +static int8_t free_slot(void) +{ + uint8_t i; + for (i = 0; i < SKELE_MAX_PADS; i++) + if (!pads[i]) + return (int8_t)i; + return -1; +} + +static void update_axes(uint8_t slot) +{ + pad_axes[slot][SKELE_PAD_AXIS_LX] = + SDL_GetGamepadAxis(pads[slot], SDL_GAMEPAD_AXIS_LEFTX) / 32767.0f; + pad_axes[slot][SKELE_PAD_AXIS_LY] = + SDL_GetGamepadAxis(pads[slot], SDL_GAMEPAD_AXIS_LEFTY) / 32767.0f; + pad_axes[slot][SKELE_PAD_AXIS_RX] = + SDL_GetGamepadAxis(pads[slot], SDL_GAMEPAD_AXIS_RIGHTX) / 32767.0f; + pad_axes[slot][SKELE_PAD_AXIS_RY] = + SDL_GetGamepadAxis(pads[slot], SDL_GAMEPAD_AXIS_RIGHTY) / 32767.0f; + pad_axes[slot][SKELE_PAD_AXIS_LT] = + SDL_GetGamepadAxis(pads[slot], SDL_GAMEPAD_AXIS_LEFT_TRIGGER) / + 32767.0f; + pad_axes[slot][SKELE_PAD_AXIS_RT] = + SDL_GetGamepadAxis(pads[slot], SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) / + 32767.0f; +} + +uint8_t skele_input_poll(void) +{ + SDL_Event e; + skele_key_t key; + int8_t slot; + uint8_t i; + skele_pad_button_t btn; + + memset(key_down, 0, sizeof(key_down)); + memset(pad_btn_down, 0, sizeof(pad_btn_down)); + mouse_dx = 0; + mouse_dy = 0; + + while (SDL_PollEvent(&e)) { + switch (e.type) { + case SDL_EVENT_QUIT: + return 0; + + case SDL_EVENT_MOUSE_MOTION: + mouse_dx += (int32_t)e.motion.xrel; + mouse_dy += (int32_t)e.motion.yrel; + break; + + case SDL_EVENT_KEY_DOWN: + if (e.key.repeat) + break; + switch (e.key.scancode) { + case SDL_SCANCODE_F11: + skele_video_toggle_fullscreen(); + break; + case SDL_SCANCODE_F4: + skele_video_cycle_scale(); + break; + default: + key = scancode_to_key(e.key.scancode); + if (key != SKELE_KEY_UNKNOWN) { + key_down[key] = 1; + key_held[key] = 1; + } + break; + } + break; + + case SDL_EVENT_KEY_UP: + key = scancode_to_key(e.key.scancode); + if (key != SKELE_KEY_UNKNOWN) + key_held[key] = 0; + break; + + case SDL_EVENT_GAMEPAD_ADDED: + slot = free_slot(); + if (slot >= 0) { + pads[slot] = SDL_OpenGamepad(e.gdevice.which); + if (pads[slot]) + memset(pad_btn_held[slot], 0, + sizeof(pad_btn_held[slot])); + } + break; + + case SDL_EVENT_GAMEPAD_REMOVED: + slot = pad_slot(e.gdevice.which); + if (slot >= 0) { + SDL_CloseGamepad(pads[slot]); + pads[slot] = NULL; + memset(pad_btn_held[slot], 0, + sizeof(pad_btn_held[slot])); + memset(pad_axes[slot], 0, + sizeof(pad_axes[slot])); + } + break; + + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + slot = pad_slot(e.gbutton.which); + btn = + sdl_btn_to_pad((SDL_GamepadButton)e.gbutton.button); + if (slot >= 0 && btn != SKELE_PAD_BUTTON_COUNT) { + pad_btn_down[slot][btn] = 1; + pad_btn_held[slot][btn] = 1; + } + break; + + case SDL_EVENT_GAMEPAD_BUTTON_UP: + slot = pad_slot(e.gbutton.which); + btn = + sdl_btn_to_pad((SDL_GamepadButton)e.gbutton.button); + if (slot >= 0 && btn != SKELE_PAD_BUTTON_COUNT) + pad_btn_held[slot][btn] = 0; + break; + + default: + break; + } + } + + for (i = 0; i < SKELE_MAX_PADS; i++) + if (pads[i]) + update_axes(i); + + return 1; +} + +uint8_t skele_key_down(skele_key_t key) +{ + return key < SKELE_KEY_COUNT ? key_down[key] : 0; +} + +uint8_t skele_key_held(skele_key_t key) +{ + return key < SKELE_KEY_COUNT ? key_held[key] : 0; +} + +uint8_t skele_pad_connected(uint8_t pad) +{ + return pad < SKELE_MAX_PADS ? pads[pad] != NULL : 0; +} + +uint8_t skele_pad_button_down(uint8_t pad, skele_pad_button_t btn) +{ + return (pad < SKELE_MAX_PADS && btn < SKELE_PAD_BUTTON_COUNT) + ? pad_btn_down[pad][btn] + : 0; +} + +uint8_t skele_pad_button_held(uint8_t pad, skele_pad_button_t btn) +{ + return (pad < SKELE_MAX_PADS && btn < SKELE_PAD_BUTTON_COUNT) + ? pad_btn_held[pad][btn] + : 0; +} + +float skele_pad_axis(uint8_t pad, skele_pad_axis_t axis) +{ + return (pad < SKELE_MAX_PADS && axis < SKELE_PAD_AXIS_COUNT) + ? pad_axes[pad][axis] + : 0.0f; +} + +void skele_mouse_delta(int32_t *dx, int32_t *dy) +{ + if (dx) + *dx = mouse_dx; + if (dy) + *dy = mouse_dy; +} diff --git a/src/platform/client/video/gl.c b/src/platform/client/video/gl.c new file mode 100644 index 0000000..bd53ce6 --- /dev/null +++ b/src/platform/client/video/gl.c @@ -0,0 +1,179 @@ +#include "client/video.h" +#include "skele.h" +#include +#include + +static SDL_Window *window = NULL; +static SDL_GLContext gl_ctx = NULL; +static uint16_t vid_w = 0; +static uint16_t vid_h = 0; +static uint8_t cur_scale = 1; +static uint8_t fullscreen = 0; + +static uint8_t max_scale(uint16_t rw, uint16_t rh) +{ + SDL_DisplayID display; + const SDL_DisplayMode *mode; + uint16_t aw, ah; + uint8_t scale; + + display = + window ? SDL_GetDisplayForWindow(window) : SDL_GetPrimaryDisplay(); + mode = SDL_GetCurrentDisplayMode(display); + if (!mode) + return 1; + + aw = (uint16_t)((mode->w * 3) / 4); + ah = (uint16_t)((mode->h * 3) / 4); + scale = 1; + while ((rw * (scale + 1)) <= aw && (rh * (scale + 1)) <= ah) + scale++; + + return scale; +} + +static void set_scale(uint8_t scale) +{ + SDL_DisplayID display; + cur_scale = scale; + display = SDL_GetDisplayForWindow(window); + SDL_SetWindowSize(window, (int)(vid_w * scale), (int)(vid_h * scale)); + SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED_DISPLAY(display), + SDL_WINDOWPOS_CENTERED_DISPLAY(display)); +} + +static void toggle_fullscreen(void) +{ + fullscreen = !fullscreen; + SDL_SetWindowFullscreen(window, fullscreen); +} + +static void cycle_scale(void) +{ + uint8_t next; + if (fullscreen) + return; + next = cur_scale + 1; + if (next > max_scale(vid_w, vid_h)) + next = 1; + set_scale(next); +} + +uint8_t skele_video_init(skele_video_config_t cfg) +{ + SDL_DisplayID display; + SDL_WindowFlags flags = 0; + const SDL_DisplayMode *mode; + uint16_t window_w, window_h, aw, ah; + + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) { + stk_log(STK_LOG_ERROR, "video: SDL_Init failed: %s", + SDL_GetError()); + return SKELE_INIT_FAILURE; + } + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, GL_MAJOR); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, GL_MINOR); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, + SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + + flags |= SDL_WINDOW_OPENGL; + if (cfg.flags & SKELE_VIDEO_FULLSCREEN) + flags |= SDL_WINDOW_FULLSCREEN; + if (cfg.flags & SKELE_VIDEO_BORDERLESS) + flags |= SDL_WINDOW_BORDERLESS; + if (cfg.flags & SKELE_VIDEO_RESIZABLE) + flags |= SDL_WINDOW_RESIZABLE; + if (cfg.flags & SKELE_VIDEO_HIGHDPI) + flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY; + + display = SDL_GetPrimaryDisplay(); + vid_w = + cfg.render_width ? cfg.render_width : SKELE_DEFAULT_RENDER_WIDTH; + vid_h = + cfg.render_height ? cfg.render_height : SKELE_DEFAULT_RENDER_HEIGHT; + + if (cfg.window_width && cfg.window_height) { + window_w = cfg.window_width; + window_h = cfg.window_height; + cur_scale = 1; + } else { + mode = SDL_GetCurrentDisplayMode(display); + if (!mode) { + stk_log(STK_LOG_ERROR, + "video: SDL_GetCurrentDisplayMode failed: %s", + SDL_GetError()); + SDL_Quit(); + return SKELE_INIT_FAILURE; + } + + aw = (uint16_t)((mode->w * 3) / 4); + ah = (uint16_t)((mode->h * 3) / 4); + cur_scale = 1; + while ((vid_w * (cur_scale + 1)) <= aw && + (vid_h * (cur_scale + 1)) <= ah) + cur_scale++; + + window_w = vid_w * cur_scale; + window_h = vid_h * cur_scale; + } + + window = SDL_CreateWindow("skele", window_w, window_h, + flags | SDL_WINDOW_HIDDEN); + if (!window) { + stk_log(STK_LOG_ERROR, "video: SDL_CreateWindow failed: %s", + SDL_GetError()); + SDL_Quit(); + return SKELE_INIT_FAILURE; + } + + SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED_DISPLAY(display), + SDL_WINDOWPOS_CENTERED_DISPLAY(display)); + + if (cfg.flags & SKELE_VIDEO_FULLSCREEN) + fullscreen = 1; + + gl_ctx = SDL_GL_CreateContext(window); + if (!gl_ctx) { + stk_log(STK_LOG_ERROR, "video: SDL_GL_CreateContext failed: %s", + SDL_GetError()); + SDL_DestroyWindow(window); + SDL_Quit(); + return SKELE_INIT_FAILURE; + } + + SDL_GL_SetSwapInterval(1); + SDL_ShowWindow(window); + return SKELE_INIT_SUCCESS; +} + +void skele_video_shutdown(void) +{ + if (gl_ctx) + SDL_GL_DestroyContext(gl_ctx); + if (window) + SDL_DestroyWindow(window); + SDL_Quit(); + gl_ctx = NULL; + window = NULL; + vid_w = 0; + vid_h = 0; + cur_scale = 1; + fullscreen = 0; +} + +void skele_video_present(void) { SDL_GL_SwapWindow(window); } +void skele_video_toggle_fullscreen(void) { toggle_fullscreen(); } +void skele_video_cycle_scale(void) { cycle_scale(); } + +void skele_video_set_mouse_grab(uint8_t grab) +{ + SDL_SetWindowMouseGrab(window, grab ? true : false); + SDL_SetWindowRelativeMouseMode(window, grab ? true : false); +} + +void skele_video_set_title(const char *title) +{ + SDL_SetWindowTitle(window, title); +} diff --git a/src/platform/client/video/sdl.c b/src/platform/client/video/sdl.c new file mode 100644 index 0000000..1e56aa0 --- /dev/null +++ b/src/platform/client/video/sdl.c @@ -0,0 +1,253 @@ +#include "client/blit.h" +#include "client/palette.h" +#include "client/video.h" +#include "skele.h" +#include +#include +#include +#include + +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static SDL_Texture *texture = NULL; +static SDL_Palette *sdl_pal = NULL; +static uint16_t vid_w = 0; +static uint16_t vid_h = 0; +static uint32_t vid_total = 0; +static uint8_t cur_scale = 1; +static uint8_t fullscreen = 0; + +static uint8_t max_scale(uint16_t rw, uint16_t rh) +{ + SDL_DisplayID display; + const SDL_DisplayMode *mode; + uint16_t aw, ah; + uint8_t scale; + + display = + window ? SDL_GetDisplayForWindow(window) : SDL_GetPrimaryDisplay(); + mode = SDL_GetCurrentDisplayMode(display); + if (!mode) + return 1; + + aw = (uint16_t)((mode->w * 3) / 4); + ah = (uint16_t)((mode->h * 3) / 4); + scale = 1; + while ((rw * (scale + 1)) <= aw && (rh * (scale + 1)) <= ah) + scale++; + + return scale; +} + +static void set_scale(uint8_t scale) +{ + SDL_DisplayID display; + cur_scale = scale; + display = SDL_GetDisplayForWindow(window); + SDL_SetWindowSize(window, (int)(vid_w * scale), (int)(vid_h * scale)); + SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED_DISPLAY(display), + SDL_WINDOWPOS_CENTERED_DISPLAY(display)); +} + +static void toggle_fullscreen(void) +{ + fullscreen = !fullscreen; + SDL_SetWindowFullscreen(window, fullscreen); +} + +static void cycle_scale(void) +{ + uint8_t next; + if (fullscreen) + return; + next = cur_scale + 1; + if (next > max_scale(vid_w, vid_h)) + next = 1; + set_scale(next); +} + +uint8_t skele_video_init(skele_video_config_t cfg) +{ + SDL_DisplayID display; + SDL_WindowFlags flags = 0; + const SDL_DisplayMode *mode; + uint16_t window_w, window_h, aw, ah; + + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) { + stk_log(STK_LOG_ERROR, "video: SDL_Init failed: %s", + SDL_GetError()); + return SKELE_INIT_FAILURE; + } + + if (cfg.flags & SKELE_VIDEO_FULLSCREEN) + flags |= SDL_WINDOW_FULLSCREEN; + if (cfg.flags & SKELE_VIDEO_BORDERLESS) + flags |= SDL_WINDOW_BORDERLESS; + if (cfg.flags & SKELE_VIDEO_RESIZABLE) + flags |= SDL_WINDOW_RESIZABLE; + if (cfg.flags & SKELE_VIDEO_HIGHDPI) + flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY; + + display = SDL_GetPrimaryDisplay(); + vid_w = + cfg.render_width ? cfg.render_width : SKELE_DEFAULT_RENDER_WIDTH; + vid_h = + cfg.render_height ? cfg.render_height : SKELE_DEFAULT_RENDER_HEIGHT; + vid_total = (uint32_t)(vid_w * vid_h); + + if (cfg.window_width && cfg.window_height) { + window_w = cfg.window_width; + window_h = cfg.window_height; + cur_scale = 1; + } else { + mode = SDL_GetCurrentDisplayMode(display); + if (!mode) { + stk_log(STK_LOG_ERROR, + "video: SDL_GetCurrentDisplayMode failed: %s", + SDL_GetError()); + SDL_Quit(); + return SKELE_INIT_FAILURE; + } + + aw = (uint16_t)((mode->w * 3) / 4); + ah = (uint16_t)((mode->h * 3) / 4); + cur_scale = 1; + while ((vid_w * (cur_scale + 1)) <= aw && + (vid_h * (cur_scale + 1)) <= ah) + cur_scale++; + + window_w = vid_w * cur_scale; + window_h = vid_h * cur_scale; + } + + window = SDL_CreateWindow("skele", window_w, window_h, + flags | SDL_WINDOW_HIDDEN); + if (!window) { + stk_log(STK_LOG_ERROR, "video: SDL_CreateWindow failed: %s", + SDL_GetError()); + SDL_Quit(); + return SKELE_INIT_FAILURE; + } + + SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED_DISPLAY(display), + SDL_WINDOWPOS_CENTERED_DISPLAY(display)); + + if (cfg.flags & SKELE_VIDEO_FULLSCREEN) + fullscreen = 1; + + renderer = SDL_CreateRenderer(window, NULL); + if (!renderer) { + stk_log(STK_LOG_ERROR, "video: SDL_CreateRenderer failed: %s", + SDL_GetError()); + SDL_DestroyWindow(window); + SDL_Quit(); + return SKELE_INIT_FAILURE; + } + + texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_INDEX8, + SDL_TEXTUREACCESS_STREAMING, vid_w, vid_h); + if (!texture) { + stk_log(STK_LOG_ERROR, "video: SDL_CreateTexture failed: %s", + SDL_GetError()); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return SKELE_INIT_FAILURE; + } + + sdl_pal = SDL_CreatePalette(SKELE_PALETTE_COLORS); + if (!sdl_pal) { + stk_log(STK_LOG_ERROR, "video: SDL_CreatePalette failed: %s", + SDL_GetError()); + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return SKELE_INIT_FAILURE; + } + + SDL_SetTexturePalette(texture, sdl_pal); + + { + SDL_Color black[SKELE_PALETTE_COLORS]; + uint16_t i; + memset(black, 0, sizeof(black)); + for (i = 0; i < SKELE_PALETTE_COLORS; i++) + black[i].a = 255; + SDL_SetPaletteColors(sdl_pal, black, 0, SKELE_PALETTE_COLORS); + } + + SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); + SDL_SetRenderVSync(renderer, 1); + SDL_ShowWindow(window); + return SKELE_INIT_SUCCESS; +} + +void skele_video_shutdown(void) +{ + if (sdl_pal) + SDL_DestroyPalette(sdl_pal); + if (texture) + SDL_DestroyTexture(texture); + if (renderer) + SDL_DestroyRenderer(renderer); + if (window) + SDL_DestroyWindow(window); + SDL_Quit(); + sdl_pal = NULL; + texture = NULL; + renderer = NULL; + window = NULL; + vid_w = 0; + vid_h = 0; + vid_total = 0; + cur_scale = 1; + fullscreen = 0; +} + +void skele_video_present(void) { SDL_RenderPresent(renderer); } +void skele_video_toggle_fullscreen(void) { toggle_fullscreen(); } +void skele_video_cycle_scale(void) { cycle_scale(); } + +void skele_video_set_mouse_grab(uint8_t grab) +{ + SDL_SetWindowMouseGrab(window, grab ? true : false); + SDL_SetWindowRelativeMouseMode(window, grab ? true : false); +} + +void skele_palette_set(skele_palette_t pal) +{ + SDL_Color colors[SKELE_PALETTE_COLORS]; + uint16_t i; + uint32_t c; + + for (i = 0; i < SKELE_PALETTE_COLORS; i++) { + c = pal[i]; + colors[i].r = (uint8_t)(c >> 16); + colors[i].g = (uint8_t)(c >> 8); + colors[i].b = (uint8_t)(c); + colors[i].a = 255; + } + SDL_SetPaletteColors(sdl_pal, colors, 0, SKELE_PALETTE_COLORS); +} + +void skele_palette_set_index(uint8_t index, uint32_t color) +{ + SDL_Color c; + c.r = (uint8_t)(color >> 16); + c.g = (uint8_t)(color >> 8); + c.b = (uint8_t)(color); + c.a = 255; + SDL_SetPaletteColors(sdl_pal, &c, index, 1); +} + +void skele_video_blit(uint8_t *pixels) +{ + SDL_UpdateTexture(texture, NULL, pixels, vid_w); + SDL_RenderTexture(renderer, texture, NULL, NULL); +} + +void skele_video_set_title(const char *title) +{ + SDL_SetWindowTitle(window, title); +} diff --git a/src/platform/clock/posix.c b/src/platform/clock/posix.c new file mode 100644 index 0000000..e074d44 --- /dev/null +++ b/src/platform/clock/posix.c @@ -0,0 +1,35 @@ +#define _POSIX_C_SOURCE 199309L +#include "clock.h" +#include +#include + +static void (*signal_cb)(void) = NULL; + +static void handle_signal(int sig) +{ + (void)sig; + if (signal_cb) + signal_cb(); +} + +void skele_clock_init(void (*on_signal)(void)) +{ + signal_cb = on_signal; + signal(SIGINT, handle_signal); + signal(SIGTERM, handle_signal); +} + +uint64_t skele_time_ns(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)(ts.tv_sec * 1000000000ULL + ts.tv_nsec); +} + +void skele_sleep_ns(uint64_t ns) +{ + struct timespec ts; + ts.tv_sec = (time_t)(ns / 1000000000ULL); + ts.tv_nsec = (long)(ns % 1000000000ULL); + nanosleep(&ts, NULL); +} diff --git a/src/platform/clock/win32.c b/src/platform/clock/win32.c new file mode 100644 index 0000000..e5f294d --- /dev/null +++ b/src/platform/clock/win32.c @@ -0,0 +1,37 @@ +#include "clock.h" +#include + +static void (*signal_cb)(void) = NULL; +static LARGE_INTEGER perf_freq; + +static BOOL WINAPI handle_ctrl(DWORD type) +{ + (void)type; + if (signal_cb) + signal_cb(); + return TRUE; +} + +void skele_clock_init(void (*on_signal)(void)) +{ + signal_cb = on_signal; + QueryPerformanceFrequency(&perf_freq); + SetConsoleCtrlHandler(handle_ctrl, TRUE); +} + +uint64_t skele_time_ns(void) +{ + LARGE_INTEGER cnt; + QueryPerformanceCounter(&cnt); + return (uint64_t)(cnt.QuadPart * 1000000000ULL / perf_freq.QuadPart); +} + +void skele_sleep_ns(uint64_t ns) +{ + HANDLE timer = CreateWaitableTimer(NULL, TRUE, NULL); + LARGE_INTEGER li; + li.QuadPart = -(LONGLONG)(ns / 100); + SetWaitableTimer(timer, &li, 0, NULL, NULL, FALSE); + WaitForSingleObject(timer, INFINITE); + CloseHandle(timer); +} diff --git a/src/skele.c b/src/skele.c new file mode 100644 index 0000000..32798a6 --- /dev/null +++ b/src/skele.c @@ -0,0 +1,51 @@ +#include "skele.h" +#include +#include + +uint64_t skele_tick_ns = 1000000000ULL / SKELE_DEFAULT_TICK_RATE; + +static uint8_t stk_initialized = 0; + +const char *skele_version(void) { return "0.0.0"; } + +void skele_set_tick_rate(uint8_t rate) +{ + skele_tick_ns = 1000000000ULL / (rate ? rate : SKELE_DEFAULT_TICK_RATE); +} + +uint8_t skele_init(void) { return SKELE_INIT_SUCCESS; } + +void skele_shutdown(void) {} + +void skele_tick(void) {} + +uint8_t skele_stk_setup(void) +{ + if (stk_initialized) + return SKELE_INIT_SUCCESS; + + stk_set_log_prefix("skele"); + stk_set_module_init_fn("skele_mod_init"); + stk_set_module_shutdown_fn("skele_mod_shutdown"); + stk_set_module_name_fn("skele_mod_name"); + stk_set_module_version_fn("skele_mod_ver"); + stk_set_module_description_fn("skele_mod_desc"); + stk_set_module_deps_sym("skele_mod_deps"); + + if (stk_init() != STK_INIT_SUCCESS) { + stk_log(STK_LOG_ERROR, "failed to initialize stk"); + return SKELE_INIT_FAILURE; + } + + stk_initialized = 1; + stk_log(STK_LOG_INFO, "skele v%s", SKELE_VERSION); + return SKELE_INIT_SUCCESS; +} + +void skele_stk_teardown(void) +{ + if (!stk_initialized) + return; + stk_shutdown(); + stk_initialized = 0; +}