/* * Copyright (C) 2023 Christopher J. Howard * * This file is part of Antkeeper source code. * * Antkeeper source code is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Antkeeper source code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Antkeeper source code. If not, see . */ #include "game/graphics.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace graphics { static void reroute_framebuffers(::game& ctx); static void rebuild_hdr_framebuffer(::game& ctx) { // Construct HDR framebuffer sampler auto hdr_sampler = std::make_shared ( gl::sampler_filter::linear, gl::sampler_filter::linear, gl::sampler_mipmap_mode::linear, gl::sampler_address_mode::clamp_to_edge, gl::sampler_address_mode::clamp_to_edge ); // Construct HDR framebuffer color texture ctx.hdr_color_texture = std::make_shared ( std::make_shared ( std::make_shared ( gl::format::r32g32b32_sfloat, ctx.render_resolution.x(), ctx.render_resolution.y() ) ), hdr_sampler ); // Construct HDR framebuffer depth texture ctx.hdr_depth_texture = std::make_shared ( std::make_shared ( std::make_shared ( gl::format::d32_sfloat_s8_uint, ctx.render_resolution.x(), ctx.render_resolution.y() ) ), hdr_sampler ); // Construct HDR framebuffer const gl::framebuffer_attachment hdr_attachments[2] = { { gl::color_attachment_bit, ctx.hdr_color_texture->get_image_view(), 0 }, { gl::depth_stencil_attachment_bits, ctx.hdr_depth_texture->get_image_view(), 0 } }; ctx.hdr_framebuffer = std::make_shared(hdr_attachments, ctx.render_resolution.x(), ctx.render_resolution.y()); } static void rebuild_ldr_framebuffers(::game& ctx) { auto ldr_sampler = std::make_shared ( gl::sampler_filter::linear, gl::sampler_filter::linear, gl::sampler_mipmap_mode::linear, gl::sampler_address_mode::clamp_to_edge, gl::sampler_address_mode::clamp_to_edge ); // Construct LDR framebuffer A color texture ctx.ldr_color_texture_a = std::make_shared ( std::make_shared ( std::make_shared ( gl::format::r8g8b8_unorm, ctx.render_resolution.x(), ctx.render_resolution.y() ) ), ldr_sampler ); // Construct LDR framebuffer A const gl::framebuffer_attachment ldr_attachments_a[1] = { { gl::color_attachment_bit, ctx.ldr_color_texture_a->get_image_view(), 0 } }; ctx.ldr_framebuffer_a = std::make_shared(ldr_attachments_a, ctx.render_resolution.x(), ctx.render_resolution.y()); // Construct LDR framebuffer B color texture ctx.ldr_color_texture_b = std::make_shared ( std::make_shared ( std::make_shared ( gl::format::r8g8b8_unorm, ctx.render_resolution.x(), ctx.render_resolution.y() ) ), ldr_sampler ); // Construct LDR framebuffer B const gl::framebuffer_attachment ldr_attachments_b[1] = { { gl::color_attachment_bit, ctx.ldr_color_texture_b->get_image_view(), 0 } }; ctx.ldr_framebuffer_b = std::make_shared(ldr_attachments_b, ctx.render_resolution.x(), ctx.render_resolution.y()); } void rebuild_shadow_framebuffer(::game& ctx) { // Construct shadow map sampler auto shadow_sampler = std::make_shared ( gl::sampler_filter::linear, gl::sampler_filter::linear, gl::sampler_mipmap_mode::linear, gl::sampler_address_mode::clamp_to_border, gl::sampler_address_mode::clamp_to_border, gl::sampler_address_mode::clamp_to_border, 0.0f, 0.0f, true, gl::compare_op::greater, -1000.0f, 1000.0f, std::array{0.0f, 0.0f, 0.0f, 0.0f} ); // Construct shadow map framebuffer depth texture ctx.shadow_map_depth_texture = std::make_shared ( std::make_shared ( std::make_shared ( gl::format::d32_sfloat, ctx.shadow_map_resolution, ctx.shadow_map_resolution ) ), shadow_sampler ); // Construct shadow map framebuffer const gl::framebuffer_attachment shadow_map_attachments[1] = { { gl::depth_attachment_bit, ctx.shadow_map_depth_texture->get_image_view(), 0 } }; ctx.shadow_map_framebuffer = std::make_shared(shadow_map_attachments, ctx.shadow_map_resolution, ctx.shadow_map_resolution); } void create_framebuffers(::game& ctx) { debug::log_trace("Creating framebuffers..."); // Calculate render resolution const math::ivec2& viewport_size = ctx.window->get_viewport_size(); ctx.render_resolution = {static_cast(viewport_size.x() * ctx.render_scale + 0.5f), static_cast(viewport_size.y() * ctx.render_scale + 0.5f)}; rebuild_hdr_framebuffer(ctx); rebuild_ldr_framebuffers(ctx); rebuild_shadow_framebuffer(ctx); debug::log_trace("Created framebuffers"); } void destroy_framebuffers(::game& ctx) { debug::log_trace("Destroying framebuffers..."); // Delete HDR framebuffer and its attachments ctx.hdr_framebuffer.reset(); ctx.hdr_color_texture.reset(); ctx.hdr_depth_texture.reset(); // Delete LDR framebuffers and attachments ctx.ldr_framebuffer_a.reset(); ctx.ldr_color_texture_a.reset(); ctx.ldr_framebuffer_b.reset(); ctx.ldr_color_texture_b.reset(); // Delete shadow map framebuffer and its attachments ctx.shadow_map_framebuffer.reset(); ctx.shadow_map_depth_texture.reset(); debug::log_trace("Destroyed framebuffers"); } void change_render_resolution(::game& ctx, float scale) { // Recalculate render resolution const math::ivec2& viewport_size = ctx.window->get_viewport_size(); const auto render_resolution = math::ivec2{static_cast(viewport_size.x() * scale + 0.5f), static_cast(viewport_size.y() * scale + 0.5f)}; if (ctx.render_resolution == render_resolution) { return; } debug::log_trace("Changing render resolution to {}...", scale); // Update render resolution scale ctx.render_scale = scale; ctx.render_resolution = render_resolution; rebuild_hdr_framebuffer(ctx); rebuild_ldr_framebuffers(ctx); // Enable or disable resample pass if (viewport_size.x() != ctx.render_resolution.x() || viewport_size.y() != ctx.render_resolution.y()) { ctx.resample_pass->set_enabled(true); debug::log_debug("Resample pass enabled"); } else { ctx.resample_pass->set_enabled(false); debug::log_debug("Resample pass disabled"); } reroute_framebuffers(ctx); debug::log_trace("Changed render resolution to {}", scale); } void save_screenshot(::game& ctx) { // Determine timestamped screenshot filename const auto time = std::chrono::floor(std::chrono::system_clock::now()); const std::string screenshot_filename = std::format("{0}-{1:%Y%m%d}T{1:%H%M%S}Z.png", config::application_name, time); // Determine path to screenshot file std::filesystem::path screenshot_filepath = ctx.screenshots_path / screenshot_filename; std::string screenshot_filepath_string = screenshot_filepath.string(); debug::log_debug("Saving screenshot to \"{}\"...", screenshot_filepath_string); // Get viewport dimensions const auto& viewport_size = ctx.window->get_viewport_size(); // Allocate screenshot pixel data buffer std::unique_ptr frame = std::make_unique(viewport_size.x() * viewport_size.y() * 3); // Read pixel data from backbuffer into pixel data buffer glReadBuffer(GL_BACK); glReadPixels(0, 0, viewport_size.x(), viewport_size.y(), GL_RGB, GL_UNSIGNED_BYTE, frame.get()); // Write screenshot file in separate thread std::thread ( [frame = std::move(frame), w = viewport_size.x(), h = viewport_size.y(), path = std::move(screenshot_filepath_string)] { stbi_flip_vertically_on_write(1); stbi_write_png(path.c_str(), w, h, 3, frame.get(), w * 3); debug::log_debug("Saved screenshot to \"{}\"", path); } ).detach(); } void select_anti_aliasing_method(::game& ctx, render::anti_aliasing_method method) { // Switch AA method switch (method) { // Off case render::anti_aliasing_method::none: debug::log_debug("Anti-aliasing disabled"); reroute_framebuffers(ctx); break; // FXAA case render::anti_aliasing_method::fxaa: debug::log_debug("Anti-aliasing enabled (FXAA)"); reroute_framebuffers(ctx); break; } // Update AA method setting ctx.anti_aliasing_method = method; } void reroute_framebuffers(::game& ctx) { if (ctx.resample_pass->is_enabled()) { ctx.common_final_pass->set_framebuffer(ctx.ldr_framebuffer_a.get()); } else { ctx.common_final_pass->set_framebuffer(nullptr); } ctx.sky_pass->set_framebuffer(ctx.hdr_framebuffer.get()); ctx.surface_material_pass->set_framebuffer(ctx.hdr_framebuffer.get()); ctx.bloom_pass->set_source_texture(ctx.hdr_color_texture); ctx.common_final_pass->set_color_texture(ctx.hdr_color_texture); ctx.common_final_pass->set_bloom_texture(ctx.bloom_pass->get_bloom_texture()); } } // namespace graphics