Add CRT post processing effect and allow for lower internal resolutions; see #1

This commit is contained in:
Dominic Szablewski 2023-08-13 14:02:19 +02:00
parent 7a9f757a79
commit c0019b1bca
6 changed files with 505 additions and 156 deletions

View file

@ -8,6 +8,18 @@ typedef enum {
RENDER_BLEND_LIGHTER RENDER_BLEND_LIGHTER
} render_blend_mode_t; } render_blend_mode_t;
typedef enum {
RENDER_RES_NATIVE,
RENDER_RES_240P,
RENDER_RES_480P,
} render_resolution_t;
typedef enum {
RENDER_POST_NONE,
RENDER_POST_CRT,
NUM_RENDER_POST_EFFCTS,
} render_post_effect_t;
#define RENDER_USE_MIPMAPS 1 #define RENDER_USE_MIPMAPS 1
#define RENDER_FADEOUT_NEAR 48000.0 #define RENDER_FADEOUT_NEAR 48000.0
@ -15,10 +27,12 @@ typedef enum {
extern uint16_t RENDER_NO_TEXTURE; extern uint16_t RENDER_NO_TEXTURE;
void render_init(vec2i_t size); void render_init(vec2i_t screen_size);
void render_cleanup(); void render_cleanup();
void render_resize(vec2i_t size); void render_set_screen_size(vec2i_t size);
void render_set_resolution(render_resolution_t res);
void render_set_post_effect(render_post_effect_t post);
vec2i_t render_size(); vec2i_t render_size();
void render_frame_prepare(); void render_frame_prepare();

View file

@ -19,6 +19,10 @@
void glCreateTextures(GLuint ignored, GLsizei n, GLuint *name) { void glCreateTextures(GLuint ignored, GLsizei n, GLuint *name) {
glGenTextures(1, name); glGenTextures(1, name);
} }
void glFramebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level) {
glFramebufferTexture2D(target, attachment, GL_TEXTURE_2D, texture, level);
}
#endif #endif
// WINDOWS // WINDOWS
@ -37,14 +41,12 @@
#include "libs/stb_image_write.h" #include "libs/stb_image_write.h"
#include "system.h"
#include "render.h" #include "render.h"
#include "mem.h" #include "mem.h"
#include "utils.h" #include "utils.h"
#define NEAR_PLANE 16.0
#define FAR_PLANE 262144.0
#define ATLAS_SIZE 64 #define ATLAS_SIZE 64
#define ATLAS_GRID 32 #define ATLAS_GRID 32
#define ATLAS_BORDER 16 #define ATLAS_BORDER 16
@ -52,12 +54,24 @@
#define RENDER_TRIS_BUFFER_CAPACITY 2048 #define RENDER_TRIS_BUFFER_CAPACITY 2048
#define TEXTURES_MAX 1024 #define TEXTURES_MAX 1024
// WebGL (GLES) needs the `precision` to be set, OpenGL 2.something
// doesn't like that...
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
// WebGL (GLES) needs the `precision` to be set, wheras OpenGL 2
// doesn't like that...
#define SHADER_SOURCE(...) "precision highp float;" #__VA_ARGS__ #define SHADER_SOURCE(...) "precision highp float;" #__VA_ARGS__
// WebGL1 only allows for a 16 bit depth buffer attachment, so
// we sacrifice a bit of the near plane to get more precision
// further out
#define NEAR_PLANE 128.0
#define FAR_PLANE (RENDER_FADEOUT_FAR)
#define RENDER_DEPTH_BUFFER_INTERNAL_FORMAT GL_DEPTH_COMPONENT16
#else #else
#define SHADER_SOURCE(...) #__VA_ARGS__ #define SHADER_SOURCE(...) #__VA_ARGS__
#define NEAR_PLANE 16.0
#define FAR_PLANE (RENDER_FADEOUT_FAR)
#define RENDER_DEPTH_BUFFER_INTERNAL_FORMAT GL_DEPTH_COMPONENT24
#endif #endif
@ -68,98 +82,24 @@ typedef struct {
uint16_t RENDER_NO_TEXTURE; uint16_t RENDER_NO_TEXTURE;
static GLuint u_color; #define use_program(SHADER) \
static GLuint u_view; glUseProgram((SHADER)->program); \
static GLuint u_model; glBindVertexArray((SHADER)->vao);
static GLuint u_projection;
static GLuint u_screen;
static GLuint u_camera_pos;
static GLuint u_fade;
static GLuint a_pos; #define bind_va_f(index, container, member, start) \
static GLuint a_uv;
static GLuint a_color;
static GLuint vbo;
static tris_t tris_buffer[RENDER_TRIS_BUFFER_CAPACITY];
static uint32_t tris_len = 0;
static vec2i_t screen_size;
static uint32_t atlas_map[ATLAS_SIZE] = {0};
static GLuint atlas_texture = 0;
static render_blend_mode_t blend_mode = RENDER_BLEND_NORMAL;
static mat4_t projection_mat_2d = mat4_identity();
static mat4_t projection_mat_3d = mat4_identity();
static mat4_t sprite_mat = mat4_identity();
static mat4_t view_mat = mat4_identity();
static render_texture_t textures[TEXTURES_MAX];
static uint32_t textures_len = 0;
static bool texture_mipmap_is_dirty = false;
static const char * const VERTEX_SHADER = SHADER_SOURCE(
attribute vec3 pos;
attribute vec2 uv;
attribute vec4 color;
varying vec4 v_color;
varying vec2 v_uv;
uniform mat4 view;
uniform mat4 model;
uniform mat4 projection;
uniform vec2 screen;
uniform vec3 camera_pos;
uniform vec2 fade;
void main() {
gl_Position = projection * view * model * vec4(pos, 1.0);
gl_Position.xy += screen.xy * gl_Position.w;
v_color = color;
v_color.a *= smoothstep(
fade.y, fade.x, // fadeout far, near
length(vec4(camera_pos, 1.0) - model * vec4(pos, 1.0))
);
v_uv = uv;
v_uv = uv / 2048.0; // ATLAS_GRID * ATLAS_SIZE
}
);
static const char * const FRAGMENT_SHADER_YCRCB = SHADER_SOURCE(
varying vec4 v_color;
varying vec2 v_uv;
uniform sampler2D texture;
void main() {
vec4 tex_color = texture2D(texture, v_uv);
vec4 color = tex_color * v_color;
if (color.a == 0.0) {
discard;
}
color.rgb = color.rgb * 2.0;
gl_FragColor = color;
}
);
#define render_bind_va_f(index, container, member, start) \
glVertexAttribPointer( \ glVertexAttribPointer( \
index, member_size(container, member)/sizeof(float), GL_FLOAT, false, \ index, member_size(container, member)/sizeof(float), GL_FLOAT, false, \
sizeof(container), \ sizeof(container), \
(GLvoid*)(offsetof(container, member) + start) \ (GLvoid*)(offsetof(container, member) + start) \
) )
#define render_bind_va_color(index, container, member, start) \ #define bind_va_color(index, container, member, start) \
glVertexAttribPointer( \ glVertexAttribPointer( \
index, 4, GL_UNSIGNED_BYTE, true, \ index, 4, GL_UNSIGNED_BYTE, true, \
sizeof(container), \ sizeof(container), \
(GLvoid*)(offsetof(container, member) + start) \ (GLvoid*)(offsetof(container, member) + start) \
) )
static void render_flush();
static GLuint compile_shader(GLenum type, const char *source);
static GLuint compile_shader(GLenum type, const char *source) { static GLuint compile_shader(GLenum type, const char *source) {
GLuint shader = glCreateShader(type); GLuint shader = glCreateShader(type);
@ -177,11 +117,308 @@ static GLuint compile_shader(GLenum type, const char *source) {
return shader; return shader;
} }
static GLuint create_program(const char *vs_source, const char *fs_source) {
GLuint vs = compile_shader(GL_VERTEX_SHADER, vs_source);
GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fs_source);
GLuint program = glCreateProgram();
glAttachShader(program, vs);
glAttachShader(program, fs);
glLinkProgram(program);
GLint linked;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if (!linked) {
#ifdef CRTEMU_REPORT_SHADER_ERRORS
char error_message[256];
strcpy(error_message, prefix);
int len = 0, written = 0;
glGetShaderiv(vs, GL_INFO_LOG_LENGTH, &len);
glGetShaderInfoLog(programm, (GLsizei)( sizeof(error_message)), &written, error_message);
die("Shader Link Error: %s" error_message);
#endif
return 0;
}
glUseProgram(program);
return program;
}
// -----------------------------------------------------------------------------
// Main game shaders
static const char * const SHADER_GAME_VS = SHADER_SOURCE(
attribute vec3 pos;
attribute vec2 uv;
attribute vec4 color;
varying vec4 v_color;
varying vec2 v_uv;
uniform mat4 view;
uniform mat4 model;
uniform mat4 projection;
uniform vec2 screen;
uniform vec3 camera_pos;
uniform vec2 fade;
uniform float time;
void main() {
gl_Position = projection * view * model * vec4(pos, 1.0);
gl_Position.xy += screen.xy * gl_Position.w;
v_color = color;
v_color.a *= smoothstep(
fade.y, fade.x, // fadeout far, near
length(vec4(camera_pos, 1.0) - model * vec4(pos, 1.0))
);
v_uv = uv;
v_uv = uv / 2048.0; // ATLAS_GRID * ATLAS_SIZE
}
);
static const char * const SHADER_GAME_FS = SHADER_SOURCE(
varying vec4 v_color;
varying vec2 v_uv;
uniform sampler2D texture;
void main() {
vec4 tex_color = texture2D(texture, v_uv);
vec4 color = tex_color * v_color;
if (color.a == 0.0) {
discard;
}
color.rgb = color.rgb * 2.0;
gl_FragColor = color;
}
);
typedef struct {
GLuint program;
GLuint vao;
struct {
GLuint view;
GLuint model;
GLuint projection;
GLuint screen;
GLuint camera_pos;
GLuint fade;
GLuint time;
} uniform;
struct {
GLuint pos;
GLuint uv;
GLuint color;
} attribute;
} prg_game_t;
prg_game_t *shader_game_init() {
prg_game_t *s = mem_bump(sizeof(prg_game_t));
s->program = create_program(SHADER_GAME_VS, SHADER_GAME_FS);
s->uniform.view = glGetUniformLocation(s->program, "view");
s->uniform.model = glGetUniformLocation(s->program, "model");
s->uniform.projection = glGetUniformLocation(s->program, "projection");
s->uniform.screen = glGetUniformLocation(s->program, "screen");
s->uniform.camera_pos = glGetUniformLocation(s->program, "camera_pos");
s->uniform.fade = glGetUniformLocation(s->program, "fade");
s->attribute.pos = glGetAttribLocation(s->program, "pos");
s->attribute.uv = glGetAttribLocation(s->program, "uv");
s->attribute.color = glGetAttribLocation(s->program, "color");
glGenVertexArrays(1, &s->vao);
glBindVertexArray(s->vao);
glEnableVertexAttribArray(s->attribute.pos);
glEnableVertexAttribArray(s->attribute.uv);
glEnableVertexAttribArray(s->attribute.color);
bind_va_f(s->attribute.pos, vertex_t, pos, 0);
bind_va_f(s->attribute.uv, vertex_t, uv, 0);
bind_va_color(s->attribute.color, vertex_t, color, 0);
return s;
}
// -----------------------------------------------------------------------------
// POST Effect shaders
static const char * const SHADER_POST_VS = SHADER_SOURCE(
attribute vec3 pos;
attribute vec2 uv;
varying vec2 v_uv;
uniform mat4 projection;
uniform vec2 screen_size;
uniform float time;
void main() {
gl_Position = projection * vec4(pos, 1.0);
v_uv = uv;
}
);
static const char * const SHADER_POST_FS_DEFAULT = SHADER_SOURCE(
varying vec2 v_uv;
uniform sampler2D texture;
uniform vec2 screen_size;
void main() {
gl_FragColor = texture2D(texture, v_uv);
}
);
// CRT effect based on https://www.shadertoy.com/view/Ms23DR
// by https://github.com/mattiasgustavsson/
static const char * const SHADER_POST_FS_CRT = SHADER_SOURCE(
varying vec2 v_uv;
uniform float time;
uniform sampler2D texture;
uniform vec2 screen_size;
vec2 curve(vec2 uv) {
uv = (uv - 0.5) * 2.0;
uv *= 1.1;
uv.x *= 1.0 + pow((abs(uv.y) / 5.0), 2.0);
uv.y *= 1.0 + pow((abs(uv.x) / 4.0), 2.0);
uv = (uv / 2.0) + 0.5;
uv = uv *0.92 + 0.04;
return uv;
}
void main(){
vec2 uv = curve(gl_FragCoord.xy / screen_size);
vec3 color;
float x = sin(0.3*time+uv.y*21.0)*sin(0.7*time+uv.y*29.0)*sin(0.3+0.33*time+uv.y*31.0)*0.0017;
color.r = texture2D(texture, vec2(x+uv.x+0.001,uv.y+0.001)).x+0.05;
color.g = texture2D(texture, vec2(x+uv.x+0.000,uv.y-0.002)).y+0.05;
color.b = texture2D(texture, vec2(x+uv.x-0.002,uv.y+0.000)).z+0.05;
color.r += 0.08*texture2D(texture, 0.75*vec2(x+0.025, -0.027)+vec2(uv.x+0.001,uv.y+0.001)).x;
color.g += 0.05*texture2D(texture, 0.75*vec2(x+-0.022, -0.02)+vec2(uv.x+0.000,uv.y-0.002)).y;
color.b += 0.08*texture2D(texture, 0.75*vec2(x+-0.02, -0.018)+vec2(uv.x-0.002,uv.y+0.000)).z;
color = clamp(color*0.6+0.4*color*color*1.0,0.0,1.0);
float vignette = (0.0 + 1.0*16.0*uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y));
color *= vec3(pow(vignette, 0.25));
color *= vec3(0.95,1.05,0.95);
color *= 2.8;
float scanlines = clamp( 0.35+0.35*sin(3.5*time+uv.y*screen_size.y*1.5), 0.0, 1.0);
float s = pow(scanlines,1.7);
color = color * vec3(0.4+0.7*s);
color *= 1.0+0.01*sin(110.0*time);
if (uv.x < 0.0 || uv.x > 1.0)
color *= 0.0;
if (uv.y < 0.0 || uv.y > 1.0)
color *= 0.0;
color*=1.0-0.65*vec3(clamp((mod(gl_FragCoord.x, 2.0)-1.0)*2.0,0.0,1.0));
gl_FragColor = vec4(color,1.0);
}
);
typedef struct {
GLuint program;
GLuint vao;
struct {
GLuint projection;
GLuint screen_size;
GLuint time;
} uniform;
struct {
GLuint pos;
GLuint uv;
} attribute;
} prg_post_t;
void shader_post_general_init(prg_post_t *s) {
s->uniform.projection = glGetUniformLocation(s->program, "projection");
s->uniform.screen_size = glGetUniformLocation(s->program, "screen_size");
s->uniform.time = glGetUniformLocation(s->program, "time");
s->attribute.pos = glGetAttribLocation(s->program, "pos");
s->attribute.uv = glGetAttribLocation(s->program, "uv");
glGenVertexArrays(1, &s->vao);
glBindVertexArray(s->vao);
glEnableVertexAttribArray(s->attribute.pos);
glEnableVertexAttribArray(s->attribute.uv);
bind_va_f(s->attribute.pos, vertex_t, pos, 0);
bind_va_f(s->attribute.uv, vertex_t, uv, 0);
}
prg_post_t *shader_post_default_init() {
prg_post_t *s = mem_bump(sizeof(prg_post_t));
s->program = create_program(SHADER_POST_VS, SHADER_POST_FS_DEFAULT);
shader_post_general_init(s);
return s;
}
prg_post_t *shader_post_crt_init() {
prg_post_t *s = mem_bump(sizeof(prg_post_t));
s->program = create_program(SHADER_POST_VS, SHADER_POST_FS_CRT);
shader_post_general_init(s);
return s;
}
// -----------------------------------------------------------------------------
static GLuint vbo;
static tris_t tris_buffer[RENDER_TRIS_BUFFER_CAPACITY];
static uint32_t tris_len = 0;
static vec2i_t screen_size;
static vec2i_t backbuffer_size;
static uint32_t atlas_map[ATLAS_SIZE] = {0};
static GLuint atlas_texture = 0;
static render_blend_mode_t blend_mode = RENDER_BLEND_NORMAL;
static mat4_t projection_mat_2d = mat4_identity();
static mat4_t projection_mat_bb = mat4_identity();
static mat4_t projection_mat_3d = mat4_identity();
static mat4_t sprite_mat = mat4_identity();
static mat4_t view_mat = mat4_identity();
static render_texture_t textures[TEXTURES_MAX];
static uint32_t textures_len = 0;
static bool texture_mipmap_is_dirty = false;
static render_resolution_t render_res;
static GLuint backbuffer = 0;
static GLuint backbuffer_texture = 0;
static GLuint backbuffer_depth_buffer = 0;
prg_game_t *prg_game;
prg_post_t *prg_post;
prg_post_t *prg_post_effects[NUM_RENDER_POST_EFFCTS] = {};
static void render_flush();
// static void gl_message_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) { // static void gl_message_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) {
// puts(message); // puts(message);
// } // }
void render_init(vec2i_t size) { void render_init(vec2i_t screen_size) {
#if defined(__APPLE__) && defined(__MACH__) #if defined(__APPLE__) && defined(__MACH__)
// OSX // OSX
// (nothing to do here) // (nothing to do here)
@ -215,50 +452,23 @@ void render_init(vec2i_t size) {
printf("atlas texture %5d\n", atlas_texture); printf("atlas texture %5d\n", atlas_texture);
// Shaders
GLuint fragment_shader = compile_shader(GL_FRAGMENT_SHADER, FRAGMENT_SHADER_YCRCB);
GLuint vertex_shader = compile_shader(GL_VERTEX_SHADER, VERTEX_SHADER);
GLuint shader_program = glCreateProgram();
glAttachShader(shader_program, vertex_shader);
glAttachShader(shader_program, fragment_shader);
glLinkProgram(shader_program);
glUseProgram(shader_program);
u_color = glGetUniformLocation(shader_program, "color");
u_view = glGetUniformLocation(shader_program, "view");
u_model = glGetUniformLocation(shader_program, "model");
u_projection = glGetUniformLocation(shader_program, "projection");
u_screen = glGetUniformLocation(shader_program, "screen");
u_camera_pos = glGetUniformLocation(shader_program, "camera_pos");
u_fade = glGetUniformLocation(shader_program, "fade");
a_pos = glGetAttribLocation(shader_program, "pos");
a_uv = glGetAttribLocation(shader_program, "uv");
a_color = glGetAttribLocation(shader_program, "color");
// Tris buffer // Tris buffer
glGenBuffers(1, &vbo); glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo);
GLuint va;
glGenVertexArrays(1, &va);
glBindVertexArray(va);
// Post Shaders
// Defaults prg_post_effects[RENDER_POST_NONE] = shader_post_default_init();
prg_post_effects[RENDER_POST_CRT] = shader_post_crt_init();
render_set_post_effect(RENDER_POST_NONE);
glEnableVertexAttribArray(a_pos); // Game shader
glEnableVertexAttribArray(a_uv);
glEnableVertexAttribArray(a_color);
render_bind_va_f(a_pos, vertex_t, pos, 0); prg_game = shader_game_init();
render_bind_va_f(a_uv, vertex_t, uv, 0); use_program(prg_game);
render_bind_va_color(a_color, vertex_t, color, 0);
render_resize(size);
render_set_view(vec3(0, 0, 0), vec3(0, 0, 0)); render_set_view(vec3(0, 0, 0), vec3(0, 0, 0));
render_set_model_mat(&mat4_identity()); render_set_model_mat(&mat4_identity());
@ -274,6 +484,12 @@ void render_init(vec2i_t size) {
rgba(128,128,128,255), rgba(128,128,128,255) rgba(128,128,128,255), rgba(128,128,128,255)
}; };
RENDER_NO_TEXTURE = render_texture_create(2, 2, white_pixels); RENDER_NO_TEXTURE = render_texture_create(2, 2, white_pixels);
// Backbuffer
render_res = RENDER_RES_NATIVE;
render_set_screen_size(screen_size);
} }
void render_cleanup() { void render_cleanup() {
@ -281,17 +497,17 @@ void render_cleanup() {
} }
static void render_setup_2d_projection_mat() { static mat4_t render_setup_2d_projection_mat(vec2i_t size) {
float near = -1; float near = -1;
float far = 1; float far = 1;
float left = 0; float left = 0;
float right = screen_size.x; float right = size.x;
float bottom = screen_size.y; float bottom = size.y;
float top = 0; float top = 0;
float lr = 1 / (left - right); float lr = 1 / (left - right);
float bt = 1 / (bottom - top); float bt = 1 / (bottom - top);
float nf = 1 / (near - far); float nf = 1 / (near - far);
projection_mat_2d = mat4( return mat4(
-2 * lr, 0, 0, 0, -2 * lr, 0, 0, 0,
0, -2 * bt, 0, 0, 0, -2 * bt, 0, 0,
0, 0, 2 * nf, 0, 0, 0, 2 * nf, 0,
@ -299,16 +515,16 @@ static void render_setup_2d_projection_mat() {
); );
} }
static void render_setup_3d_projection_mat() { static mat4_t render_setup_3d_projection_mat(vec2i_t size) {
// wipeout has a horizontal fov of 90deg, but we want the fov to be fixed // wipeout has a horizontal fov of 90deg, but we want the fov to be fixed
// for the vertical axis, so that widescreen displays just have a wider // for the vertical axis, so that widescreen displays just have a wider
// view. For the original 4/3 aspect ratio this equates to a vertial fov // view. For the original 4/3 aspect ratio this equates to a vertial fov
// of 73.75deg. // of 73.75deg.
float aspect = (float)screen_size.x / (float)screen_size.y; float aspect = (float)size.x / (float)size.y;
float fov = (73.75 / 180.0) * 3.14159265358; float fov = (73.75 / 180.0) * 3.14159265358;
float f = 1.0 / tan(fov / 2); float f = 1.0 / tan(fov / 2);
float nf = 1.0 / (NEAR_PLANE - FAR_PLANE); float nf = 1.0 / (NEAR_PLANE - FAR_PLANE);
projection_mat_3d = mat4( return mat4(
f / aspect, 0, 0, 0, f / aspect, 0, 0, 0,
0, f, 0, 0, 0, f, 0, 0,
0, 0, (FAR_PLANE + NEAR_PLANE) * nf, -1, 0, 0, (FAR_PLANE + NEAR_PLANE) * nf, -1,
@ -316,20 +532,85 @@ static void render_setup_3d_projection_mat() {
); );
} }
void render_resize(vec2i_t size) { void render_set_screen_size(vec2i_t size) {
glViewport(0, 0, size.x, size.y);
screen_size = size; screen_size = size;
projection_mat_bb = render_setup_2d_projection_mat(screen_size);
render_setup_2d_projection_mat(); render_set_resolution(render_res);
render_setup_3d_projection_mat(); }
void render_set_resolution(render_resolution_t res) {
render_res = res;
if (res == RENDER_RES_NATIVE) {
backbuffer_size = screen_size;
}
else {
float aspect = (float)screen_size.x / (float)screen_size.y;
if (res == RENDER_RES_240P) {
backbuffer_size = vec2i(240.0 * aspect, 240);
}
else if (res == RENDER_RES_480P) {
backbuffer_size = vec2i(480.0 * aspect, 480);
}
else {
die("Invalid resolution: %d", res);
}
}
if (!backbuffer) {
glGenTextures(1, &backbuffer_texture);
glGenFramebuffers(1, &backbuffer);
glGenRenderbuffers(1, &backbuffer_depth_buffer);
}
glBindTexture(GL_TEXTURE_2D, backbuffer_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, backbuffer_size.x, backbuffer_size.y, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_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);
glBindFramebuffer(GL_FRAMEBUFFER, backbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, backbuffer_depth_buffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, backbuffer_depth_buffer);
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, backbuffer_texture, 0);
glBindRenderbuffer(GL_RENDERBUFFER, backbuffer_depth_buffer);
glRenderbufferStorage(GL_RENDERBUFFER, RENDER_DEPTH_BUFFER_INTERNAL_FORMAT, backbuffer_size.x, backbuffer_size.y);
projection_mat_2d = render_setup_2d_projection_mat(backbuffer_size);
projection_mat_3d = render_setup_3d_projection_mat(backbuffer_size);
// Use nearest texture min filter for 240p and 480p
glBindTexture(GL_TEXTURE_2D, atlas_texture);
if (res == RENDER_RES_NATIVE) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, RENDER_USE_MIPMAPS ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
}
else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
glViewport(0, 0, backbuffer_size.x, backbuffer_size.y);
}
void render_set_post_effect(render_post_effect_t post) {
error_if(post < 0 || post > NUM_RENDER_POST_EFFCTS, "Invalid post effect %d", post);
prg_post = prg_post_effects[post];
} }
vec2i_t render_size() { vec2i_t render_size() {
return screen_size; return backbuffer_size;
} }
void render_frame_prepare() { void render_frame_prepare() {
glUniform2f(u_screen, 0, 0); use_program(prg_game);
glBindFramebuffer(GL_FRAMEBUFFER, backbuffer);
glViewport(0, 0, backbuffer_size.x, backbuffer_size.y);
glBindTexture(GL_TEXTURE_2D, atlas_texture);
glUniform2f(prg_game->uniform.screen, 0, 0);
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
glDepthMask(true); glDepthMask(true);
glDisable(GL_POLYGON_OFFSET_FILL); glDisable(GL_POLYGON_OFFSET_FILL);
@ -340,6 +621,36 @@ void render_frame_prepare() {
void render_frame_end() { void render_frame_end() {
render_flush(); render_flush();
use_program(prg_post);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, screen_size.x, screen_size.y);
glBindTexture(GL_TEXTURE_2D, backbuffer_texture);
glUniformMatrix4fv(prg_post->uniform.projection, 1, false, projection_mat_bb.m);
glUniform1f(prg_post->uniform.time, system_cycle_time());
glUniform2f(prg_post->uniform.screen_size, screen_size.x, screen_size.y);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
rgba_t white = rgba(128,128,128,255);
tris_buffer[tris_len++] = (tris_t){
.vertices = {
{.pos = {0, screen_size.y, 0}, .uv = {0, 0}, .color = white},
{.pos = {screen_size.x, 0, 0}, .uv = {1, 1}, .color = white},
{.pos = {0, 0, 0}, .uv = {0, 1}, .color = white},
}
};
tris_buffer[tris_len++] = (tris_t){
.vertices = {
{.pos = {screen_size.x, screen_size.y, 0}, .uv = {1, 0}, .color = white},
{.pos = {screen_size.x, 0, 0}, .uv = {1, 1}, .color = white},
{.pos = {0, screen_size.y, 0}, .uv = {0, 0}, .color = white},
}
};
render_flush();
} }
void render_flush() { void render_flush() {
@ -373,10 +684,10 @@ void render_set_view(vec3_t pos, vec3_t angles) {
render_set_model_mat(&mat4_identity()); render_set_model_mat(&mat4_identity());
render_flush(); render_flush();
glUniformMatrix4fv(u_view, 1, false, view_mat.m); glUniformMatrix4fv(prg_game->uniform.view, 1, false, view_mat.m);
glUniformMatrix4fv(u_projection, 1, false, projection_mat_3d.m); glUniformMatrix4fv(prg_game->uniform.projection, 1, false, projection_mat_3d.m);
glUniform3f(u_camera_pos, pos.x, pos.y, pos.z); glUniform3f(prg_game->uniform.camera_pos, pos.x, pos.y, pos.z);
glUniform2f(u_fade, RENDER_FADEOUT_NEAR, RENDER_FADEOUT_FAR); glUniform2f(prg_game->uniform.fade, RENDER_FADEOUT_NEAR, RENDER_FADEOUT_FAR);
} }
void render_set_view_2d() { void render_set_view_2d() {
@ -385,14 +696,14 @@ void render_set_view_2d() {
render_set_depth_write(false); render_set_depth_write(false);
render_set_model_mat(&mat4_identity()); render_set_model_mat(&mat4_identity());
glUniform3f(u_camera_pos, 0, 0, 0); glUniform3f(prg_game->uniform.camera_pos, 0, 0, 0);
glUniformMatrix4fv(u_view, 1, false, mat4_identity().m); glUniformMatrix4fv(prg_game->uniform.view, 1, false, mat4_identity().m);
glUniformMatrix4fv(u_projection, 1, false, projection_mat_2d.m); glUniformMatrix4fv(prg_game->uniform.projection, 1, false, projection_mat_2d.m);
} }
void render_set_model_mat(mat4_t *m) { void render_set_model_mat(mat4_t *m) {
render_flush(); render_flush();
glUniformMatrix4fv(u_model, 1, false, m->m); glUniformMatrix4fv(prg_game->uniform.model, 1, false, m->m);
} }
void render_set_depth_write(bool enabled) { void render_set_depth_write(bool enabled) {
@ -423,7 +734,7 @@ void render_set_depth_offset(float offset) {
void render_set_screen_position(vec2_t pos) { void render_set_screen_position(vec2_t pos) {
render_flush(); render_flush();
glUniform2f(u_screen, pos.x, -pos.y); glUniform2f(prg_game->uniform.screen, pos.x, -pos.y);
} }
void render_set_blend_mode(render_blend_mode_t new_mode) { void render_set_blend_mode(render_blend_mode_t new_mode) {

View file

@ -57,7 +57,7 @@ void system_reset_cycle_time() {
} }
void system_resize(vec2i_t size) { void system_resize(vec2i_t size) {
render_resize(size); render_set_screen_size(size);
} }
double system_time_scale_get() { double system_time_scale_get() {

View file

@ -399,6 +399,8 @@ save_t save = {
.ui_scale = 0, .ui_scale = 0,
.show_fps = false, .show_fps = false,
.fullscreen = false, .fullscreen = false,
.screen_res = 0,
.post_effect = 0,
.has_rapier_class = true, // for testing; should be false in prod .has_rapier_class = true, // for testing; should be false in prod
.has_bonus_circuts = true, // for testing; should be false in prod .has_bonus_circuts = true, // for testing; should be false in prod
@ -488,6 +490,20 @@ static int global_textures_len = 0;
static void *global_mem_mark = 0; static void *global_mem_mark = 0;
void game_init() { void game_init() {
if (file_exists("save.dat")) {
uint32_t size;
save_t *save_file = (save_t *)file_load("save.dat", &size);
if (size == sizeof(save_t) && save_file->magic == SAVE_DATA_MAGIC) {
printf("load save data success\n");
memcpy(&save, save_file, sizeof(save_t));
}
mem_temp_free(save_file);
}
platform_set_fullscreen(save.fullscreen);
render_set_resolution(save.screen_res);
render_set_post_effect(save.post_effect);
srand((int)(platform_now() * 100)); srand((int)(platform_now() * 100));
ui_load(); ui_load();
@ -574,16 +590,6 @@ void game_init() {
game_set_scene(GAME_SCENE_INTRO); game_set_scene(GAME_SCENE_INTRO);
if (file_exists("save.dat")) {
uint32_t size;
save_t *save_file = (save_t *)file_load("save.dat", &size);
if (size == sizeof(save_t) && save_file->magic == SAVE_DATA_MAGIC) {
printf("load save data success\n");
memcpy(&save, save_file, sizeof(save_t));
}
mem_temp_free(save_file);
}
} }
void game_set_scene(game_scene_t scene) { void game_set_scene(game_scene_t scene) {

View file

@ -242,6 +242,8 @@ typedef struct {
uint8_t ui_scale; uint8_t ui_scale;
bool show_fps; bool show_fps;
bool fullscreen; bool fullscreen;
int screen_res;
int post_effect;
uint32_t has_rapier_class; uint32_t has_rapier_class;
uint32_t has_bonus_circuts; uint32_t has_bonus_circuts;

View file

@ -156,8 +156,22 @@ static void toggle_ui_scale(menu_t *menu, int data) {
save.is_dirty = true; save.is_dirty = true;
} }
static void toggle_res(menu_t *menu, int data) {
render_set_resolution(data);
save.screen_res = data;
save.is_dirty = true;
}
static void toggle_post(menu_t *menu, int data) {
render_set_post_effect(data);
save.post_effect = data;
save.is_dirty = true;
}
static const char *opts_off_on[] = {"OFF", "ON"}; static const char *opts_off_on[] = {"OFF", "ON"};
static const char *opts_ui_sizes[] = {"AUTO", "1X", "2X", "3X", "4X"}; static const char *opts_ui_sizes[] = {"AUTO", "1X", "2X", "3X", "4X"};
static const char *opts_res[] = {"NATIVE", "240P", "480P"};
static const char *opts_post[] = {"NONE", "CRT EFFECT"};
static void page_options_video_init(menu_t *menu) { static void page_options_video_init(menu_t *menu) {
menu_page_t *page = menu_push(menu, "VIDEO OPTIONS", NULL); menu_page_t *page = menu_push(menu, "VIDEO OPTIONS", NULL);
@ -173,6 +187,8 @@ static void page_options_video_init(menu_t *menu) {
#endif #endif
menu_page_add_toggle(page, save.ui_scale, "UI SCALE", opts_ui_sizes, len(opts_ui_sizes), toggle_ui_scale); menu_page_add_toggle(page, save.ui_scale, "UI SCALE", opts_ui_sizes, len(opts_ui_sizes), toggle_ui_scale);
menu_page_add_toggle(page, save.show_fps, "SHOW FPS", opts_off_on, len(opts_off_on), toggle_show_fps); menu_page_add_toggle(page, save.show_fps, "SHOW FPS", opts_off_on, len(opts_off_on), toggle_show_fps);
menu_page_add_toggle(page, save.screen_res, "SCREEN RESOLUTION", opts_res, len(opts_res), toggle_res);
menu_page_add_toggle(page, save.post_effect, "POST PROCESSING", opts_post, len(opts_post), toggle_post);
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------