/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "screen.h" #include "assert/assert.h" #include "util/memory.h" #include "display/mesh/quad.h" #include "display/shader/shaderunlit.h" screen_t SCREEN; errorret_t screenInit() { memoryZero(&SCREEN, sizeof(screen_t)); SCREEN.background = COLOR_CORNFLOWER_BLUE; #ifdef DUSK_DISPLAY_SIZE_DYNAMIC SCREEN.mode = SCREEN_MODE_FIXED_VIEWPORT_HEIGHT; SCREEN.fixedHeight.height = DUSK_DISPLAY_SCREEN_HEIGHT; quadBuffer( SCREEN.frameBufferMeshVertices, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f #if MESH_ENABLE_COLOR , COLOR_WHITE #endif ); errorChain(meshInit( &SCREEN.frameBufferMesh, QUAD_PRIMITIVE_TYPE, QUAD_VERTEX_COUNT, SCREEN.frameBufferMeshVertices )); #endif // Init screen to backbuffer mode by default errorChain(screenBind()); errorOk(); } errorret_t screenBind() { // Assume backbuffer is currently bound. switch(SCREEN.mode) { case SCREEN_MODE_BACKBUFFER: { // Screen mode backbuffer uses the full display size SCREEN.width = frameBufferGetWidth(FRAMEBUFFER_BOUND); SCREEN.height = frameBufferGetHeight(FRAMEBUFFER_BOUND); SCREEN.aspect = frameBufferGetAspect(FRAMEBUFFER_BOUND); // No needd for a framebuffer. #ifdef DUSK_DISPLAY_SIZE_DYNAMIC if(SCREEN.framebufferReady) { errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } #endif break; } #ifdef DUSK_DISPLAY_SIZE_DYNAMIC case SCREEN_MODE_FIXED_SIZE: { SCREEN.width = SCREEN.fixedSize.width; SCREEN.height = SCREEN.fixedSize.height; SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; if(SCREEN.framebufferReady) { // Is current framebuffer the correct size? int32_t curFbWidth, curFbHeight; curFbWidth = frameBufferGetWidth(&SCREEN.framebuffer); curFbHeight = frameBufferGetHeight(&SCREEN.framebuffer); if(curFbWidth == SCREEN.width && curFbHeight == SCREEN.height) { // Correct size, nothing to do. errorChain(frameBufferBind(&SCREEN.framebuffer)); errorOk(); } // Need a new framebuffer. errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } // Create new framebuffer errorChain(frameBufferInit( &SCREEN.framebuffer, SCREEN.width, SCREEN.height )); SCREEN.framebufferReady = true; errorChain(frameBufferBind(&SCREEN.framebuffer)); break; } case SCREEN_MODE_ASPECT_RATIO: { // Aspect ratio mode, requires a framebuffer. int32_t fbWidth, fbHeight; fbWidth = frameBufferGetWidth(FRAMEBUFFER_BOUND); fbHeight = frameBufferGetHeight(FRAMEBUFFER_BOUND); float_t currentAspect = frameBufferGetAspect(FRAMEBUFFER_BOUND); if(currentAspect == SCREEN.aspectRatio.ratio) { // No need to use framebuffer. SCREEN.width = fbWidth; SCREEN.height = fbHeight; SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; if(SCREEN.framebufferReady) { errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } errorOk(); } int32_t newFbWidth, newFbHeight; if(currentAspect > SCREEN.aspectRatio.ratio) { // Wider than target aspect, limit by height newFbWidth = (int32_t)floorf(fbHeight * SCREEN.aspectRatio.ratio); newFbHeight = (int32_t)fbHeight; } else { // Taller than target aspect, limit by width newFbHeight = (int32_t)floorf(fbWidth / SCREEN.aspectRatio.ratio); newFbWidth = (int32_t)fbWidth; } if(SCREEN.framebufferReady) { // Is current framebuffer the correct size? int32_t curFbWidth, curFbHeight; float_t curFbAspect = frameBufferGetAspect(&SCREEN.framebuffer); curFbWidth = frameBufferGetWidth(&SCREEN.framebuffer); curFbHeight = frameBufferGetHeight(&SCREEN.framebuffer); if(curFbWidth == newFbWidth && curFbHeight == newFbHeight) { // Correct size, nothing to do. SCREEN.width = newFbWidth; SCREEN.height = newFbHeight; SCREEN.aspect = curFbAspect; errorChain(frameBufferBind(&SCREEN.framebuffer)); errorOk(); } // Need a new framebuffer. errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } // Create new framebuffer 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 errorChain(frameBufferBind(&SCREEN.framebuffer)); break; } case SCREEN_MODE_FIXED_HEIGHT: { float_t fbWidth = (float_t)frameBufferGetWidth(FRAMEBUFFER_BOUND); float_t fbHeight = (float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND); float_t fbAspect = fbWidth / fbHeight; int32_t newFbWidth, newFbHeight; newFbHeight = SCREEN.fixedHeight.height; newFbWidth = (int32_t)floorf(newFbHeight * fbAspect); SCREEN.width = newFbWidth; SCREEN.height = newFbHeight; SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; if(fbWidth == newFbWidth && fbHeight == newFbHeight) { // No need to use framebuffer. if(SCREEN.framebufferReady) { errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } errorOk(); } if(SCREEN.framebufferReady) { // Is current framebuffer the correct size? int32_t curFbWidth, curFbHeight; curFbWidth = frameBufferGetWidth(&SCREEN.framebuffer); curFbHeight = frameBufferGetHeight(&SCREEN.framebuffer); if(curFbWidth == newFbWidth && curFbHeight == newFbHeight) { errorChain(frameBufferBind(&SCREEN.framebuffer)); errorOk(); } // Need a new framebuffer. errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } // Create a new framebuffer. errorChain(frameBufferInit( &SCREEN.framebuffer, newFbWidth, newFbHeight )); SCREEN.framebufferReady = true; errorChain(frameBufferBind(&SCREEN.framebuffer)); break; } case SCREEN_MODE_FIXED_WIDTH: { float_t fbWidth = (float_t)frameBufferGetWidth(FRAMEBUFFER_BOUND); float_t fbHeight = (float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND); float_t fbAspect = fbWidth / fbHeight; int32_t newFbWidth, newFbHeight; newFbWidth = SCREEN.fixedWidth.width; newFbHeight = (int32_t)floorf(newFbWidth / fbAspect); SCREEN.width = newFbWidth; SCREEN.height = newFbHeight; SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; if(fbWidth == newFbWidth && fbHeight == newFbHeight) { // No need to use framebuffer. if(SCREEN.framebufferReady) { errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } errorOk(); } if(SCREEN.framebufferReady) { // Is current framebuffer the correct size? int32_t curFbWidth, curFbHeight; curFbWidth = frameBufferGetWidth(&SCREEN.framebuffer); curFbHeight = frameBufferGetHeight(&SCREEN.framebuffer); if(curFbWidth == newFbWidth && curFbHeight == newFbHeight) { errorChain(frameBufferBind(&SCREEN.framebuffer)); errorOk(); } // Need a new framebuffer. errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } // Create a new framebuffer. errorChain(frameBufferInit( &SCREEN.framebuffer, newFbWidth, newFbHeight )); SCREEN.framebufferReady = true; errorChain(frameBufferBind(&SCREEN.framebuffer)); break; } case SCREEN_MODE_FIXED_VIEWPORT_HEIGHT: { SCREEN.height = SCREEN.fixedViewportHeight.height; float_t fbWidth = (float_t)frameBufferGetWidth(FRAMEBUFFER_BOUND); float_t fbHeight = (float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND); float_t fbAspect = fbWidth / fbHeight; SCREEN.width = (int32_t)floorf(SCREEN.height * fbAspect); SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; break; } #endif default: { assertUnreachable("Invalid screen mode."); break; } } errorOk(); } errorret_t screenUnbind() { switch(SCREEN.mode) { // Nothing to do here. case SCREEN_MODE_BACKBUFFER: break; #ifdef DUSK_DISPLAY_SIZE_DYNAMIC case SCREEN_MODE_ASPECT_RATIO: case SCREEN_MODE_FIXED_HEIGHT: case SCREEN_MODE_FIXED_SIZE: case SCREEN_MODE_FIXED_WIDTH: if(SCREEN.framebufferReady) { errorChain(frameBufferBind(NULL)); } break; case SCREEN_MODE_FIXED_VIEWPORT_HEIGHT: break; #endif default: assertUnreachable("Invalid screen mode."); break; } errorOk(); } errorret_t screenRender() { if(SCREEN.mode == SCREEN_MODE_BACKBUFFER) { errorOk(); } #ifdef DUSK_DISPLAY_SIZE_DYNAMIC if(SCREEN.mode == SCREEN_MODE_FIXED_VIEWPORT_HEIGHT) { glViewport(0, 0, SCREEN.width, SCREEN.height); errorOk(); } if( SCREEN.mode == SCREEN_MODE_ASPECT_RATIO || SCREEN.mode == SCREEN_MODE_FIXED_HEIGHT || SCREEN.mode == SCREEN_MODE_FIXED_SIZE || SCREEN.mode == SCREEN_MODE_FIXED_WIDTH ) { if(!SCREEN.framebufferReady) { // Nothing to do here. errorOk(); } float_t bbWidth, bbHeight; bbWidth = (float_t)frameBufferGetWidth(FRAMEBUFFER_BOUND); bbHeight = (float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND); float_t backBufferAspect = bbWidth / bbHeight; // Determine framebuffer centering float_t fbWidth, fbHeight, fbAspect; float_t fbX, fbY; fbWidth = frameBufferGetWidth(&SCREEN.framebuffer); fbHeight = frameBufferGetHeight(&SCREEN.framebuffer); fbAspect = fbWidth / fbHeight; if(backBufferAspect > fbAspect) { fbHeight = bbHeight; fbWidth = fbHeight * fbAspect; fbX = (bbWidth - fbWidth) * 0.5f; fbY = 0.0f; } else { fbWidth = bbWidth; fbHeight = fbWidth / fbAspect; fbX = 0.0f; fbY = (bbHeight - fbHeight) * 0.5f; } // Determine back buffer matricies float_t centerX = bbWidth * 0.5f; float_t centerY = bbHeight * 0.5f; mat4 view, proj, model; glm_ortho( 0.0f, bbWidth, bbHeight, 0.0f, 0.01f, 1.0f, proj ); glm_mat4_identity(view); glm_mat4_identity(model); quadBuffer( SCREEN.frameBufferMeshVertices, centerX - fbWidth * 0.5f, centerY + fbHeight * 0.5f, // top-left centerX + fbWidth * 0.5f, centerY - fbHeight * 0.5f, // bottom-right 0.0f, 0.0f, 1.0f, 1.0f #if MESH_ENABLE_COLOR , COLOR_WHITE #endif ); frameBufferClear( FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH, COLOR_BLACK ); shaderBind(&SHADER_UNLIT); 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."); } errorret_t screenDispose() { #ifdef DUSK_DISPLAY_SIZE_DYNAMIC if(SCREEN.framebufferReady) { errorChain(frameBufferDispose(&SCREEN.framebuffer)); SCREEN.framebufferReady = false; } #endif errorOk(); }