Files
dusk/src/dusk/display/screen/screen.c
T
2026-04-10 07:31:31 -05:00

399 lines
12 KiB
C

/**
* 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"
#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,
COLOR_WHITE,
0.0f, 0.0f,
1.0f, 1.0f
);
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 = (float_t)SCREEN.width / (float_t)SCREEN.height;
// 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 = (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) {
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;
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;
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
COLOR_WHITE,
0.0f, 0.0f,
1.0f, 1.0f
);
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();
}