/** * 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 "util/memory.h" #include "display/mesh/quad.h" screen_t SCREEN; void screenInit() { memoryZero(&SCREEN, sizeof(screen_t)); #if DISPLAY_SIZE_DYNAMIC == 1 cameraInitOrthographic(&SCREEN.framebufferCamera); SCREEN.framebufferCamera.viewType = CAMERA_VIEW_TYPE_2D; SCREEN.framebufferCamera._2d.position[0] = 0; SCREEN.framebufferCamera._2d.position[1] = 0; SCREEN.framebufferCamera._2d.zoom = 1.0f; quadBuffer( SCREEN.frameBufferMeshVertices, 0.0f, 0.0f, 1.0f, 1.0f, COLOR_WHITE, 0.0f, 0.0f, 1.0f, 1.0f ); meshInit( &SCREEN.frameBufferMesh, QUAD_PRIMITIVE_TYPE, QUAD_VERTEX_COUNT, SCREEN.frameBufferMeshVertices ); #endif } void 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); // No needd for a framebuffer. #if DISPLAY_SIZE_DYNAMIC == 1 if(SCREEN.framebufferReady) { frameBufferDispose(&SCREEN.framebuffer); SCREEN.framebufferReady = false; } #endif break; } #if DISPLAY_SIZE_DYNAMIC == 1 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. frameBufferBind(&SCREEN.framebuffer); return; } // Need a new framebuffer. frameBufferDispose(&SCREEN.framebuffer); SCREEN.framebufferReady = false; } // Create new framebuffer frameBufferInit(&SCREEN.framebuffer, SCREEN.width, SCREEN.height); SCREEN.framebufferReady = true; 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 = (float_t)fbWidth / (float_t)fbHeight; 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) { frameBufferDispose(&SCREEN.framebuffer); SCREEN.framebufferReady = false; } return; } 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; 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 = (float_t)SCREEN.width / (float_t)SCREEN.height; frameBufferBind(&SCREEN.framebuffer); return; } // Need a new framebuffer. frameBufferDispose(&SCREEN.framebuffer); SCREEN.framebufferReady = false; } // Create new framebuffer 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); 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) { frameBufferDispose(&SCREEN.framebuffer); SCREEN.framebufferReady = false; } return; } 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) { frameBufferBind(&SCREEN.framebuffer); return; } // Need a new framebuffer. frameBufferDispose(&SCREEN.framebuffer); SCREEN.framebufferReady = false; } // Create a new framebuffer. frameBufferInit(&SCREEN.framebuffer, newFbWidth, newFbHeight); SCREEN.framebufferReady = true; 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) { frameBufferDispose(&SCREEN.framebuffer); SCREEN.framebufferReady = false; } return; } 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) { frameBufferBind(&SCREEN.framebuffer); return; } // Need a new framebuffer. frameBufferDispose(&SCREEN.framebuffer); SCREEN.framebufferReady = false; } // Create a new framebuffer. frameBufferInit(&SCREEN.framebuffer, newFbWidth, newFbHeight); SCREEN.framebufferReady = true; frameBufferBind(&SCREEN.framebuffer); break; } #endif default: { assertUnreachable("Invalid screen mode."); break; } } } void screenUnbind() { switch(SCREEN.mode) { case SCREEN_MODE_BACKBUFFER: { // Nothing to do here. break; } #if DISPLAY_SIZE_DYNAMIC == 1 case SCREEN_MODE_ASPECT_RATIO: case SCREEN_MODE_FIXED_HEIGHT: case SCREEN_MODE_FIXED_SIZE: case SCREEN_MODE_FIXED_WIDTH: if(SCREEN.framebufferReady) frameBufferBind(NULL); break; #endif default: { assertUnreachable("Invalid screen mode."); break; } } } void screenRender() { if(SCREEN.mode == SCREEN_MODE_BACKBUFFER) { return; } #if DISPLAY_SIZE_DYNAMIC == 1 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. return; } 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; } float_t centerX = bbWidth * 0.5f; float_t centerY = bbHeight * 0.5f; SCREEN.framebufferCamera.orthographic.left = 0.0f; SCREEN.framebufferCamera.orthographic.right = bbWidth; SCREEN.framebufferCamera.orthographic.top = 0.0f; SCREEN.framebufferCamera.orthographic.bottom = bbHeight; quadBuffer( SCREEN.frameBufferMeshVertices, centerX - fbWidth * 0.5f, centerY + fbHeight * 0.5f, // top-left centerX + fbWidth * 0.5f, centerY - fbHeight * 0.5f, // bottom-right COLOR_WHITE, 0.0f, 0.0f, 1.0f, 1.0f ); frameBufferClear( FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH, COLOR_BLACK ); cameraPushMatrix(&SCREEN.framebufferCamera); textureBind(&SCREEN.framebuffer.texture); meshDraw(&SCREEN.frameBufferMesh, 0, -1); cameraPopMatrix(); return; }; #endif assertUnreachable("Invalid screen mode."); } void screenDispose() { #if DISPLAY_SIZE_DYNAMIC == 1 if(SCREEN.framebufferReady) { frameBufferDispose(&SCREEN.framebuffer); SCREEN.framebufferReady = false; } #endif }