diff --git a/src/display/CMakeLists.txt b/src/display/CMakeLists.txt index 92621df..1643b6e 100644 --- a/src/display/CMakeLists.txt +++ b/src/display/CMakeLists.txt @@ -12,7 +12,9 @@ target_sources(${DUSK_TARGET_NAME} ) # Subdirectories +add_subdirectory(framebuffer) add_subdirectory(mesh) +add_subdirectory(texture) if(DUSK_TARGET_SYSTEM STREQUAL "linux") target_compile_definitions(${DUSK_TARGET_NAME} diff --git a/src/display/camera.c b/src/display/camera.c index 6abf772..4cdc9ed 100644 --- a/src/display/camera.c +++ b/src/display/camera.c @@ -9,6 +9,7 @@ #include "display/display.h" #include "assert/assert.h" #include "scene/node.h" +#include "display/framebuffer/framebuffer.h" camera_t CAMERA_DATA[ECS_ENTITY_COUNT_MAX] = { 0 }; ecscomponent_t CAMERA_COMPONENT = ecsComponentInit( @@ -56,7 +57,10 @@ void cameraPush(const ecsid_t id) { case CAMERA_TYPE_PERSPECTIVE: glm_perspective( cam->perspective.fov, - 4.0f / 3.0f, + ( + (float_t)frameBufferGetWidth(FRAMEBUFFER_BOUND) / + (float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND) + ), cam->nearClip, cam->farClip, projection diff --git a/src/display/display.c b/src/display/display.c index fe34194..b15359d 100644 --- a/src/display/display.c +++ b/src/display/display.c @@ -9,6 +9,7 @@ #include "console/console.h" #include "display/renderer.h" #include "ecs/ecssystem.h" +#include "display/framebuffer/framebuffer.h" #include "display/mesh/quad.h" @@ -58,6 +59,7 @@ errorret_t displayInit(void) { #endif quadInit(); + frameBufferInitBackbuffer(); errorOk(); } diff --git a/src/display/framebuffer/CMakeLists.txt b/src/display/framebuffer/CMakeLists.txt new file mode 100644 index 0000000..9f0656b --- /dev/null +++ b/src/display/framebuffer/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_TARGET_NAME} + PRIVATE + framebuffer.c +) \ No newline at end of file diff --git a/src/display/framebuffer/framebuffer.c b/src/display/framebuffer/framebuffer.c new file mode 100644 index 0000000..2b51cd6 --- /dev/null +++ b/src/display/framebuffer/framebuffer.c @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "framebuffer.h" +#include "assert/assert.h" +#include "util/memory.h" + +framebuffer_t FRAMEBUFFER_BACKBUFFER = {0}; +const framebuffer_t *FRAMEBUFFER_BOUND = &FRAMEBUFFER_BACKBUFFER; + +void frameBufferInitBackbuffer() { + memoryZero(&FRAMEBUFFER_BACKBUFFER, sizeof(framebuffer_t)); + + FRAMEBUFFER_BACKBUFFER.id = -1; + FRAMEBUFFER_BOUND = &FRAMEBUFFER_BACKBUFFER; +} + +int32_t frameBufferGetWidth(const framebuffer_t *framebuffer) { + #if DUSK_DISPLAY_SDL2 + if(framebuffer == &FRAMEBUFFER_BACKBUFFER) { + int32_t windowWidth, windowHeight; + SDL_GetWindowSize(DISPLAY.window, &windowWidth, &windowHeight); + return windowWidth; + } + + assertUnreachable("Framebuffer width not implemented"); + return 0; + #endif +} + +int32_t frameBufferGetHeight(const framebuffer_t *framebuffer) { + #if DUSK_DISPLAY_SDL2 + if(framebuffer == &FRAMEBUFFER_BACKBUFFER) { + int32_t windowWidth, windowHeight; + SDL_GetWindowSize(DISPLAY.window, &windowWidth, &windowHeight); + return windowHeight; + } + + assertUnreachable("Framebuffer height not implemented"); + return 0; + #endif +} + +void frameBufferBind(const framebuffer_t *framebuffer) { + if(framebuffer == NULL) { + #if DUSK_DISPLAY_SDL2 + frameBufferBind(&FRAMEBUFFER_BACKBUFFER); + #endif + + FRAMEBUFFER_BOUND = &FRAMEBUFFER_BACKBUFFER; + return; + } + + // Bind the framebuffer for rendering + #if DUSK_DISPLAY_SDL2 + if(framebuffer == &FRAMEBUFFER_BACKBUFFER) { + + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + int32_t windowWidth, windowHeight; + SDL_GetWindowSize(DISPLAY.window, &windowWidth, &windowHeight); + } else { + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->id); + } + glViewport( + 0, 0, + frameBufferGetWidth(framebuffer), frameBufferGetHeight(framebuffer) + ); + #endif + + FRAMEBUFFER_BOUND = framebuffer; +} + +/** + * Disposes of the framebuffer using EXT methods. + * + * @param framebuffer The framebuffer to dispose of. + */ +void frameBufferDispose(framebuffer_t *framebuffer) { + assertNotNull(framebuffer, "Framebuffer cannot be NULL"); + + #if DUSK_DISPLAY_SDL2 + if(framebuffer == &FRAMEBUFFER_BACKBUFFER) { + assertUnreachable("Cannot dispose of backbuffer"); + } + + glDeleteFramebuffersEXT(1, &framebuffer->id); + #endif +} + +// #if RENDER_USE_FRAMEBUFFER +// void frameBufferInit( +// framebuffer_t *framebuffer, +// const uint32_t width, +// const uint32_t height +// ) { +// assertNotNull(framebuffer, "Framebuffer cannot be NULL"); +// assertTrue(width > 0 && height > 0, "Width & height must be greater than 0"); + +// memoryZero(framebuffer, sizeof(framebuffer_t)); +// textureInit(&framebuffer->texture, width, height, GL_RGBA, NULL); + +// // Generate the framebuffer object using EXT +// glGenFramebuffersEXT(1, &framebuffer->id); +// glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->id); + +// // Attach the texture to the framebuffer +// glFramebufferTexture2DEXT( +// GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, +// GL_TEXTURE_2D, framebuffer->texture.id, 0 +// ); + +// // Check if the framebuffer is complete +// if(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) { +// assertUnreachable("Framebuffer is not complete"); +// } + +// // Unbind the framebuffer +// glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); +// } + +// void frameBufferBind(const framebuffer_t *framebuffer) { +// if(framebuffer == NULL) { +// glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); +// return; +// } + +// // Bind the framebuffer for rendering +// glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->id); +// } + +// void frameBufferDispose(framebuffer_t *framebuffer) { +// assertNotNull(framebuffer, "Framebuffer cannot be NULL"); + +// glDeleteFramebuffersEXT(1, &framebuffer->id); +// textureDispose(&framebuffer->texture); +// } +// #endif \ No newline at end of file diff --git a/src/display/framebuffer/framebuffer.h b/src/display/framebuffer/framebuffer.h new file mode 100644 index 0000000..f34bd70 --- /dev/null +++ b/src/display/framebuffer/framebuffer.h @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "display/display.h" +#include "display/texture/texture.h" + +typedef struct { + #if DUSK_DISPLAY_SDL2 + // OpenGL Framebuffer Object ID + GLuint id; + #endif +} framebuffer_t; + +extern framebuffer_t FRAMEBUFFER_BACKBUFFER; +extern const framebuffer_t *FRAMEBUFFER_BOUND; + +void frameBufferInitBackbuffer(); + +/** + * Gets the width of the framebuffer. + * + * @param framebuffer The framebuffer to get the width of. + * @return The width of the framebuffer, or 0 if the framebuffer is NULL. + */ +int32_t frameBufferGetWidth(const framebuffer_t *framebuffer); + +/** + * Gets the height of the framebuffer. + * + * @param framebuffer The framebuffer to get the height of. + * @return The height of the framebuffer, or 0 if the framebuffer is NULL. + */ +int32_t frameBufferGetHeight(const framebuffer_t *framebuffer); + +/** + * Binds the framebuffer for rendering, or the backbuffer if the framebuffer + * provided is NULL. + * + * @param framebuffer The framebuffer to bind, or NULL to bind the backbuffer. + */ +void frameBufferBind(const framebuffer_t *framebuffer); + +/** + * Disposes of the framebuffer using EXT methods. + * + * @param framebuffer The framebuffer to dispose of. + */ +void frameBufferDispose(framebuffer_t *framebuffer); + +// #if RENDER_USE_FRAMEBUFFER +// typedef struct { +// GLuint id; +// texture_t texture; +// } framebuffer_t; + +// /** +// * Initializes a framebuffer using EXT methods. +// * +// * @param framebuffer The framebuffer to initialize. +// * @param width The width of the framebuffer. +// * @param height The height of the framebuffer. +// * @return An error code indicating success or failure. +// */ +// void frameBufferInit( +// framebuffer_t *framebuffer, +// const uint32_t width, +// const uint32_t height +// ); + +// #endif \ No newline at end of file diff --git a/src/display/mesh/meshrenderer.c b/src/display/mesh/meshrenderer.c index 6b672cd..dc11960 100644 --- a/src/display/mesh/meshrenderer.c +++ b/src/display/mesh/meshrenderer.c @@ -22,5 +22,7 @@ void meshRendererDraw(const ecsid_t id) { if(!meshRendererHas(id)) return; meshrenderer_t *renderer = &MESH_RENDERER_DATA[id]; if(!renderer->mesh) return; + + textureBind(renderer->texture); meshDraw(renderer->mesh, 0, -1); } \ No newline at end of file diff --git a/src/display/mesh/meshrenderer.h b/src/display/mesh/meshrenderer.h index e2cea80..0f35d1e 100644 --- a/src/display/mesh/meshrenderer.h +++ b/src/display/mesh/meshrenderer.h @@ -8,9 +8,11 @@ #pragma once #include "ecs/ecscomponent.h" #include "display/mesh/mesh.h" +#include "display/texture/texture.h" typedef struct { mesh_t *mesh; + texture_t *texture; } meshrenderer_t; extern meshrenderer_t MESH_RENDERER_DATA[ECS_ENTITY_COUNT_MAX]; diff --git a/src/display/renderer.c b/src/display/renderer.c index 7588a87..4db9d6f 100644 --- a/src/display/renderer.c +++ b/src/display/renderer.c @@ -8,6 +8,7 @@ #include "renderer.h" #include "display/mesh/meshrenderer.h" #include "scene/node.h" +#include "display/framebuffer/framebuffer.h" void rendererRender(const ecsid_t camera) { if(camera == -1) return; @@ -18,6 +19,7 @@ void rendererRender(const ecsid_t camera) { ecsid_t id; meshCount = meshRendererGetAll(meshes); + frameBufferBind(NULL); cameraPush(camera); for(uint32_t i = 0; i < meshCount; i++) { id = meshes[i]; diff --git a/src/display/texture/CMakeLists.txt b/src/display/texture/CMakeLists.txt new file mode 100644 index 0000000..369aaac --- /dev/null +++ b/src/display/texture/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_TARGET_NAME} + PRIVATE + texture.c +) + +# Subdirs +# add_subdirectory(draw) \ No newline at end of file diff --git a/src/display/texture/texture.c b/src/display/texture/texture.c new file mode 100644 index 0000000..0f028c6 --- /dev/null +++ b/src/display/texture/texture.c @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "texture.h" +#include "assert/assert.h" +#include "util/memory.h" +#include "util/math.h" + +const texture_t *TEXTURE_BOUND = NULL; + +void textureInit( + texture_t *texture, + const int32_t width, + const int32_t height, + const textureformat_t format, + const void *data +) { + assertNotNull(texture, "Texture cannot be NULL"); + assertTrue(width > 0 && height > 0, "width/height must be greater than 0"); + + memoryZero(texture, sizeof(texture_t)); + texture->width = width; + texture->height = height; + + #if PSP + assertTrue( + width == mathNextPowTwo(width), + "Width must be powers of 2 for PSP" + ); + assertTrue( + height == mathNextPowTwo(height), + "Height must be powers of 2 for PSP" + ); + #endif + + #if DUSK_DISPLAY_SDL2 + glGenTextures(1, &texture->id); + glBindTexture(GL_TEXTURE_2D, texture->id); + glTexImage2D( + GL_TEXTURE_2D, 0, format, width, height, 0, + format, GL_UNSIGNED_BYTE, data + ); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindTexture(GL_TEXTURE_2D, 0); + #endif +} + +void textureBind(const texture_t *texture) { + if(TEXTURE_BOUND == texture) return; + + if(texture == NULL) { + #if DUSK_DISPLAY_SDL2 + glDisable(GL_TEXTURE_2D); + #endif + TEXTURE_BOUND = NULL; + return; + } + + assertTrue( + texture->id != 0, + "Texture ID must not be 0" + ); + assertTrue( + texture->width > 0 && texture->height > 0, + "Texture width and height must be greater than 0" + ); + + #if DUSK_DISPLAY_SDL2 + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, texture->id); + #endif + TEXTURE_BOUND = texture; +} + +void textureDispose(texture_t *texture) { + assertNotNull(texture, "Texture cannot be NULL"); + assertTrue(texture->id != 0, "Texture ID must not be 0"); + + #if DUSK_DISPLAY_SDL2 + glDeleteTextures(1, &texture->id); + #endif +} \ No newline at end of file diff --git a/src/display/texture/texture.h b/src/display/texture/texture.h new file mode 100644 index 0000000..b92078a --- /dev/null +++ b/src/display/texture/texture.h @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "display/display.h" +#include "display/color.h" + +typedef enum { + #if DUSK_DISPLAY_SDL2 + TEXTURE_FORMAT_RGBA = GL_RGBA, + TEXTURE_FORMAT_ALPHA = GL_ALPHA, + #endif +} textureformat_t; + +typedef struct { + #if DUSK_DISPLAY_SDL2 + GLuint id; + #endif + + int32_t width; + int32_t height; +} texture_t; + +extern const texture_t *TEXTURE_BOUND; + +/** + * Initializes a texture. + * + * @param texture The texture to initialize. + * @param width The width of the texture. + * @param height The height of the texture. + * @param format The format of the texture (e.g., GL_RGBA, GL_ALPHA). + * @param data The pixel data for the texture. + */ +void textureInit( + texture_t *texture, + const int32_t width, + const int32_t height, + const textureformat_t format, + const void *data +); + +/** + * Binds a texture for rendering. Providing NULL will unbind any texture. + * + * @param texture The texture to bind. + */ +void textureBind(const texture_t *texture); + +/** + * Disposes a texture. + * + * @param texture The texture to dispose. + */ +void textureDispose(texture_t *texture); \ No newline at end of file diff --git a/src/scene/test/scenetest.c b/src/scene/test/scenetest.c index 2f6c47a..7ff0eee 100644 --- a/src/scene/test/scenetest.c +++ b/src/scene/test/scenetest.c @@ -8,10 +8,11 @@ #include "scenetest.h" #include "scene/node.h" #include "display/camera.h" -#include "display/display.h" #include "display/mesh/meshrenderer.h" #include "display/mesh/quad.h" +texture_t test; + void sceneTestAdd(void) { // Initialize the entity with a camera component ecsid_t camera = ecsEntityAdd(); @@ -27,9 +28,16 @@ void sceneTestAdd(void) { ); nodeMatrixSet(camera, lookAt); + color4b_t pixels[4] = { + COLOR_RED, COLOR_GREEN, + COLOR_BLUE, COLOR_WHITE + }; + textureInit(&test, 2, 2, TEXTURE_FORMAT_RGBA, pixels); + // Test cube ecsid_t cube = ecsEntityAdd(); node = nodeAdd(cube); meshrenderer_t *renderer = meshRendererAdd(cube); renderer->mesh = &QUAD_MESH_SIMPLE; + renderer->texture = &test; } \ No newline at end of file