From bd57f345ed9dbed1d81683e48199626de2ea9044 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Fri, 27 Jun 2025 10:18:39 -0700 Subject: Restructure project --- src/util/geometry.c | 44 +++++++ src/util/ibl.c | 328 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/util/shader.c | 136 ++++++++++++++++++++++ src/util/skyquad.c | 161 ++++++++++++++++++++++++++ 4 files changed, 669 insertions(+) create mode 100644 src/util/geometry.c create mode 100644 src/util/ibl.c create mode 100644 src/util/shader.c create mode 100644 src/util/skyquad.c (limited to 'src/util') diff --git a/src/util/geometry.c b/src/util/geometry.c new file mode 100644 index 0000000..afe0109 --- /dev/null +++ b/src/util/geometry.c @@ -0,0 +1,44 @@ +#include + +#include + +static void make_quad_11_positions(vec2 positions[4]) { + positions[0] = vec2_make(-1, +1); + positions[1] = vec2_make(-1, -1); + positions[2] = vec2_make(+1, +1); + positions[3] = vec2_make(+1, -1); +} + +static void make_quad_01_positions(vec2 positions[4]) { + positions[0] = vec2_make(0, 0); + positions[1] = vec2_make(1, 0); + positions[2] = vec2_make(1, 1); + positions[3] = vec2_make(0, 1); +} + +static GeometryDesc make_quad_desc(vec2 positions[4]) { + GeometryDesc desc = (GeometryDesc){0}; + desc.positions2d.data = positions; + desc.positions2d.size_bytes = 4 * sizeof(vec2); + desc.num_verts = 4; + desc.type = TriangleStrip; + return desc; +} + +Geometry* gfx_make_quad_11(GfxCore* gfxcore) { + assert(gfxcore); + + vec2 positions[4]; + make_quad_11_positions(positions); + const GeometryDesc geometry_desc = make_quad_desc(positions); + return gfx_make_geometry(gfxcore, &geometry_desc); +} + +Geometry* gfx_make_quad_01(GfxCore* gfxcore) { + assert(gfxcore); + + vec2 positions[4]; + make_quad_01_positions(positions); + const GeometryDesc geometry_desc = make_quad_desc(positions); + return gfx_make_geometry(gfxcore, &geometry_desc); +} diff --git a/src/util/ibl.c b/src/util/ibl.c new file mode 100644 index 0000000..5a79990 --- /dev/null +++ b/src/util/ibl.c @@ -0,0 +1,328 @@ +#include + +#include +#include +#include +#include + +#include +#include + +typedef struct IBL { + Geometry* quad; + ShaderProgram* brdf_integration_map_shader; + ShaderProgram* irradiance_map_shader; + ShaderProgram* prefiltered_environment_map_shader; + Texture* brdf_integration_map; + FrameBuffer* framebuffer; + mat4 rotations[6]; +} IBL; + +static const CubemapFace faces[6] = { + CubemapFacePosX, // Right. + CubemapFaceNegX, // Left. + CubemapFacePosY, // Up. + CubemapFaceNegY, // Down. + CubemapFacePosZ, // Back. + CubemapFaceNegZ, // Front. +}; + +static const float flips[6] = { + -1.0f, // Right. + -1.0f, // Left. + +1.0f, // Up. + +1.0f, // Down. + -1.0f, // Back. + -1.0f, // Front. +}; + +IBL* gfx_make_ibl(GfxCore* gfxcore) { + assert(gfxcore); + + IBL* ibl = calloc(1, sizeof(IBL)); + if (!ibl) { + return 0; + } + + if (!(ibl->quad = gfx_make_quad_11(gfxcore))) { + goto cleanup; + } + + // We only need the BRDF integration once since we are caching the map, but + // compiling the shader up front may lead to fewer surprises. Not that the + // shader is fully compiled up front anyway, since the driver will typically + // defer full compilation to the first draw call. + if (!(ibl->brdf_integration_map_shader = + gfx_make_brdf_integration_map_shader(gfxcore))) { + goto cleanup; + } + + if (!(ibl->irradiance_map_shader = gfx_make_irradiance_map_shader(gfxcore))) { + goto cleanup; + } + + if (!(ibl->prefiltered_environment_map_shader = + gfx_make_prefiltered_environment_map_shader(gfxcore))) { + goto cleanup; + } + + // Create an empty framebuffer for now. Will attach the colour buffer later + // as we render the faces of the cube. + if (!(ibl->framebuffer = gfx_make_framebuffer( + gfxcore, + &(FrameBufferDesc){ + .colour = + (FrameBufferAttachment){.type = FrameBufferNoAttachment}, + .depth = (FrameBufferAttachment){ + .type = FrameBufferNoAttachment}}))) { + goto cleanup; + } + + // TODO: Debug the camera rotations. Irradiance debug output should appear + // just like the input cubemap. + + // Right. + ibl->rotations[0] = mat4_lookat( + /*position=*/vec3_make(0, 0, 0), + /*target=*/vec3_make(1, 0, 0), + /*up=*/vec3_make(0, 1, 0)); + // Left. + ibl->rotations[1] = mat4_lookat( + /*position=*/vec3_make(0, 0, 0), + /*target=*/vec3_make(-1, 0, 0), + /*up=*/vec3_make(0, 1, 0)); + // Up. + ibl->rotations[2] = mat4_lookat( + /*position=*/vec3_make(0, 0, 0), + /*target=*/vec3_make(0, 1, 0), + /*up=*/vec3_make(0, 0, 1)); + // Down. + ibl->rotations[3] = mat4_lookat( + /*position=*/vec3_make(0, 0, 0), + /*target=*/vec3_make(0, -1, 0), + /*up=*/vec3_make(0, 0, -1)); + // Back. + ibl->rotations[4] = mat4_lookat( + /*position=*/vec3_make(0, 0, 0), + /*target=*/vec3_make(0, 0, 1), + /*up=*/vec3_make(0, 1, 0)); + // Front. + ibl->rotations[5] = mat4_lookat( + /*position=*/vec3_make(0, 0, 0), + /*target=*/vec3_make(0, 0, -1), + /*up=*/vec3_make(0, 1, 0)); + + return ibl; + +cleanup: + gfx_destroy_ibl(gfxcore, &ibl); + return 0; +} + +void gfx_destroy_ibl(GfxCore* gfxcore, IBL** ibl) { + if (!ibl) { + return; + } + if ((*ibl)->quad) { + gfx_destroy_geometry(gfxcore, &(*ibl)->quad); + } + if ((*ibl)->brdf_integration_map_shader) { + gfx_destroy_shader_program(gfxcore, &(*ibl)->brdf_integration_map_shader); + } + if ((*ibl)->irradiance_map_shader) { + gfx_destroy_shader_program(gfxcore, &(*ibl)->irradiance_map_shader); + } + if ((*ibl)->prefiltered_environment_map_shader) { + gfx_destroy_shader_program( + gfxcore, &(*ibl)->prefiltered_environment_map_shader); + } + if ((*ibl)->brdf_integration_map) { + gfx_destroy_texture(gfxcore, &(*ibl)->brdf_integration_map); + } + if ((*ibl)->framebuffer) { + gfx_destroy_framebuffer(gfxcore, &(*ibl)->framebuffer); + } + free(*ibl); + *ibl = 0; +} + +Texture* gfx_make_brdf_integration_map( + IBL* ibl, GfxCore* gfxcore, int width, int height) { + assert(ibl); + assert(gfxcore); + + if (ibl->brdf_integration_map) { + return ibl->brdf_integration_map; + } + + bool success = false; + + if (!(ibl->brdf_integration_map = gfx_make_texture( + gfxcore, &(TextureDesc){ + .width = width, + .height = height, + .depth = 1, + .dimension = Texture2D, + .format = TextureRG16F, + .filtering = LinearFiltering, + .wrap = ClampToEdge, + .mipmaps = false}))) { + goto cleanup; + } + + gfx_activate_framebuffer(ibl->framebuffer); + gfx_framebuffer_set_viewport(ibl->framebuffer, 0, 0, width, height); + gfx_activate_shader_program(ibl->brdf_integration_map_shader); + if (!gfx_framebuffer_attach_colour( + ibl->framebuffer, &(FrameBufferAttachment){ + .type = FrameBufferTexture, + .texture.texture = ibl->brdf_integration_map, + .texture.mip_level = 0})) { + goto cleanup; + } + gfx_render_geometry(ibl->quad); + + success = true; + +cleanup: + gfx_deactivate_shader_program(ibl->brdf_integration_map_shader); + gfx_deactivate_framebuffer(ibl->framebuffer); + if (!success && ibl->brdf_integration_map) { + gfx_destroy_texture(gfxcore, &ibl->brdf_integration_map); + return 0; + } else { + return ibl->brdf_integration_map; + } +} + +Texture* gfx_make_irradiance_map( + IBL* ibl, GfxCore* gfxcore, const Texture* environment_map, int width, + int height) { + assert(ibl); + assert(gfxcore); + assert(environment_map); + + bool success = false; + + Texture* irradiance_map = 0; + + // TODO: Could define colour-renderable texture formats separately to make + // framebuffer creation less error-prone. Or, at the very least, validate the + // choice at runtime. + // + // Make sure to use a float colour format to avoid [0,1] clamping when the + // irradiance values are computed! + if (!(irradiance_map = gfx_make_texture( + gfxcore, &(TextureDesc){ + .width = width, + .height = height, + .depth = 1, + .dimension = TextureCubeMap, + .format = TextureR11G11B10F, + .filtering = LinearFiltering, + .mipmaps = false}))) { + goto cleanup; + } + + gfx_activate_framebuffer(ibl->framebuffer); + gfx_framebuffer_set_viewport(ibl->framebuffer, 0, 0, width, height); + gfx_activate_shader_program(ibl->irradiance_map_shader); + gfx_set_texture_uniform(ibl->irradiance_map_shader, "Sky", environment_map); + for (int i = 0; i < 6; ++i) { + if (!gfx_framebuffer_attach_colour( + ibl->framebuffer, &(FrameBufferAttachment){ + .type = FrameBufferCubemapTexture, + .cubemap.face = faces[i], + .cubemap.texture = irradiance_map})) { + goto cleanup; + } + gfx_set_float_uniform(ibl->irradiance_map_shader, "Flip", flips[i]); + gfx_set_mat4_uniform( + ibl->irradiance_map_shader, "CameraRotation", &ibl->rotations[i]); + gfx_apply_uniforms(ibl->irradiance_map_shader); + gfx_render_geometry(ibl->quad); + } + + success = true; + +cleanup: + gfx_deactivate_shader_program(ibl->irradiance_map_shader); + gfx_deactivate_framebuffer(ibl->framebuffer); + if (!success && irradiance_map) { + gfx_destroy_texture(gfxcore, &irradiance_map); + return 0; + } else { + return irradiance_map; + } +} + +Texture* gfx_make_prefiltered_environment_map( + IBL* ibl, GfxCore* gfxcore, const Texture* environment_map, int width, + int height, int* max_mip_level) { + assert(ibl); + assert(gfxcore); + assert(environment_map); + assert(max_mip_level); + + bool success = false; + + Texture* prefiltered_env_map = 0; + + if (!(prefiltered_env_map = gfx_make_texture( + gfxcore, &(TextureDesc){ + .width = width, + .height = height, + .depth = 1, + .dimension = TextureCubeMap, + .format = TextureR11G11B10F, + .filtering = LinearFiltering, + .mipmaps = true}))) { + goto cleanup; + } + + gfx_activate_framebuffer(ibl->framebuffer); + gfx_activate_shader_program(ibl->prefiltered_environment_map_shader); + gfx_set_texture_uniform( + ibl->prefiltered_environment_map_shader, "Sky", environment_map); + const int max_mip = (int)(rlog2(min(width, height))); + for (int mip = 0; mip <= max_mip; ++mip) { + const int mip_width = width >> mip; + const int mip_height = height >> mip; + const float roughness = (float)mip / (float)(max_mip); + gfx_framebuffer_set_viewport(ibl->framebuffer, 0, 0, mip_width, mip_height); + gfx_set_float_uniform( + ibl->prefiltered_environment_map_shader, "Roughness", roughness); + + for (int i = 0; i < 6; ++i) { + if (!gfx_framebuffer_attach_colour( + ibl->framebuffer, &(FrameBufferAttachment){ + .type = FrameBufferCubemapTexture, + .cubemap.face = faces[i], + .cubemap.mip_level = mip, + .cubemap.texture = prefiltered_env_map})) { + goto cleanup; + } + gfx_set_float_uniform( + ibl->prefiltered_environment_map_shader, "Flip", flips[i]); + gfx_set_mat4_uniform( + ibl->prefiltered_environment_map_shader, "CameraRotation", + &ibl->rotations[i]); + gfx_apply_uniforms(ibl->prefiltered_environment_map_shader); + gfx_render_geometry(ibl->quad); + } + } + + *max_mip_level = max_mip; + + success = true; + +cleanup: + gfx_deactivate_shader_program(ibl->prefiltered_environment_map_shader); + gfx_deactivate_framebuffer(ibl->framebuffer); + if (!success && prefiltered_env_map) { + gfx_destroy_texture(gfxcore, &prefiltered_env_map); + return 0; + } else { + return prefiltered_env_map; + } +} diff --git a/src/util/shader.c b/src/util/shader.c new file mode 100644 index 0000000..f5c22cc --- /dev/null +++ b/src/util/shader.c @@ -0,0 +1,136 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static ShaderProgram* make_shader_program( + GfxCore* gfxcore, const char* vert_source, const char* frag_source, + const ShaderCompilerDefine* defines, size_t num_defines) { + assert(gfxcore); + assert(vert_source); + assert(frag_source); + + Shader* vert = 0; + Shader* frag = 0; + + ShaderDesc vertex_shader_desc = { + .code = vert_source, .type = VertexShader, .num_defines = num_defines}; + ShaderDesc fragment_shader_desc = { + .code = frag_source, .type = FragmentShader, .num_defines = num_defines}; + if (num_defines > 0) { + memcpy( + vertex_shader_desc.defines, defines, + num_defines * sizeof(ShaderCompilerDefine)); + memcpy( + fragment_shader_desc.defines, defines, + num_defines * sizeof(ShaderCompilerDefine)); + } + vert = gfx_make_shader(gfxcore, &vertex_shader_desc); + if (!vert) { + goto cleanup; + } + frag = gfx_make_shader(gfxcore, &fragment_shader_desc); + if (!frag) { + goto cleanup; + } + + ShaderProgramDesc shader_program_desc = { + .vertex_shader = vert, .fragment_shader = frag}; + ShaderProgram* prog = gfx_make_shader_program(gfxcore, &shader_program_desc); + if (!prog) { + goto cleanup; + } + return prog; + +cleanup: + if (vert) { + gfx_destroy_shader(gfxcore, &vert); + } + if (frag) { + gfx_destroy_shader(gfxcore, &frag); + } + return 0; +} + +ShaderProgram* gfx_make_brdf_integration_map_shader(GfxCore* gfxcore) { + return make_shader_program( + gfxcore, quad_vert, brdf_integration_map_frag, 0, 0); +} + +ShaderProgram* gfx_make_cook_torrance_shader(GfxCore* gfxcore) { + return make_shader_program( + gfxcore, cook_torrance_vert, cook_torrance_frag, 0, 0); +} + +ShaderProgram* gfx_make_cook_torrance_shader_perm( + GfxCore* gfxcore, const ShaderCompilerDefine* defines, size_t num_defines) { + return make_shader_program( + gfxcore, cook_torrance_vert, cook_torrance_frag, defines, num_defines); +} + +ShaderProgram* gfx_make_immediate_mode_shader(GfxCore* gfxcore) { + return make_shader_program( + gfxcore, immediate_mode_vert, immediate_mode_frag, 0, 0); +} + +ShaderProgram* gfx_make_irradiance_map_shader(GfxCore* gfxcore) { + return make_shader_program( + gfxcore, cubemap_filtering_vert, irradiance_map_frag, 0, 0); +} + +ShaderProgram* gfx_make_prefiltered_environment_map_shader(GfxCore* gfxcore) { + return make_shader_program( + gfxcore, cubemap_filtering_vert, prefiltered_environment_map_frag, 0, 0); +} + +ShaderProgram* gfx_make_debug3d_shader(GfxCore* gfxcore) { + return make_shader_program(gfxcore, debug3d_vert, debug3d_frag, 0, 0); +} + +ShaderProgram* gfx_make_skyquad_shader(GfxCore* gfxcore) { + return make_shader_program(gfxcore, skyquad_vert, skyquad_frag, 0, 0); +} + +ShaderProgram* gfx_make_view_normal_mapped_normals_shader(GfxCore* gfxcore) { + return make_shader_program( + gfxcore, view_normal_mapped_normals_vert, view_normal_mapped_normals_frag, + 0, 0); +} + +ShaderProgram* gfx_make_view_normals_shader(GfxCore* gfxcore) { + return make_shader_program( + gfxcore, view_normals_vert, view_normals_frag, 0, 0); +} + +ShaderProgram* gfx_make_view_tangents_shader(GfxCore* gfxcore) { + return make_shader_program( + gfxcore, view_tangents_vert, view_tangents_frag, 0, 0); +} + +ShaderProgram* gfx_make_view_texture_shader(GfxCore* gfxcore) { + return make_shader_program( + gfxcore, view_texture_vert, view_texture_frag, 0, 0); +} diff --git a/src/util/skyquad.c b/src/util/skyquad.c new file mode 100644 index 0000000..08fa044 --- /dev/null +++ b/src/util/skyquad.c @@ -0,0 +1,161 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +SceneObject* gfx_make_skyquad(GfxCore* gfxcore, const Texture* texture) { + assert(gfxcore); + assert(texture); + + ShaderProgram* shader = 0; + Geometry* geometry = 0; + Material* material = 0; + Mesh* mesh = 0; + SceneObject* object = 0; + + shader = gfx_make_skyquad_shader(gfxcore); + if (!shader) { + goto cleanup; + } + + geometry = gfx_make_quad_11(gfxcore); + if (!geometry) { + goto cleanup; + } + + MaterialDesc material_desc = (MaterialDesc){0}; + material_desc.uniforms[0] = (ShaderUniform){ + .type = UniformTexture, + .value.texture = texture, + .name = sstring_make("Skyquad")}; + material_desc.num_uniforms = 1; + material = gfx_make_material(&material_desc); + if (!material) { + goto cleanup; + } + + MeshDesc mesh_desc = (MeshDesc){0}; + mesh_desc.geometry = geometry; + mesh_desc.material = material; + mesh_desc.shader = shader; + mesh = gfx_make_mesh(&mesh_desc); + if (!mesh) { + goto cleanup; + } + + object = gfx_make_object(&(ObjectDesc){.num_meshes = 1, .meshes = {mesh}}); + if (!object) { + goto cleanup; + } + + return object; + +cleanup: + if (shader) { + gfx_destroy_shader_program(gfxcore, &shader); + } + if (geometry) { + gfx_destroy_geometry(gfxcore, &geometry); + } + if (material) { + gfx_destroy_material(&material); + } + if (mesh) { + gfx_destroy_mesh(&mesh); + } + if (object) { + gfx_destroy_object(&object); + } + return false; +} + +/// Create an environment light node. +static SceneNode* make_environment_light( + SceneNode* root, const Texture* environment_map) { + assert(root); + assert(environment_map); + + Light* light = 0; + SceneNode* light_node = 0; + + light = gfx_make_light(&(LightDesc){ + .type = EnvironmentLightType, + .light = {(EnvironmentLightDesc){.environment_map = environment_map}}}); + if (!light) { + goto cleanup; + } + + light_node = gfx_make_light_node(light); + if (!light_node) { + goto cleanup; + } + gfx_set_node_parent(light_node, root); + + return light_node; + +cleanup: + if (light) { + gfx_destroy_light(&light); + } + if (light_node) { + gfx_destroy_node(&light_node); + } + return 0; +} + +SceneNode* gfx_setup_skyquad( + GfxCore* gfxcore, SceneNode* root, const Texture* environment_map) { + assert(gfxcore); + assert(root); + assert(environment_map); + + SceneObject* skyquad_object = 0; + SceneNode* object_node = 0; + SceneNode* light_node = 0; + + // Create the skyquad object. + skyquad_object = gfx_make_skyquad(gfxcore, environment_map); + if (!skyquad_object) { + goto cleanup; + } + + // Create an object node to render the skyquad in the background. + object_node = gfx_make_object_node(skyquad_object); + if (!object_node) { + goto cleanup; + } + gfx_set_node_parent(object_node, root); + + // Create an environment light node under which to root objects affected by + // the skyquad. + light_node = make_environment_light(root, environment_map); + if (!light_node) { + goto cleanup; + } + + return light_node; + +cleanup: + if (skyquad_object) { + gfx_destroy_object(&skyquad_object); + } + if (object_node) { + gfx_destroy_node(&object_node); + } + if (light_node) { + gfx_destroy_node(&light_node); + } + return 0; +} -- cgit v1.2.3