diff --git a/src/display/CMakeLists.txt b/src/display/CMakeLists.txt index f01b3b2..eae96ab 100644 --- a/src/display/CMakeLists.txt +++ b/src/display/CMakeLists.txt @@ -9,6 +9,7 @@ target_sources(${DUSK_TARGET_NAME} display.c camera.c tileset.c + screen.c ) # Subdirectories diff --git a/src/display/display.c b/src/display/display.c index b9984ad..ceea8b3 100644 --- a/src/display/display.c +++ b/src/display/display.c @@ -12,6 +12,7 @@ #include "display/spritebatch/spritebatch.h" #include "display/ui/ui.h" #include "display/mesh/quad.h" +#include "display/screen.h" display_t DISPLAY; @@ -68,6 +69,7 @@ errorret_t displayInit(void) { spriteBatchInit(); errorChain(uiInit()); errorChain(sceneManagerInit()); + screenInit(); errorOk(); } @@ -85,24 +87,10 @@ errorret_t displayUpdate(void) { break; } } - - // TODO: move to framebuffer component - int32_t windowWidth, windowHeight; - #if PSP - windowWidth = DISPLAY_WINDOW_WIDTH_DEFAULT; - windowHeight = DISPLAY_WINDOW_HEIGHT_DEFAULT; - #else - SDL_GetWindowSize(DISPLAY.window, &windowWidth, &windowHeight); - #endif - glViewport(0, 0, windowWidth, windowHeight); #endif spriteBatchClear(); - frameBufferBind(&FRAMEBUFFER_BACKBUFFER); - frameBufferClear( - FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH, - COLOR_CORNFLOWER_BLUE - ); + screenBind(); sceneManagerUpdate(); uiUpdate(); @@ -110,6 +98,8 @@ errorret_t displayUpdate(void) { sceneManagerRender(); uiRender(); + screenUnbindAndRender(); + #if DISPLAY_SDL2 SDL_GL_SwapWindow(DISPLAY.window); #endif @@ -124,6 +114,7 @@ errorret_t displayUpdate(void) { } errorret_t displayDispose(void) { + screenDispose(); sceneManagerDispose(); uiDispose(); spriteBatchDispose(); diff --git a/src/display/display.h b/src/display/display.h index af22a76..63e007a 100644 --- a/src/display/display.h +++ b/src/display/display.h @@ -6,37 +6,10 @@ */ #pragma once +#include "displaydefs.h" #include "error/error.h" - -#if DISPLAY_SDL2 - #include - - #define GL_GLEXT_PROTOTYPES - #include - #include - - #ifndef DISPLAY_SIZE_DYNAMIC - #define DISPLAY_SIZE_DYNAMIC 1 - #endif -#else - #error "Need to specify display backend." -#endif - -#ifndef DISPLAY_WIDTH - #define DISPLAY_WIDTH 320 -#endif -#ifndef DISPLAY_HEIGHT - #define DISPLAY_HEIGHT 240 -#endif - -#ifndef DISPLAY_WINDOW_WIDTH_DEFAULT - #define DISPLAY_WINDOW_WIDTH_DEFAULT DISPLAY_WIDTH -#endif -#ifndef DISPLAY_WINDOW_HEIGHT_DEFAULT - #define DISPLAY_WINDOW_HEIGHT_DEFAULT DISPLAY_HEIGHT -#endif - - +#include "display/camera.h" +#include "display/framebuffer/framebuffer.h" typedef struct { #if DISPLAY_SDL2 diff --git a/src/display/displaydefs.h b/src/display/displaydefs.h new file mode 100644 index 0000000..d25e14e --- /dev/null +++ b/src/display/displaydefs.h @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once + +#if DISPLAY_SDL2 + #include + + #define GL_GLEXT_PROTOTYPES + #include + #include + + #ifndef DISPLAY_SIZE_DYNAMIC + #define DISPLAY_SIZE_DYNAMIC 1 + #endif +#else + #error "Need to specify display backend." +#endif + +#ifndef DISPLAY_WIDTH + #define DISPLAY_WIDTH 320 +#endif +#ifndef DISPLAY_HEIGHT + #define DISPLAY_HEIGHT 240 +#endif + +#ifndef DISPLAY_WINDOW_WIDTH_DEFAULT + #define DISPLAY_WINDOW_WIDTH_DEFAULT DISPLAY_WIDTH +#endif +#ifndef DISPLAY_WINDOW_HEIGHT_DEFAULT + #define DISPLAY_WINDOW_HEIGHT_DEFAULT DISPLAY_HEIGHT +#endif \ No newline at end of file diff --git a/src/display/framebuffer/framebuffer.c b/src/display/framebuffer/framebuffer.c index 2e7fa2d..c430e00 100644 --- a/src/display/framebuffer/framebuffer.c +++ b/src/display/framebuffer/framebuffer.c @@ -6,6 +6,7 @@ */ #include "framebuffer.h" +#include "display/display.h" #include "assert/assert.h" #include "util/memory.h" @@ -19,6 +20,41 @@ void frameBufferInitBackbuffer() { FRAMEBUFFER_BOUND = &FRAMEBUFFER_BACKBUFFER; } +#if DISPLAY_SIZE_DYNAMIC == 1 + void frameBufferInit( + framebuffer_t *framebuffer, + const uint32_t width, + const uint32_t height + ) { + #if DISPLAY_SDL2 == 1 + 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, (texturedata_t){ + .rgba = { .colors = NULL } + }); + + glGenFramebuffersEXT(1, &framebuffer->id); + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->id); + + glFramebufferTexture2DEXT( + GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_TEXTURE_2D, framebuffer->texture.id, 0 + ); + + if( + glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != + GL_FRAMEBUFFER_COMPLETE_EXT + ) { + assertUnreachable("Framebuffer is not complete"); + } + + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + #endif + } +#endif + int32_t frameBufferGetWidth(const framebuffer_t *framebuffer) { #if DISPLAY_SDL2 if(framebuffer == &FRAMEBUFFER_BACKBUFFER) { @@ -31,8 +67,7 @@ int32_t frameBufferGetWidth(const framebuffer_t *framebuffer) { #endif } - assertUnreachable("Framebuffer width not implemented"); - return 0; + return framebuffer->texture.width; #endif } @@ -48,8 +83,7 @@ int32_t frameBufferGetHeight(const framebuffer_t *framebuffer) { #endif } - assertUnreachable("Framebuffer height not implemented"); - return 0; + return framebuffer->texture.height; #endif } @@ -78,6 +112,7 @@ void frameBufferBind(const framebuffer_t *framebuffer) { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->id); #endif } + glViewport( 0, 0, frameBufferGetWidth(framebuffer), frameBufferGetHeight(framebuffer) @@ -109,11 +144,6 @@ void frameBufferClear(uint8_t flags, color_t color) { #endif } -/** - * 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"); @@ -125,56 +155,8 @@ void frameBufferDispose(framebuffer_t *framebuffer) { #if DISPLAY_SIZE_DYNAMIC == 0 assertUnreachable("Dynamic size framebuffers not supported"); #else + textureDispose(&framebuffer->texture); glDeleteFramebuffersEXT(1, &framebuffer->id); #endif #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 +} \ No newline at end of file diff --git a/src/display/framebuffer/framebuffer.h b/src/display/framebuffer/framebuffer.h index 2bce751..dd8b4c4 100644 --- a/src/display/framebuffer/framebuffer.h +++ b/src/display/framebuffer/framebuffer.h @@ -6,23 +6,43 @@ */ #pragma once -#include "display/display.h" #include "display/texture/texture.h" #define FRAMEBUFFER_CLEAR_COLOR (1 << 0) #define FRAMEBUFFER_CLEAR_DEPTH (1 << 1) typedef struct { - #if DISPLAY_SDL2 + #if DISPLAY_SDL2 == 1 // OpenGL Framebuffer Object ID GLuint id; + texture_t texture; + #else + #error "Framebuffers not implemented on this platform." #endif } framebuffer_t; extern framebuffer_t FRAMEBUFFER_BACKBUFFER; extern const framebuffer_t *FRAMEBUFFER_BOUND; -void frameBufferInitBackbuffer(); +/** + * Initializes the backbuffer framebuffer. + */ +void frameBufferInitBackbuffer(void); + +/** + * Initializes a framebuffer. + * + * @param framebuffer The framebuffer to initialize. + * @param width The width of the framebuffer. + * @param height The height of the framebuffer. + */ +#if DISPLAY_SIZE_DYNAMIC == 1 + void frameBufferInit( + framebuffer_t *framebuffer, + const uint32_t width, + const uint32_t height + ); +#endif /** * Gets the width of the framebuffer. diff --git a/src/display/screen.c b/src/display/screen.c new file mode 100644 index 0000000..f85b8fd --- /dev/null +++ b/src/display/screen.c @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "screen.h" +#include "assert/assert.h" +#include "display/spritebatch/spritebatch.h" + +screen_t SCREEN; + +void screenInit(void) { + // Virtual backbuffer for dynamic resolution scaling + #if DISPLAY_SIZE_DYNAMIC == 1 + frameBufferInit(&SCREEN.frameBuffer, DISPLAY_WIDTH, DISPLAY_HEIGHT); + cameraInit(&SCREEN.frameBufferCamera); + SCREEN.frameBufferCamera.projType = CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC; + SCREEN.frameBufferCamera.viewType = CAMERA_VIEW_TYPE_MATRIX; + glm_lookat( + (vec3){0.0f, 0.0f, 1.0f}, + (vec3){0.0f, 0.0f, 0.0f}, + (vec3){0.0f, 1.0f, 0.0f}, + SCREEN.frameBufferCamera.view + ); + SCREEN.frameBufferCamera.nearClip = -1.0f; + SCREEN.frameBufferCamera.farClip = 1.0f; + #endif +} + +void screenBind(void) { + #if DISPLAY_SIZE_DYNAMIC == 1 + frameBufferBind(&SCREEN.frameBuffer); + #else + frameBufferBind(NULL); + #endif + + frameBufferClear( + FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH, + COLOR_CORNFLOWER_BLUE + ); +} + +void screenUnbindAndRender(void) { + assertTrue(SPRITEBATCH.spriteCount == 0, "Sprite batch not flushed"); + + // Render to real backbuffer + #if DISPLAY_SIZE_DYNAMIC == 1 + frameBufferBind(NULL); + frameBufferClear( + FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH, + COLOR_BLACK + ); + + + SCREEN.frameBufferCamera.orthographic.left = 0; + SCREEN.frameBufferCamera.orthographic.right = frameBufferGetWidth( + FRAMEBUFFER_BOUND + ); + SCREEN.frameBufferCamera.orthographic.bottom = frameBufferGetHeight( + FRAMEBUFFER_BOUND + ); + SCREEN.frameBufferCamera.orthographic.top = 0; + cameraPushMatrix(&SCREEN.frameBufferCamera); + + vec2 backbuffer = { + (float_t)frameBufferGetWidth(FRAMEBUFFER_BOUND), + (float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND) + }; + vec2 virtual = { + (float_t)frameBufferGetWidth(&SCREEN.frameBuffer), + (float_t)frameBufferGetHeight(&SCREEN.frameBuffer) + }; + + // Compare aspect ratios. + vec4 viewport; + float_t backbufferAspect = backbuffer[0] / backbuffer[1]; + float_t virtualAspect = virtual[0] / virtual[1]; + if (backbufferAspect > virtualAspect) { + // Backbuffer is wider: pillarbox + float_t scale = backbuffer[1] / virtual[1]; + float_t width = virtual[0] * scale; + viewport[0] = (backbuffer[0] - width) * 0.5f; + viewport[1] = 0; + viewport[2] = width; + viewport[3] = backbuffer[1]; + } else { + // Backbuffer is taller: letterbox + float_t scale = backbuffer[0] / virtual[0]; + float_t height = virtual[1] * scale; + viewport[0] = 0; + viewport[1] = (backbuffer[1] - height) * 0.5f; + viewport[2] = backbuffer[0]; + viewport[3] = height; + } + + spriteBatchPush( + &SCREEN.frameBuffer.texture, + viewport[0], viewport[1], + viewport[0] + viewport[2], viewport[1] + viewport[3], + COLOR_WHITE, + 0.0f, 1.0f, 1.0f, 0.0f + ); + spriteBatchFlush(); + cameraPopMatrix(); + #endif +} + +void screenDispose(void) { + frameBufferDispose(&SCREEN.frameBuffer); +} \ No newline at end of file diff --git a/src/display/screen.h b/src/display/screen.h new file mode 100644 index 0000000..cdc0faf --- /dev/null +++ b/src/display/screen.h @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "display/camera.h" +#include "display/framebuffer/framebuffer.h" + +typedef struct { + #if DISPLAY_SIZE_DYNAMIC == 1 + framebuffer_t frameBuffer; + camera_t frameBufferCamera; + #else + void *empty; + #endif +} screen_t; + +extern screen_t SCREEN; + +/** + * Initializes the screen. + */ +void screenInit(void); + +/** + * Binds the screen for rendering. + */ +void screenBind(void); + +/** + * Unbinds the screen and renders it. + */ +void screenUnbindAndRender(void); + +/** + * Disposes of the screen. + */ +void screenDispose(void); \ No newline at end of file diff --git a/src/display/texture/texture.c b/src/display/texture/texture.c index 638aba2..7ad2e6d 100644 --- a/src/display/texture/texture.c +++ b/src/display/texture/texture.c @@ -44,7 +44,6 @@ void textureInit( switch(format) { case TEXTURE_FORMAT_RGBA: - assertNotNull(data.rgba.colors, "RGBA texture data cannot be NULL"); glTexImage2D( GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, (void*)data.rgba.colors @@ -52,7 +51,6 @@ void textureInit( break; case TEXTURE_FORMAT_ALPHA: - assertNotNull(data.alpha.data, "Alpha texture data cannot be NULL"); glTexImage2D( GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, (void*)data.alpha.data diff --git a/src/display/texture/texture.h b/src/display/texture/texture.h index cfe9e4d..be6ac48 100644 --- a/src/display/texture/texture.h +++ b/src/display/texture/texture.h @@ -6,8 +6,8 @@ */ #pragma once -#include "display/display.h" #include "display/color.h" +#include "display/displaydefs.h" typedef enum { #if DISPLAY_SDL2