diff --git a/archive/dusk/map.py b/archive/dusk/map.py index 358e87d..4b2ebbf 100644 --- a/archive/dusk/map.py +++ b/archive/dusk/map.py @@ -156,7 +156,7 @@ class Map: newTopLeftChunkY = y // CHUNK_HEIGHT - (MAP_HEIGHT // 2) newTopLeftChunkZ = z // CHUNK_DEPTH - (MAP_DEPTH // 2) - if (newTopLeftChunkX != self.topLeftX or + if(newTopLeftChunkX != self.topLeftX or newTopLeftChunkY != self.topLeftY or newTopLeftChunkZ != self.topLeftZ): @@ -166,7 +166,7 @@ class Map: chunkWorldX = chunk.x chunkWorldY = chunk.y chunkWorldZ = chunk.z - if (chunkWorldX < newTopLeftChunkX or + if(chunkWorldX < newTopLeftChunkX or chunkWorldX >= newTopLeftChunkX + MAP_WIDTH or chunkWorldY < newTopLeftChunkY or chunkWorldY >= newTopLeftChunkY + MAP_HEIGHT or diff --git a/assets/scene/minesweeper.lua b/assets/scene/minesweeper.lua index 993693e..a6fa2ec 100644 --- a/assets/scene/minesweeper.lua +++ b/assets/scene/minesweeper.lua @@ -192,7 +192,7 @@ function sceneRender() spriteBatchPush( nil, x, y, x + 32, y + 32, - colorBlue() + colorWhite() ) -- Update mouse position diff --git a/cmake/targets/linux.cmake b/cmake/targets/linux.cmake index f83d136..bec0a02 100644 --- a/cmake/targets/linux.cmake +++ b/cmake/targets/linux.cmake @@ -29,6 +29,7 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC DUSK_SDL2 DUSK_OPENGL DUSK_LINUX + DUSK_OPENGL_LEGACY DUSK_DISPLAY_SIZE_DYNAMIC DUSK_DISPLAY_WIDTH_DEFAULT=640 DUSK_DISPLAY_HEIGHT_DEFAULT=480 diff --git a/cmake/targets/psp.cmake b/cmake/targets/psp.cmake index e58dd80..4f3a6b0 100644 --- a/cmake/targets/psp.cmake +++ b/cmake/targets/psp.cmake @@ -36,6 +36,7 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC DUSK_PSP DUSK_INPUT_GAMEPAD DUSK_PLATFORM_ENDIAN_LITTLE + DUSK_OPENGL_LEGACY DUSK_DISPLAY_WIDTH=480 DUSK_DISPLAY_HEIGHT=272 ) diff --git a/src/dusk/display/CMakeLists.txt b/src/dusk/display/CMakeLists.txt index 6d9754a..ecdf8fa 100644 --- a/src/dusk/display/CMakeLists.txt +++ b/src/dusk/display/CMakeLists.txt @@ -14,6 +14,7 @@ add_subdirectory(camera) add_subdirectory(framebuffer) add_subdirectory(mesh) add_subdirectory(screen) +add_subdirectory(shader) add_subdirectory(spritebatch) add_subdirectory(text) add_subdirectory(texture) diff --git a/src/dusk/display/camera/camera.c b/src/dusk/display/camera/camera.c index af0639e..bcabaf0 100644 --- a/src/dusk/display/camera/camera.c +++ b/src/dusk/display/camera/camera.c @@ -45,13 +45,63 @@ void cameraInitOrthographic(camera_t *camera) { camera->_2d.zoom = 1.0f; } -void cameraPushMatrix(camera_t *camera) { - assertNotNull(camera, "Invalid camera"); - cameraPushMatrixPlatform(camera); +void cameraGetProjectionMatrix(camera_t *camera, mat4 dest) { + assertNotNull(camera, "Not a camera component"); + assertNotNull(dest, "Destination matrix must not be null"); + + if( + camera->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE || + camera->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED + ) { + glm_mat4_identity(dest); + glm_perspective( + camera->perspective.fov, + SCREEN.aspect, + camera->nearClip, + camera->farClip, + dest + ); + + if(camera->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED) { + dest[1][1] *= -1.0f; + } + } else if(camera->projType == CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) { + glm_mat4_identity(dest); + glm_ortho( + camera->orthographic.left, + camera->orthographic.right, + camera->orthographic.top, + camera->orthographic.bottom, + camera->nearClip, + camera->farClip, + dest + ); + } } -void cameraPopMatrix(void) { - #ifdef cameraPopMatrixPlatform - cameraPopMatrixPlatform(); - #endif +void cameraGetViewMatrix(camera_t *camera, mat4 dest) { + assertNotNull(camera, "Not a camera component"); + assertNotNull(dest, "Destination matrix must not be null"); + + if(camera->viewType == CAMERA_VIEW_TYPE_MATRIX) { + glm_mat4_copy(camera->view, dest); + } else if(camera->viewType == CAMERA_VIEW_TYPE_LOOKAT) { + glm_mat4_identity(dest); + glm_lookat( + camera->lookat.position, + camera->lookat.target, + camera->lookat.up, + dest + ); + } else if(camera->viewType == CAMERA_VIEW_TYPE_2D) { + glm_mat4_identity(dest); + glm_lookat( + (vec3){ camera->_2d.position[0], camera->_2d.position[1], 0.5f }, + (vec3){ camera->_2d.position[0], camera->_2d.position[1], 0.0f }, + (vec3){ 0.0f, 1.0f, 0.0f }, + dest + ); + } else if(camera->viewType == CAMERA_VIEW_TYPE_LOOKAT_PIXEL_PERFECT) { + assertUnreachable("LOOKAT_PIXEL_PERFECT view type is not implemented yet"); + } } \ No newline at end of file diff --git a/src/dusk/display/camera/camera.h b/src/dusk/display/camera/camera.h index 77426d7..d83fb39 100644 --- a/src/dusk/display/camera/camera.h +++ b/src/dusk/display/camera/camera.h @@ -86,13 +86,17 @@ void cameraInitPerspective(camera_t *camera); void cameraInitOrthographic(camera_t *camera); /** - * Pushes the camera's view matrix onto the matrix stack. + * Gets the projection matrix for a camera. * - * @param id The ID of the camera entity to use. + * @param camera Camera to get the projection matrix for + * @param dest Matrix to store the projection matrix in */ -void cameraPushMatrix(camera_t *camera); +void cameraGetProjectionMatrix(camera_t *camera, mat4 dest); /** - * Pops the camera's view matrix off the matrix stack. + * Gets the view matrix for a camera. + * + * @param camera Camera to get the view matrix for + * @param dest Matrix to store the view matrix in */ -void cameraPopMatrix(void); \ No newline at end of file +void cameraGetViewMatrix(camera_t *camera, mat4 dest); \ No newline at end of file diff --git a/src/dusk/display/display.c b/src/dusk/display/display.c index 8419140..61ce15a 100644 --- a/src/dusk/display/display.c +++ b/src/dusk/display/display.c @@ -17,6 +17,8 @@ #include "util/memory.h" #include "util/string.h" #include "asset/asset.h" +#include "display/shader/shaderunlit.h" +#include "time/time.h" display_t DISPLAY = { 0 }; @@ -27,6 +29,7 @@ errorret_t displayInit(void) { errorChain(displayPlatformInit()); #endif + errorChain(shaderInit(&SHADER_UNLIT, &SHADER_UNLIT_DEFINITION)); errorChain(quadInit()); errorChain(frameBufferInitBackBuffer()); errorChain(spriteBatchInit()); @@ -46,16 +49,40 @@ errorret_t displayUpdate(void) { errorChain(frameBufferBind(NULL)); // Bind screen and render scene - screenBind(); + errorChain(screenBind()); frameBufferClear( FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH, SCREEN.background ); - errorChain(sceneRender()); + errorChain(shaderBind(&SHADER_UNLIT)); + + camera_t camera; + cameraInitOrthographic(&camera); + camera.orthographic.left = 0.0f; + camera.orthographic.right = SCREEN.width; + camera.orthographic.top = SCREEN.height; + camera.orthographic.bottom = 0.0f; + + mat4 proj, view, model; + cameraGetProjectionMatrix(&camera, proj); + cameraGetViewMatrix(&camera, view); + glm_mat4_identity(model); + + errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj)); + errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view)); + errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model)); + + errorChain(spriteBatchPush( + NULL, + TIME.time * 2.0f, TIME.time * 3.0f, 100, 100, COLOR_WHITE, 0, 0, 1, 1 + )); + errorChain(spriteBatchFlush()); + + // errorCatch(errorPrint(sceneRender())); // Render UI - uiRender(); + // uiRender(); // Finish up screenUnbind(); @@ -69,6 +96,7 @@ errorret_t displayUpdate(void) { } errorret_t displayDispose(void) { + errorChain(shaderDispose(&SHADER_UNLIT)); errorChain(spriteBatchDispose()); screenDispose(); errorChain(textDispose()); diff --git a/src/dusk/display/mesh/mesh.c b/src/dusk/display/mesh/mesh.c index 5dd79d8..005a6d4 100644 --- a/src/dusk/display/mesh/mesh.c +++ b/src/dusk/display/mesh/mesh.c @@ -25,6 +25,29 @@ errorret_t meshInit( errorOk(); } +errorret_t meshFlush( + mesh_t *mesh, + const int32_t vertexOffset, + const int32_t vertexCount +) { + #ifdef meshFlushPlatform + assertNotNull(mesh, "Mesh cannot be NULL"); + assertTrue(vertexOffset >= 0, "Vertex offset must be non-negative."); + assertTrue(vertexCount == -1 || vertexCount > 0, "Vertex count incorrect."); + + int32_t vertCount = meshGetVertexCount(mesh); + assertTrue(vertexOffset < (vertCount - 1), "Need at least one vert to draw"); + + int32_t drawCount = vertexCount; + if(vertexCount == -1) { + drawCount = vertCount - vertexOffset; + } + + errorChain(meshFlushPlatform(mesh, vertexOffset, vertexCount)); + #endif + errorOk(); +} + errorret_t meshDraw( const mesh_t *mesh, const int32_t vertexOffset, @@ -32,7 +55,7 @@ errorret_t meshDraw( ) { assertNotNull(mesh, "Mesh cannot be NULL"); assertTrue(vertexOffset >= 0, "Vertex offset must be non-negative"); - assertTrue(vertexCount >= -1, "Vertex count must be -1 or non-negative"); + assertTrue(vertexCount == -1 || vertexCount > 0, "Incorrect vert count"); int32_t vertDrawCount = vertexCount; if(vertexCount == -1) { diff --git a/src/dusk/display/mesh/mesh.h b/src/dusk/display/mesh/mesh.h index b580f94..da16166 100644 --- a/src/dusk/display/mesh/mesh.h +++ b/src/dusk/display/mesh/mesh.h @@ -40,6 +40,22 @@ errorret_t meshInit( const meshvertex_t *vertices ); +/** + * Instructs the mesh to flush the vertices to the GPU. This is surprisingly + * only really necessary on modern devices, as we tend to let older devices + * read the vertices from the main memory directly. + * + * @param mesh Mesh to flush the vertices for. + * @param vertexOffset Start vertex to flush. + * @param vertexCount Count of vertices to flush, set to -1 for all. + * @return Error state. + */ +errorret_t meshFlush( + mesh_t *mesh, + const int32_t vertexOffset, + const int32_t vertexCount +); + /** * Draws a mesh. * diff --git a/src/dusk/display/screen/screen.c b/src/dusk/display/screen/screen.c index 831dc5d..ad9ab66 100644 --- a/src/dusk/display/screen/screen.c +++ b/src/dusk/display/screen/screen.c @@ -9,6 +9,7 @@ #include "assert/assert.h" #include "util/memory.h" #include "display/mesh/quad.h" +#include "display/shader/shaderunlit.h" screen_t SCREEN; @@ -44,11 +45,11 @@ errorret_t screenInit() { #endif // Init screen to backbuffer mode by default - screenBind(); + errorChain(screenBind()); errorOk(); } -void screenBind() { +errorret_t screenBind() { // Assume backbuffer is currently bound. switch(SCREEN.mode) { case SCREEN_MODE_BACKBUFFER: { @@ -59,7 +60,7 @@ void screenBind() { // No needd for a framebuffer. #ifdef DUSK_DISPLAY_SIZE_DYNAMIC if(SCREEN.framebufferReady) { - frameBufferDispose(&SCREEN.framebuffer); + errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } #endif @@ -79,19 +80,21 @@ void screenBind() { curFbHeight = frameBufferGetHeight(&SCREEN.framebuffer); if(curFbWidth == SCREEN.width && curFbHeight == SCREEN.height) { // Correct size, nothing to do. - frameBufferBind(&SCREEN.framebuffer); - return; + errorChain(frameBufferBind(&SCREEN.framebuffer)); + errorOk(); } // Need a new framebuffer. - frameBufferDispose(&SCREEN.framebuffer); + errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } // Create new framebuffer - frameBufferInit(&SCREEN.framebuffer, SCREEN.width, SCREEN.height); + errorChain(frameBufferInit( + &SCREEN.framebuffer, SCREEN.width, SCREEN.height + )); SCREEN.framebufferReady = true; - frameBufferBind(&SCREEN.framebuffer); + errorChain(frameBufferBind(&SCREEN.framebuffer)); break; } @@ -109,10 +112,10 @@ void screenBind() { SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; if(SCREEN.framebufferReady) { - frameBufferDispose(&SCREEN.framebuffer); + errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } - return; + errorOk(); } int32_t newFbWidth, newFbHeight; @@ -136,24 +139,26 @@ void screenBind() { SCREEN.width = newFbWidth; SCREEN.height = newFbHeight; SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; - frameBufferBind(&SCREEN.framebuffer); - return; + errorChain(frameBufferBind(&SCREEN.framebuffer)); + errorOk(); } // Need a new framebuffer. - frameBufferDispose(&SCREEN.framebuffer); + errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } // Create new framebuffer - frameBufferInit(&SCREEN.framebuffer, newFbWidth, newFbHeight); + errorChain(frameBufferInit( + &SCREEN.framebuffer, newFbWidth, newFbHeight + )); SCREEN.width = newFbWidth; SCREEN.height = newFbHeight; SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; SCREEN.framebufferReady = true; // Bind FB - frameBufferBind(&SCREEN.framebuffer); + errorChain(frameBufferBind(&SCREEN.framebuffer)); break; } @@ -173,10 +178,10 @@ void screenBind() { if(fbWidth == newFbWidth && fbHeight == newFbHeight) { // No need to use framebuffer. if(SCREEN.framebufferReady) { - frameBufferDispose(&SCREEN.framebuffer); + errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } - return; + errorOk(); } if(SCREEN.framebufferReady) { @@ -185,19 +190,21 @@ void screenBind() { curFbWidth = frameBufferGetWidth(&SCREEN.framebuffer); curFbHeight = frameBufferGetHeight(&SCREEN.framebuffer); if(curFbWidth == newFbWidth && curFbHeight == newFbHeight) { - frameBufferBind(&SCREEN.framebuffer); - return; + errorChain(frameBufferBind(&SCREEN.framebuffer)); + errorOk(); } // Need a new framebuffer. - frameBufferDispose(&SCREEN.framebuffer); + errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } // Create a new framebuffer. - frameBufferInit(&SCREEN.framebuffer, newFbWidth, newFbHeight); + errorChain(frameBufferInit( + &SCREEN.framebuffer, newFbWidth, newFbHeight + )); SCREEN.framebufferReady = true; - frameBufferBind(&SCREEN.framebuffer); + errorChain(frameBufferBind(&SCREEN.framebuffer)); break; } @@ -217,10 +224,10 @@ void screenBind() { if(fbWidth == newFbWidth && fbHeight == newFbHeight) { // No need to use framebuffer. if(SCREEN.framebufferReady) { - frameBufferDispose(&SCREEN.framebuffer); + errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } - return; + errorOk(); } if(SCREEN.framebufferReady) { @@ -229,19 +236,21 @@ void screenBind() { curFbWidth = frameBufferGetWidth(&SCREEN.framebuffer); curFbHeight = frameBufferGetHeight(&SCREEN.framebuffer); if(curFbWidth == newFbWidth && curFbHeight == newFbHeight) { - frameBufferBind(&SCREEN.framebuffer); - return; + errorChain(frameBufferBind(&SCREEN.framebuffer)); + errorOk(); } // Need a new framebuffer. - frameBufferDispose(&SCREEN.framebuffer); + errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } // Create a new framebuffer. - frameBufferInit(&SCREEN.framebuffer, newFbWidth, newFbHeight); + errorChain(frameBufferInit( + &SCREEN.framebuffer, newFbWidth, newFbHeight + )); SCREEN.framebufferReady = true; - frameBufferBind(&SCREEN.framebuffer); + errorChain(frameBufferBind(&SCREEN.framebuffer)); break; } @@ -262,9 +271,11 @@ void screenBind() { break; } } + + errorOk(); } -void screenUnbind() { +errorret_t screenUnbind() { switch(SCREEN.mode) { // Nothing to do here. case SCREEN_MODE_BACKBUFFER: @@ -275,7 +286,9 @@ void screenUnbind() { case SCREEN_MODE_FIXED_HEIGHT: case SCREEN_MODE_FIXED_SIZE: case SCREEN_MODE_FIXED_WIDTH: - if(SCREEN.framebufferReady) frameBufferBind(NULL); + if(SCREEN.framebufferReady) { + errorChain(frameBufferBind(NULL)); + } break; case SCREEN_MODE_FIXED_VIEWPORT_HEIGHT: @@ -286,17 +299,19 @@ void screenUnbind() { assertUnreachable("Invalid screen mode."); break; } + + errorOk(); } -void screenRender() { +errorret_t screenRender() { if(SCREEN.mode == SCREEN_MODE_BACKBUFFER) { - return; + errorOk(); } #ifdef DUSK_DISPLAY_SIZE_DYNAMIC if(SCREEN.mode == SCREEN_MODE_FIXED_VIEWPORT_HEIGHT) { glViewport(0, 0, SCREEN.width, SCREEN.height); - return; + errorOk(); } if( @@ -307,7 +322,7 @@ void screenRender() { ) { if(!SCREEN.framebufferReady) { // Nothing to do here. - return; + errorOk(); } float_t bbWidth, bbHeight; @@ -356,23 +371,34 @@ void screenRender() { FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH, COLOR_BLACK ); - cameraPushMatrix(&SCREEN.framebufferCamera); - textureBind(&SCREEN.framebuffer.texture); - meshDraw(&SCREEN.frameBufferMesh, 0, -1); - cameraPopMatrix(); - return; + shaderBind(&SHADER_UNLIT); + mat4 proj, view, model; + cameraGetProjectionMatrix(&SCREEN.framebufferCamera, proj); + cameraGetViewMatrix(&SCREEN.framebufferCamera, view); + glm_mat4_identity(model); + shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj); + shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view); + shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model); + + // errorChain(textureBind(&SCREEN.framebuffer.texture)); + errorChain(meshDraw(&SCREEN.frameBufferMesh, 0, -1)); + + errorOk(); }; #endif assertUnreachable("Invalid screen mode."); + errorThrow("Invalid screen mode."); } -void screenDispose() { +errorret_t screenDispose() { #ifdef DUSK_DISPLAY_SIZE_DYNAMIC if(SCREEN.framebufferReady) { - frameBufferDispose(&SCREEN.framebuffer); + errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } #endif + + errorOk(); } \ No newline at end of file diff --git a/src/dusk/display/screen/screen.h b/src/dusk/display/screen/screen.h index 148713d..b496da7 100644 --- a/src/dusk/display/screen/screen.h +++ b/src/dusk/display/screen/screen.h @@ -90,20 +90,28 @@ errorret_t screenInit(); /** * Binds the screen, this is done before rendering game content. + * + * @return Error code and state, if error occurs. */ -void screenBind(); +errorret_t screenBind(); /** * Unbinds the screen, does nothing for now. + * + * @return Error code and state, if error occurs. */ -void screenUnbind(); +errorret_t screenUnbind(); /** * Renders the screen to the current framebuffer. + * + * @return Error code and state, if error occurs. */ -void screenRender(); +errorret_t screenRender(); /** * Disposes the screen system. + * + * @return Error code and state, if error occurs. */ -void screenDispose(); \ No newline at end of file +errorret_t screenDispose(); \ No newline at end of file diff --git a/src/dusk/display/shader/CMakeLists.txt b/src/dusk/display/shader/CMakeLists.txt new file mode 100644 index 0000000..32debd1 --- /dev/null +++ b/src/dusk/display/shader/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + shader.c +) \ No newline at end of file diff --git a/src/dusk/display/shader/shader.c b/src/dusk/display/shader/shader.c new file mode 100644 index 0000000..cc6550a --- /dev/null +++ b/src/dusk/display/shader/shader.c @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "shader.h" +#include "assert/assert.h" + +errorret_t shaderInit(shader_t *shader, const shaderdefinition_t *def) { + assertNotNull(shader, "Shader cannot be null"); + errorChain(shaderInitPlatform(shader, def)); + errorOk(); +} + +errorret_t shaderBind(shader_t *shader) { + assertNotNull(shader, "Shader cannot be null"); + errorChain(shaderBindPlatform(shader)); + errorOk(); +} + +errorret_t shaderSetMatrix( + shader_t *shader, + const char_t *name, + mat4 matrix +) { + assertNotNull(shader, "Shader cannot be null"); + assertStrLenMin(name, 1, "Uniform name cannot be empty"); + assertNotNull(matrix, "Matrix cannot be null"); + errorChain(shaderSetMatrixPlatform(shader, name, matrix)); + errorOk(); +} + +errorret_t shaderDispose(shader_t *shader) { + assertNotNull(shader, "Shader cannot be null"); + errorChain(shaderDisposePlatform(shader)); + errorOk(); +} \ No newline at end of file diff --git a/src/dusk/display/shader/shader.h b/src/dusk/display/shader/shader.h new file mode 100644 index 0000000..2af399d --- /dev/null +++ b/src/dusk/display/shader/shader.h @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" +#include "display/texture/texture.h" +#include "display/shader/shaderplatform.h" + +#ifndef shaderInitPlatform + #error "shaderInitPlatform must be defined to use shader.h" +#endif +#ifndef shaderBindPlatform + #error "shaderBindPlatform must be defined to use shader.h" +#endif +#ifndef shaderSetMatrixPlatform + #error "shaderSetMatrixPlatform must be defined to use shader.h" +#endif +#ifndef shaderDisposePlatform + #error "shaderDisposePlatform must be defined to use shader.h" +#endif + +typedef shaderplatform_t shader_t; +typedef shaderdefinitionplatform_t shaderdefinition_t; + +/** + * Initializes a shader. This is platform dependant. + * + * @param shader Shader to initialize + * @param def Definition of the shader to initialize with. + * @return Error if failure, otherwise errorOk + */ +errorret_t shaderInit(shader_t *shader, const shaderdefinition_t *def); + +/** + * Binds a shader. This is platform dependant. + * + * @param shader Shader to bind + * @return Error if failure, otherwise errorOk + */ +errorret_t shaderBind(shader_t *shader); + +/** + * Sets a matrix uniform in the shader. This is platform dependant. + * + * @param shader Shader to set the matrix in + * @param name Name of the uniform to set + * @param matrix Matrix to set + * @return Error if failure, otherwise errorOk + */ +errorret_t shaderSetMatrix( + shader_t *shader, + const char_t *name, + mat4 matrix +); + +/** + * Sets a texture uniform in the shader. This is platform dependant. + * + * @param shader Shader to set the texture in + * @param name Name of the uniform to set + * @param texture Texture to set + * @return Error if failure, otherwise errorOk + */ +errorret_t shaderSetTexture( + shader_t *shader, + const char_t *name, + texture_t *texture +); + +/** + * Disposes of a shader. This is platform dependant. + * + * @param shader Shader to dispose + * @return Error if failure, otherwise errorOk + */ +errorret_t shaderDispose(shader_t *shader); \ No newline at end of file diff --git a/src/dusk/display/shader/shaderunlit.h b/src/dusk/display/shader/shaderunlit.h new file mode 100644 index 0000000..881a695 --- /dev/null +++ b/src/dusk/display/shader/shaderunlit.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "shader.h" + +#define SHADER_UNLIT_PROJECTION "u_Proj" +#define SHADER_UNLIT_VIEW "u_View" +#define SHADER_UNLIT_MODEL "u_Model" + +extern shaderdefinition_t SHADER_UNLIT_DEFINITION; +static shader_t SHADER_UNLIT; \ No newline at end of file diff --git a/src/dusk/display/spritebatch/spritebatch.c b/src/dusk/display/spritebatch/spritebatch.c index cf41509..5015bd5 100644 --- a/src/dusk/display/spritebatch/spritebatch.c +++ b/src/dusk/display/spritebatch/spritebatch.c @@ -19,7 +19,7 @@ errorret_t spriteBatchInit() { &SPRITEBATCH.mesh, QUAD_PRIMITIVE_TYPE, SPRITEBATCH_VERTEX_COUNT, - &SPRITEBATCH_VERTICES[0] + SPRITEBATCH_VERTICES )); errorOk(); } @@ -82,11 +82,11 @@ errorret_t spriteBatchFlush() { if(SPRITEBATCH.spriteCount == 0) { errorOk(); } - + + size_t vertexCount = QUAD_VERTEX_COUNT * SPRITEBATCH.spriteCount; + errorChain(meshFlush(&SPRITEBATCH.mesh, 0, vertexCount)); errorChain(textureBind(SPRITEBATCH.currentTexture)); - errorChain(meshDraw( - &SPRITEBATCH.mesh, 0, QUAD_VERTEX_COUNT * SPRITEBATCH.spriteCount - )); + errorChain(meshDraw(&SPRITEBATCH.mesh, 0, vertexCount)); spriteBatchClear(); errorOk(); } diff --git a/src/dusk/display/spritebatch/spritebatch.h b/src/dusk/display/spritebatch/spritebatch.h index 6a87c19..a22ec78 100644 --- a/src/dusk/display/spritebatch/spritebatch.h +++ b/src/dusk/display/spritebatch/spritebatch.h @@ -9,7 +9,7 @@ #include "display/mesh/quad.h" #include "display/texture/texture.h" -#define SPRITEBATCH_SPRITES_MAX 16 +#define SPRITEBATCH_SPRITES_MAX 1 #define SPRITEBATCH_VERTEX_COUNT (SPRITEBATCH_SPRITES_MAX * QUAD_VERTEX_COUNT) diff --git a/src/dusk/script/module/display/modulecamera.c b/src/dusk/script/module/display/modulecamera.c index 6d70443..737df9d 100644 --- a/src/dusk/script/module/display/modulecamera.c +++ b/src/dusk/script/module/display/modulecamera.c @@ -69,8 +69,6 @@ void moduleCamera(scriptcontext_t *context) { // Methods lua_register(context->luaState, "cameraCreate", moduleCameraCreate); - lua_register(context->luaState, "cameraPushMatrix", moduleCameraPushMatrix); - lua_register(context->luaState, "cameraPopMatrix", moduleCameraPopMatrix); } int moduleCameraCreate(lua_State *L) { @@ -118,26 +116,7 @@ int moduleCameraCreate(lua_State *L) { return 1; } -int moduleCameraPushMatrix(lua_State *L) { - assertNotNull(L, "Lua state cannot be NULL."); - assertTrue(lua_gettop(L) >= 1, "cameraPushMatrix requires 1 arg."); - assertTrue(lua_isuserdata(L, 1), "cameraPushMatrix arg must be userdata."); - - // Camera should be provided (pointer to camera_t). - camera_t *cam = (camera_t *)luaL_checkudata(L, 1, "camera_mt"); - assertNotNull(cam, "Camera pointer cannot be NULL."); - - cameraPushMatrix(cam); - return 0; -} - -int moduleCameraPopMatrix(lua_State *L) { - assertNotNull(L, "Lua state cannot be NULL."); - cameraPopMatrix(); - return 0; -} - -int moduleCameraIndex(lua_State *l) { + int moduleCameraIndex(lua_State *l) { assertNotNull(l, "Lua state cannot be NULL."); const char_t *key = luaL_checkstring(l, 2); diff --git a/src/dusk/script/module/display/modulecamera.h b/src/dusk/script/module/display/modulecamera.h index 7032e53..2931d3c 100644 --- a/src/dusk/script/module/display/modulecamera.h +++ b/src/dusk/script/module/display/modulecamera.h @@ -23,22 +23,6 @@ void moduleCamera(scriptcontext_t *context); */ int moduleCameraCreate(lua_State *L); -/** - * Script binding for pushing the camera matrix onto the matrix stack. - * - * @param L The Lua state. - * @return Number of return values on the Lua stack. - */ -int moduleCameraPushMatrix(lua_State *L); - -/** - * Script binding for popping the camera matrix from the matrix stack. - * - * @param L The Lua state. - * @return Number of return values on the Lua stack. - */ -int moduleCameraPopMatrix(lua_State *L); - /** * Getter for camera structure fields. * diff --git a/src/dusk/script/module/display/modulespritebatch.c b/src/dusk/script/module/display/modulespritebatch.c index 2d8e976..ce96b53 100644 --- a/src/dusk/script/module/display/modulespritebatch.c +++ b/src/dusk/script/module/display/modulespritebatch.c @@ -91,7 +91,7 @@ int moduleSpriteBatchPush(lua_State *L) { float_t maxX = (float_t)lua_tonumber(L, 4); float_t maxY = (float_t)lua_tonumber(L, 5); - spriteBatchPush( + errorret_t ret = spriteBatchPush( tex, minX, minY, @@ -103,6 +103,14 @@ int moduleSpriteBatchPush(lua_State *L) { u1, v1 ); + if(ret.code != ERROR_OK) { + int err = luaL_error(L, + "Failed to push sprite to batch: %s", + ret.state->message + ); + errorCatch(errorPrint(ret)); + return err; + } return 0; } \ No newline at end of file diff --git a/src/dusk/script/module/scene/modulescene.c b/src/dusk/script/module/scene/modulescene.c index acc56a3..30df0eb 100644 --- a/src/dusk/script/module/scene/modulescene.c +++ b/src/dusk/script/module/scene/modulescene.c @@ -19,7 +19,7 @@ int moduleSceneSet(lua_State *L) { assertNotNull(L, "Lua state cannot be NULL"); // Need string - if (!lua_isstring(L, 1)) { + if(!lua_isstring(L, 1)) { luaL_error(L, "sceneSet requires a string argument"); return 0; } diff --git a/src/dusk/script/module/system/modulesystem.c b/src/dusk/script/module/system/modulesystem.c index 1ce2ee7..6c885b7 100644 --- a/src/dusk/script/module/system/modulesystem.c +++ b/src/dusk/script/module/system/modulesystem.c @@ -26,12 +26,12 @@ int moduleSysPrint(lua_State *L) { luaL_Buffer b; luaL_buffinit(L, &b); - for (int i = 1; i <= n; ++i) { + for(int i = 1; i <= n; ++i) { size_t len; const char *s = luaL_tolstring(L, i, &len); // converts any value to string luaL_addlstring(&b, s, len); lua_pop(L, 1); // pop result of luaL_tolstring - if (i < n) luaL_addlstring(&b, "\t", 1); + if(i < n) luaL_addlstring(&b, "\t", 1); } luaL_pushresult(&b); diff --git a/src/dusk/ui/ui.c b/src/dusk/ui/ui.c index cfa73bf..c71ee80 100644 --- a/src/dusk/ui/ui.c +++ b/src/dusk/ui/ui.c @@ -30,11 +30,11 @@ void uiRender(void) { UI.camera.orthographic.right = SCREEN.width; UI.camera.orthographic.top = SCREEN.height; - cameraPushMatrix(&UI.camera); + // cameraPushMatrix(&UI.camera); spriteBatchClear(); spriteBatchFlush(); - cameraPopMatrix(); + // cameraPopMatrix(); } void uiDispose(void) { diff --git a/src/duskgl/assert/assertgl.h b/src/duskgl/assert/assertgl.h new file mode 100644 index 0000000..03425cd --- /dev/null +++ b/src/duskgl/assert/assertgl.h @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "assert/assert.h" +#include "error/errorgl.h" + +#define assertNoGLError(message) \ + assertTrue(errorGLCheck().code == ERROR_OK, message) + +// EOF \ No newline at end of file diff --git a/src/duskgl/display/CMakeLists.txt b/src/duskgl/display/CMakeLists.txt index 4c8ef86..bd57837 100644 --- a/src/duskgl/display/CMakeLists.txt +++ b/src/duskgl/display/CMakeLists.txt @@ -13,4 +13,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} add_subdirectory(camera) add_subdirectory(framebuffer) add_subdirectory(texture) -add_subdirectory(mesh) \ No newline at end of file +add_subdirectory(mesh) +add_subdirectory(shader) \ No newline at end of file diff --git a/src/duskgl/display/camera/cameragl.c b/src/duskgl/display/camera/cameragl.c index 7f31a6e..528e213 100644 --- a/src/duskgl/display/camera/cameragl.c +++ b/src/duskgl/display/camera/cameragl.c @@ -8,7 +8,7 @@ #include "display/camera/camera.h" #include "display/framebuffer/framebuffer.h" #include "display/screen/screen.h" -#include "assert/assert.h" +#include "assert/assertgl.h" void cameraPushMatrixGL(camera_t *camera) { assertNotNull(camera, "Not a camera component"); @@ -117,16 +117,19 @@ void cameraPushMatrixGL(camera_t *camera) { assertUnreachable("Invalid camera view type"); } - glPushMatrix(); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glLoadMatrixf((const GLfloat*)projection); + // glPushMatrix(); + // glMatrixMode(GL_PROJECTION); + // glLoadIdentity(); + // glLoadMatrixf((const GLfloat*)projection); + assertNoGLError("Failed to set projection matrix"); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glLoadMatrixf((const GLfloat*)view); + // glMatrixMode(GL_MODELVIEW); + // glLoadIdentity(); + // glLoadMatrixf((const GLfloat*)view); + assertNoGLError("Failed to set view matrix"); } void cameraPopMatrixGL(void) { - glPopMatrix(); + // glPopMatrix(); + assertNoGLError("Failed to pop camera matrix"); } \ No newline at end of file diff --git a/src/duskgl/display/displaygl.c b/src/duskgl/display/displaygl.c index c5d9cd5..e8b2420 100644 --- a/src/duskgl/display/displaygl.c +++ b/src/duskgl/display/displaygl.c @@ -9,8 +9,8 @@ errorret_t displayOpenGLInit(void) { glDisable(GL_CULL_FACE); - glDisable(GL_LIGHTING);// PSP defaults this on? - glShadeModel(GL_SMOOTH); // Fixes color on PSP? + // glDisable(GL_LIGHTING);// PSP defaults this on? + // glShadeModel(GL_SMOOTH); // Fixes color on PSP? errorChain(errorGLCheck()); glEnable(GL_DEPTH_TEST); @@ -24,9 +24,5 @@ errorret_t displayOpenGLInit(void) { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); errorChain(errorGLCheck()); - glEnableClientState(GL_COLOR_ARRAY);// To confirm: every frame on PSP? - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_VERTEX_ARRAY); - errorChain(errorGLCheck()); errorOk(); } \ No newline at end of file diff --git a/src/duskgl/display/displaygl.h b/src/duskgl/display/displaygl.h index 1f2d59a..2944f22 100644 --- a/src/duskgl/display/displaygl.h +++ b/src/duskgl/display/displaygl.h @@ -10,5 +10,7 @@ /** * Initializes the OpenGL specific contexts for rendering. + * + * @return An errorret_t indicating success or failure of the initialization. */ errorret_t displayOpenGLInit(void); \ No newline at end of file diff --git a/src/duskgl/display/framebuffer/framebuffergl.c b/src/duskgl/display/framebuffer/framebuffergl.c index f87599d..43f7006 100644 --- a/src/duskgl/display/framebuffer/framebuffergl.c +++ b/src/duskgl/display/framebuffer/framebuffergl.c @@ -7,7 +7,7 @@ #include "display/display.h" #include "display/framebuffer/framebuffer.h" -#include "assert/assert.h" +#include "assert/assertgl.h" #include "util/memory.h" errorret_t frameBufferGLInitBackBuffer(void) { @@ -86,6 +86,7 @@ void frameBufferGLClear(const uint8_t flags, const color_t color) { color.b / 255.0f, color.a / 255.0f ); + assertNoGLError("Failed to set clear color"); } if(flags & FRAMEBUFFER_CLEAR_DEPTH) { @@ -93,6 +94,7 @@ void frameBufferGLClear(const uint8_t flags, const color_t color) { } glClear(glFlags); + assertNoGLError("Failed to clear framebuffer"); } #ifdef DUSK_DISPLAY_SIZE_DYNAMIC diff --git a/src/duskgl/display/mesh/meshgl.c b/src/duskgl/display/mesh/meshgl.c index fd68df3..c931868 100644 --- a/src/duskgl/display/mesh/meshgl.c +++ b/src/duskgl/display/mesh/meshgl.c @@ -6,7 +6,9 @@ */ #include "display/mesh/mesh.h" -#include "assert/assert.h" +#include "assert/assertgl.h" +#include "error/errorgl.h" +#include "display/shader/shadergl.h" errorret_t meshInitGL( meshgl_t *mesh, @@ -22,6 +24,99 @@ errorret_t meshInitGL( mesh->vertexCount = vertexCount; mesh->vertices = vertices; + #ifdef DUSK_OPENGL_LEGACY + // Nothing needed. + glEnableClientState(GL_COLOR_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_VERTEX_ARRAY); + errorChain(errorGLCheck()); + #else + // Generate Vertex Buffer Object + glGenBuffers(1, &mesh->vboId); + errorChain(errorGLCheck()); + glBindBuffer(GL_ARRAY_BUFFER, mesh->vboId); + errorChain(errorGLCheck()); + glBufferData( + GL_ARRAY_BUFFER, + vertexCount * sizeof(meshvertex_t), + vertices, + GL_DYNAMIC_DRAW + ); + errorChain(errorGLCheck()); + + // Generate Vertex Array Object + glGenVertexArrays(1, &mesh->vaoId); + errorChain(errorGLCheck()); + glBindVertexArray(mesh->vaoId); + errorChain(errorGLCheck()); + glBindBuffer(GL_ARRAY_BUFFER, mesh->vboId); + errorChain(errorGLCheck()); + + // Set up vertex attribute pointers + glVertexAttribPointer( + 0, + MESH_VERTEX_POS_SIZE, + GL_FLOAT, + GL_FALSE, + sizeof(meshvertex_t), + (const GLvoid*)offsetof(meshvertex_t, pos) + ); + errorChain(errorGLCheck()); + glEnableVertexAttribArray(0); + errorChain(errorGLCheck()); + + glVertexAttribPointer( + 1, + MESH_VERTEX_UV_SIZE, + GL_FLOAT, + GL_FALSE, + sizeof(meshvertex_t), + (const GLvoid*)offsetof(meshvertex_t, uv) + ); + errorChain(errorGLCheck()); + glEnableVertexAttribArray(1); + errorChain(errorGLCheck()); + + glVertexAttribPointer( + 2, + sizeof(color_t) / sizeof(GLubyte), + GL_UNSIGNED_BYTE, + GL_TRUE, + sizeof(meshvertex_t), + (const GLvoid*)offsetof(meshvertex_t, color) + ); + errorChain(errorGLCheck()); + glEnableVertexAttribArray(2); + errorChain(errorGLCheck()); + + // Unbind VAO and VBO to prevent accidental modification + glBindBuffer(GL_ARRAY_BUFFER, 0); + errorChain(errorGLCheck()); + glBindVertexArray(0); + errorChain(errorGLCheck()); + #endif + + errorOk(); +} + +errorret_t meshFlushGL( + meshgl_t *mesh, + const int32_t vertOffset, + const int32_t vertCount +) { + #ifdef DUSK_OPENGL_LEGACY + // Nothing doing, we use the glClientState stuff. + #else + glBindBuffer(GL_ARRAY_BUFFER, mesh->vboId); + errorChain(errorGLCheck()); + glBufferData( + GL_ARRAY_BUFFER, + vertCount * sizeof(meshvertex_t), + &mesh->vertices[vertOffset], + GL_DYNAMIC_DRAW + ); + errorChain(errorGLCheck()); + #endif errorOk(); } @@ -30,29 +125,45 @@ errorret_t meshDrawGL( const int32_t offset, const int32_t count ) { - // PSP style pointer legacy OpenGL - const GLsizei stride = sizeof(meshvertex_t); + #ifdef DUSK_OPENGL_LEGACY + // Legacy pointer style rendering + const GLsizei stride = sizeof(meshvertex_t); - glColorPointer( - sizeof(color4b_t), - GL_UNSIGNED_BYTE, - stride, - (const GLvoid*)&mesh->vertices[offset].color - ); - glTexCoordPointer( - MESH_VERTEX_UV_SIZE, - GL_FLOAT, - stride, - (const GLvoid*)&mesh->vertices[offset].uv - ); - glVertexPointer( - MESH_VERTEX_POS_SIZE, - GL_FLOAT, - stride, - (const GLvoid*)&mesh->vertices[offset].pos - ); + glColorPointer( + sizeof(color4b_t), + GL_UNSIGNED_BYTE, + stride, + (const GLvoid*)&mesh->vertices[offset].color + ); + glTexCoordPointer( + MESH_VERTEX_UV_SIZE, + GL_FLOAT, + stride, + (const GLvoid*)&mesh->vertices[offset].uv + ); + glVertexPointer( + MESH_VERTEX_POS_SIZE, + GL_FLOAT, + stride, + (const GLvoid*)&mesh->vertices[offset].pos + ); - glDrawArrays(mesh->primitiveType, offset, count); + // Shader may have model matrix here + #ifdef DUSK_OPENGL_LEGACY + errorChain(shaderLegacyMatrixUpdate()); + #endif + + glDrawArrays(mesh->primitiveType, offset, count); + errorChain(errorGLCheck()); + #else + // Modern VAO/VBO rendering + glBindVertexArray(mesh->vaoId); + errorChain(errorGLCheck()); + glDrawArrays(mesh->primitiveType, offset, count); + errorChain(errorGLCheck()); + glBindVertexArray(0); + errorChain(errorGLCheck()); + #endif errorOk(); } @@ -62,6 +173,13 @@ int32_t meshGetVertexCountGL(const meshgl_t *mesh) { } errorret_t meshDisposeGL(meshgl_t *mesh) { - // No dynamic resources to free for this mesh implementation + #ifdef DUSK_OPENGL_LEGACY + // No dynamic resources to free for this mesh implementation + #else + glDeleteBuffers(1, &mesh->vboId); + errorChain(errorGLCheck()); + glDeleteVertexArrays(1, &mesh->vaoId); + errorChain(errorGLCheck()); + #endif errorOk(); } \ No newline at end of file diff --git a/src/duskgl/display/mesh/meshgl.h b/src/duskgl/display/mesh/meshgl.h index 8acc6c3..9b7533c 100644 --- a/src/duskgl/display/mesh/meshgl.h +++ b/src/duskgl/display/mesh/meshgl.h @@ -16,9 +16,16 @@ typedef enum { } meshprimitivetypegl_t; typedef struct { - const meshvertex_t *vertices; int32_t vertexCount; meshprimitivetypegl_t primitiveType; + const meshvertex_t *vertices; + + #ifdef DUSK_OPENGL_LEGACY + // Nothing needed + #else + GLuint vaoId; + GLuint vboId; + #endif } meshgl_t; /** @@ -37,6 +44,20 @@ errorret_t meshInitGL( const meshvertex_t *vertices ); +/** + * Flushes the vertices (stored in memory) to the GPU. + * + * @param mesh Mesh to flush vertices for. + * @param vertOffset First vertice index to flush. + * @param vertCount Count of vertices to flush. + * @return Error state. + */ +errorret_t meshFlushGL( + meshgl_t *mesh, + const int32_t vertOffset, + const int32_t vertCount +); + /** * Draws a mesh using OpenGL. * diff --git a/src/duskgl/display/mesh/meshplatform.h b/src/duskgl/display/mesh/meshplatform.h index 0419534..f05ab50 100644 --- a/src/duskgl/display/mesh/meshplatform.h +++ b/src/duskgl/display/mesh/meshplatform.h @@ -12,6 +12,7 @@ typedef meshprimitivetypegl_t meshprimitivetypeplatform_t; typedef meshgl_t meshplatform_t; #define meshInitPlatform meshInitGL +#define meshFlushPlatform meshFlushGL #define meshDrawPlatform meshDrawGL #define meshGetVertexCountPlatform meshGetVertexCountGL #define meshDisposePlatform meshDisposeGL \ No newline at end of file diff --git a/src/duskgl/display/shader/CMakeLists.txt b/src/duskgl/display/shader/CMakeLists.txt new file mode 100644 index 0000000..719b437 --- /dev/null +++ b/src/duskgl/display/shader/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + shadergl.c + shaderunlitgl.c +) \ No newline at end of file diff --git a/src/duskgl/display/shader/shadergl.c b/src/duskgl/display/shader/shadergl.c new file mode 100644 index 0000000..b753a43 --- /dev/null +++ b/src/duskgl/display/shader/shadergl.c @@ -0,0 +1,282 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "shadergl.h" +#include "util/memory.h" +#include "util/string.h" +#include "assert/assertgl.h" +#include "display/shader/shaderunlit.h" + +#ifdef DUSK_OPENGL_LEGACY + shaderlegacygl_t SHADER_LEGACY = { 0 }; +#endif + +errorret_t shaderInitGL(shadergl_t *shader, const shaderdefinitiongl_t *def) { + assertNotNull(shader, "Shader cannot be null"); + assertNotNull(def, "Shader definition cannot be null"); + memoryZero(shader, sizeof(shadergl_t)); + + #ifdef DUSK_OPENGL_LEGACY + glm_mat4_identity(shader->view); + glm_mat4_identity(shader->proj); + glm_mat4_identity(shader->model); + + SHADER_LEGACY.boundShader = NULL; + errorOk(); + #else + // Create vertex shader + shader->vertexShaderId = glCreateShader(GL_VERTEX_SHADER); + errorret_t err = errorGLCheck(); + errorChain(err); + + glShaderSource(shader->vertexShaderId, 1, &def->vert, NULL); + err = errorGLCheck(); + if(err.code != ERROR_OK) { + glDeleteShader(shader->vertexShaderId); + errorChain(err); + } + + glCompileShader(shader->vertexShaderId); + err = errorGLCheck(); + if(err.code != ERROR_OK) { + glDeleteShader(shader->vertexShaderId); + errorChain(err); + } + + GLint ok = 0; + glGetShaderiv(shader->vertexShaderId, GL_COMPILE_STATUS, &ok); + if(!ok) { + GLchar log[1024]; + glGetShaderInfoLog(shader->vertexShaderId, sizeof(log), NULL, log); + glDeleteShader(shader->vertexShaderId); + errorThrow("Vertex shader compilation failed: %s", log); + } + + // Create fragment shader + shader->fragmentShaderId = glCreateShader(GL_FRAGMENT_SHADER); + err = errorGLCheck(); + if(err.code != ERROR_OK) { + glDeleteShader(shader->vertexShaderId); + errorChain(err); + } + + glShaderSource(shader->fragmentShaderId, 1, &def->frag, NULL); + err = errorGLCheck(); + if(err.code != ERROR_OK) { + glDeleteShader(shader->vertexShaderId); + glDeleteShader(shader->fragmentShaderId); + errorChain(err); + } + + glCompileShader(shader->fragmentShaderId); + err = errorGLCheck(); + if(err.code != ERROR_OK) { + glDeleteShader(shader->vertexShaderId); + glDeleteShader(shader->fragmentShaderId); + errorChain(err); + } + + glGetShaderiv(shader->fragmentShaderId, GL_COMPILE_STATUS, &ok); + if(!ok) { + GLchar log[1024]; + glGetShaderInfoLog(shader->fragmentShaderId, sizeof(log), NULL, log); + glDeleteShader(shader->vertexShaderId); + glDeleteShader(shader->fragmentShaderId); + errorThrow("Fragment shader compilation failed: %s", log); + } + + // Create shader program + shader->shaderProgramId = glCreateProgram(); + err = errorGLCheck(); + if(err.code != ERROR_OK) { + glDeleteShader(shader->vertexShaderId); + glDeleteShader(shader->fragmentShaderId); + errorChain(err); + } + + glAttachShader(shader->shaderProgramId, shader->vertexShaderId); + err = errorGLCheck(); + if(err.code != ERROR_OK) { + glDeleteProgram(shader->shaderProgramId); + glDeleteShader(shader->vertexShaderId); + glDeleteShader(shader->fragmentShaderId); + errorChain(err); + } + + glAttachShader(shader->shaderProgramId, shader->fragmentShaderId); + err = errorGLCheck(); + if(err.code != ERROR_OK) { + glDeleteProgram(shader->shaderProgramId); + glDeleteShader(shader->vertexShaderId); + glDeleteShader(shader->fragmentShaderId); + errorChain(err); + } + + glLinkProgram(shader->shaderProgramId); + err = errorGLCheck(); + if(err.code != ERROR_OK) { + glDeleteProgram(shader->shaderProgramId); + glDeleteShader(shader->vertexShaderId); + glDeleteShader(shader->fragmentShaderId); + errorChain(err); + } + + ok = 0; + glGetProgramiv(shader->shaderProgramId, GL_LINK_STATUS, &ok); + if(!ok) { + GLchar log[1024]; + glGetProgramInfoLog(shader->shaderProgramId, sizeof(log), NULL, log); + glDeleteProgram(shader->shaderProgramId); + glDeleteShader(shader->vertexShaderId); + glDeleteShader(shader->fragmentShaderId); + errorThrow("Shader program linking failed: %s", log); + } + #endif + + errorOk(); +} + +errorret_t shaderParamGetLocationGL( + shadergl_t *shader, + const char_t *name, + GLint *location +) { + assertNotNull(shader, "Shader cannot be null"); + assertStrLenMin(name, 1, "Uniform name cannot be empty"); + assertNotNull(location, "Location cannot be null"); + + #ifdef DUSK_OPENGL_LEGACY + assertUnreachable("Cannot get uniform locations on legacy opengl."); + #else + shadergl_t *shaderGL = (shadergl_t *)shader; + *location = glGetUniformLocation(shaderGL->shaderProgramId, name); + errorret_t err = errorGLCheck(); + if(err.code != ERROR_OK) { + errorChain(err); + } + #endif + + errorOk(); +} + +errorret_t shaderSetMatrixGL( + shadergl_t *shader, + const char_t *name, + mat4 mat +) { + assertNotNull(shader, "Shader cannot be null"); + assertNotNull(mat, "Matrix data cannot be null"); + assertStrLenMin(name, 1, "Uniform name cannot be empty"); + + #ifdef DUSK_OPENGL_LEGACY + assertTrue( + SHADER_LEGACY.boundShader == shader, + "Shader must be bound to set legacy matrices." + ); + if(stringCompare(name, SHADER_UNLIT_PROJECTION) == 0) { + SHADER_LEGACY.dirty |= SHADER_LEGACY_DIRTY_PROJ; + glm_mat4_copy(mat, shader->proj); + + } else if(stringCompare(name, SHADER_UNLIT_VIEW) == 0) { + SHADER_LEGACY.dirty |= SHADER_LEGACY_DIRTY_VIEW; + glm_mat4_copy(mat, shader->view); + + } else if(stringCompare(name, SHADER_UNLIT_MODEL) == 0) { + SHADER_LEGACY.dirty |= SHADER_LEGACY_DIRTY_MODEL; + glm_mat4_copy(mat, shader->model); + + } else { + assertUnreachable("Cannot use a custom matrix on legacy opengl."); + } + #else + GLint location; + errorChain(shaderParamGetLocationGL(shader, name, &location)); + + glUniformMatrix4fv(location, 1, GL_FALSE, (const GLfloat *)mat); + errorChain(errorGLCheck()); + #endif + + errorOk(); +} + + +errorret_t shaderBindGL(shadergl_t *shader) { + #ifdef DUSK_OPENGL_LEGACY + assertNotNull(shader, "Cannot bind a null shader."); + SHADER_LEGACY.boundShader = shader; + SHADER_LEGACY.dirty = ( + SHADER_LEGACY_DIRTY_MODEL | + SHADER_LEGACY_DIRTY_PROJ | + SHADER_LEGACY_DIRTY_VIEW + ); + #else + assertNotNull(shader, "Shader cannot be null"); + glUseProgram(shader->shaderProgramId); + errorChain(errorGLCheck()); + #endif + + errorOk(); +} + +errorret_t shaderDisposeGL(shadergl_t *shader) { + assertNotNull(shader, "Shader cannot be null"); + + #ifdef DUSK_OPENGL_LEGACY + SHADER_LEGACY.boundShader = NULL; + #else + if(shader->shaderProgramId != 0) { + glDeleteProgram(shader->shaderProgramId); + } + + if(shader->vertexShaderId != 0) { + glDeleteShader(shader->vertexShaderId); + } + + if(shader->fragmentShaderId != 0) { + glDeleteShader(shader->fragmentShaderId); + } + + assertNoGLError("Failed disposing shader"); + #endif + + memoryZero(shader, sizeof(shadergl_t)); + errorOk(); +} + +#ifdef DUSK_OPENGL_LEGACY + errorret_t shaderLegacyMatrixUpdate() { + assertNotNull(SHADER_LEGACY.boundShader, "No shader is currently bound."); + + if((SHADER_LEGACY.dirty & SHADER_LEGACY_DIRTY_PROJ) != 0) { + glMatrixMode(GL_PROJECTION); + errorChain(errorGLCheck()); + glLoadIdentity(); + errorChain(errorGLCheck()); + glMultMatrixf((const GLfloat *)SHADER_LEGACY.boundShader->proj); + errorChain(errorGLCheck()); + } + + if((SHADER_LEGACY.dirty & SHADER_LEGACY_DIRTY_VIEW) != 0) { + glMatrixMode(GL_MODELVIEW); + errorChain(errorGLCheck()); + glLoadIdentity(); + errorChain(errorGLCheck()); + glMultMatrixf((const GLfloat *)SHADER_LEGACY.boundShader->view); + errorChain(errorGLCheck()); + } + + if((SHADER_LEGACY.dirty & SHADER_LEGACY_DIRTY_MODEL) != 0) { + glMatrixMode(GL_MODELVIEW); + errorChain(errorGLCheck()); + glMultMatrixf((const GLfloat *)SHADER_LEGACY.boundShader->model); + errorChain(errorGLCheck()); + } + + SHADER_LEGACY.dirty = 0; + errorOk(); + } +#endif \ No newline at end of file diff --git a/src/duskgl/display/shader/shadergl.h b/src/duskgl/display/shader/shadergl.h new file mode 100644 index 0000000..2ba660e --- /dev/null +++ b/src/duskgl/display/shader/shadergl.h @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/errorgl.h" + +typedef struct { + #ifdef DUSK_OPENGL_LEGACY + void *nothing; + #else + GLuint shaderProgramId; + GLuint vertexShaderId; + GLuint fragmentShaderId; + #endif + + #if DUSK_OPENGL_LEGACY + mat4 view; + mat4 proj; + mat4 model; + #endif +} shadergl_t; + +typedef struct { + #ifdef DUSK_OPENGL_LEGACY + void *nothing; + #else + const char_t *vert; + const char_t *frag; + #endif +} shaderdefinitiongl_t; + +#if DUSK_OPENGL_LEGACY + typedef struct { + shadergl_t *boundShader; + uint_fast8_t dirty; + } shaderlegacygl_t; + + extern shaderlegacygl_t SHADER_LEGACY; + + #define SHADER_LEGACY_DIRTY_PROJ (1 << 0) + #define SHADER_LEGACY_DIRTY_VIEW (1 << 1) + #define SHADER_LEGACY_DIRTY_MODEL (1 << 2) +#endif + +/** + * Initializes a shader. + * + * @param shader The shader to initialize. + * @param def The definition of the shader to initialize with. + * @return An errorret_t indicating success or failure. + */ +errorret_t shaderInitGL(shadergl_t *shader, const shaderdefinitiongl_t *def); + +/** + * Binds a shader for use in rendering. + * + * @param shader The shader to bind. + * @return An errorret_t indicating success or failure. + */ +errorret_t shaderBindGL(shadergl_t *shader); + +/** + * Retrieves the location of a shader uniform parameter. + * + * @param shader The shader to query. + * @param name The name of the uniform parameter. + * @param location Output parameter to receive the location of the uniform. + * @return An errorret_t indicating success or failure. + */ +errorret_t shaderParamGetLocationGL( + shadergl_t *shader, + const char_t *name, + GLint *location +); + +/** + * Sets a mat4 uniform parameter in the shader. + * + * @param shader The shader to update. + * @param name The name of the uniform parameter. + * @param mat The 4x4 matrix data to set. + * @return An errorret_t indicating success or failure. + */ +errorret_t shaderSetMatrixGL( + shadergl_t *shader, + const char_t *name, + mat4 matrix +); + +/** + * Disposes of a shader, freeing any associated resources. + * + * @param shader The shader to dispose. + */ +errorret_t shaderDisposeGL(shadergl_t *shader); + +#ifdef DUSK_OPENGL_LEGACY + /** + * During mesh rendering, this is requesting the legacy system to push all + * shaders necessary to render the currently bound shader's matrices. + * + * @return Any error state. + */ + errorret_t shaderLegacyMatrixUpdate(); +#endif \ No newline at end of file diff --git a/src/duskgl/display/shader/shaderplatform.h b/src/duskgl/display/shader/shaderplatform.h new file mode 100644 index 0000000..9d38587 --- /dev/null +++ b/src/duskgl/display/shader/shaderplatform.h @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "shadergl.h" + +typedef shadergl_t shaderplatform_t; +typedef shaderdefinitiongl_t shaderdefinitionplatform_t; + +#define shaderInitPlatform shaderInitGL +#define shaderBindPlatform shaderBindGL +#define shaderSetMatrixPlatform shaderSetMatrixGL +// #define shaderSetTexturePlatform shaderSetTextureGL +#define shaderDisposePlatform shaderDisposeGL \ No newline at end of file diff --git a/src/duskgl/display/shader/shaderunlitgl.c b/src/duskgl/display/shader/shaderunlitgl.c new file mode 100644 index 0000000..179dc4e --- /dev/null +++ b/src/duskgl/display/shader/shaderunlitgl.c @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "display/shader/shaderunlit.h" + +#ifdef DUSK_OPENGL_LEGACY + shaderdefinition_t SHADER_UNLIT_DEFINITION = { 0 }; +#else + shaderdefinition_t SHADER_UNLIT_DEFINITION = { + .vert = + "#version 330 core\n" + "layout(location = 0) in vec3 aPos;\n" + "uniform mat4 u_Proj;\n" + "uniform mat4 u_View;\n" + "uniform mat4 u_Model;\n" + "void main() {\n" + " gl_Position = u_Proj * u_View * u_Model * vec4(aPos, 1.0);\n" + "}\n", + + .frag = + "#version 330 core\n" + "out vec4 FragColor;\n" + "void main() {\n" + " FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n" + "}\n" + }; +#endif \ No newline at end of file diff --git a/src/duskpsp/log/log.c b/src/duskpsp/log/log.c index ba8eed9..040f0be 100644 --- a/src/duskpsp/log/log.c +++ b/src/duskpsp/log/log.c @@ -19,7 +19,7 @@ void logDebug(const char_t *message, ...) { // print to file FILE *file = fopen("ms0:/PSP/GAME/Dusk/debug.log", "a"); - if (file) { + if(file) { va_copy(copy, args); vfprintf(file, message, copy); va_end(copy); @@ -41,7 +41,7 @@ void logError(const char_t *message, ...) { // print to file FILE *file = fopen("ms0:/PSP/GAME/Dusk/error.log", "a"); - if (file) { + if(file) { va_copy(copy, args); vfprintf(file, message, copy); va_end(copy); diff --git a/src/dusksdl2/display/displaysdl2.c b/src/dusksdl2/display/displaysdl2.c index 0b03325..c34d8f8 100644 --- a/src/dusksdl2/display/displaysdl2.c +++ b/src/dusksdl2/display/displaysdl2.c @@ -21,6 +21,16 @@ errorret_t displaySDL2Init(void) { // Set OpenGL attributes (Needs to be done now or later?) SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + // SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + // SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + + #ifdef DUSK_OPENGL_LEGACY + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); + #else + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + #endif // Create window with OpenGL flag. DISPLAY.window = SDL_CreateWindow( @@ -44,7 +54,6 @@ errorret_t displaySDL2Init(void) { errorChain(errorGLCheck()); SDL_GL_SetSwapInterval(1); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); errorChain(errorGLCheck()); errorChain(displayOpenGLInit()); @@ -82,6 +91,9 @@ errorret_t displaySDL2Update(void) { } SDL_GL_MakeCurrent(DISPLAY.window, DISPLAY.glContext); + + // errorChain(shaderPaletteTextureBindGL(&testShader)); + errorOk(); } diff --git a/tools/tileset-creator.html b/tools/tileset-creator.html index 3dc5ae3..1b74b08 100644 --- a/tools/tileset-creator.html +++ b/tools/tileset-creator.html @@ -247,13 +247,13 @@ // Draw red grid lines for tile boundaries ctx.strokeStyle = 'rgba(255,0,0,1)'; - for (let x = v.scaledTileWidth; x < elOutputPreview.width; x += v.scaledTileWidth) { + for(let x = v.scaledTileWidth; x < elOutputPreview.width; x += v.scaledTileWidth) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, elOutputPreview.height); ctx.stroke(); } - for (let y = v.scaledTileHeight; y < elOutputPreview.height; y += v.scaledTileHeight) { + for(let y = v.scaledTileHeight; y < elOutputPreview.height; y += v.scaledTileHeight) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(elOutputPreview.width, y); @@ -289,7 +289,7 @@ btnBackgroundGreen.addEventListener('click', () => document.body.style.background = 'green'); elDefineBySize.addEventListener('change', () => { - if (elDefineBySize.checked) { + if(elDefineBySize.checked) { elTileSizes.style.display = ''; elTileCounts.style.display = 'none'; } @@ -297,7 +297,7 @@ }); elDefineByCount.addEventListener('change', () => { - if (elDefineByCount.checked) { + if(elDefineByCount.checked) { elTileSizes.style.display = 'none'; elTileCounts.style.display = ''; } @@ -332,7 +332,7 @@ elOutputError.style.display = 'none'; pixels = null; - if (!elFileInput.files.length) { + if(!elFileInput.files.length) { elOutputError.textContent = 'No file selected'; elOutputError.style.display = 'block'; return; @@ -340,18 +340,18 @@ const file = elFileInput.files[0]; - if (file.name.endsWith('.dpt')) { + if(file.name.endsWith('.dpt')) { // Load DPT file const reader = new FileReader(); reader.onload = () => { const arrayBuffer = reader.result; const data = new Uint8Array(arrayBuffer); - if (data[0] !== 'D'.charCodeAt(0) || data[1] !== 'P'.charCodeAt(0) || data[2] !== 'T'.charCodeAt(0)) { + if(data[0] !== 'D'.charCodeAt(0) || data[1] !== 'P'.charCodeAt(0) || data[2] !== 'T'.charCodeAt(0)) { elOutputError.textContent = 'Invalid DPT file'; elOutputError.style.display = 'block'; return; - } else if (data[3] !== 0x01) { + } else if(data[3] !== 0x01) { elOutputError.textContent = 'Unsupported DPT version'; elOutputError.style.display = 'block'; return; @@ -381,15 +381,15 @@ } const uniqueIndexes = []; - for (let i = 0; i < width * height; i++) { + for(let i = 0; i < width * height; i++) { const colorIndex = data[12 + i]; - if (!uniqueIndexes.includes(colorIndex)) { + if(!uniqueIndexes.includes(colorIndex)) { uniqueIndexes.push(colorIndex); } } const adhocPalette = []; - for (let i = 0; i < uniqueIndexes.length; i++) { + for(let i = 0; i < uniqueIndexes.length; i++) { const index = uniqueIndexes[i]; // Get the most different possible color for this index const color = [ @@ -402,7 +402,7 @@ } pixels = new Uint8Array(width * height * 4); - for (let i = 0; i < width * height; i++) { + for(let i = 0; i < width * height; i++) { const colorIndex = data[12 + i]; const color = adhocPalette[colorIndex]; pixels[i * 4] = color[0]; @@ -487,13 +487,13 @@ input.accept = '.dtf'; input.addEventListener('change', (e) => { const files = e?.target?.files; - if (!files || !files.length || !files[0]) { + if(!files || !files.length || !files[0]) { alert('No file selected'); return; } const file = files[0]; - if (!file.name.endsWith('.dtf')) { + if(!file.name.endsWith('.dtf')) { alert('Invalid file type. Please select a .dtf file.'); return; } @@ -502,12 +502,12 @@ reader.onload = () => { const arrayBuffer = reader.result; const data = new Uint8Array(arrayBuffer); - if (data[0] !== 'D'.charCodeAt(0) || data[1] !== 'T'.charCodeAt(0) || data[2] !== 'F'.charCodeAt(0)) { + if(data[0] !== 'D'.charCodeAt(0) || data[1] !== 'T'.charCodeAt(0) || data[2] !== 'F'.charCodeAt(0)) { alert('Invalid DTF file'); return; } - if (data[3] !== 0x00) { + if(data[3] !== 0x00) { alert('Unsupported DTF version'); return; }