diff options
| author | hachem <im@hachem.wtf> | 2025-08-22 00:22:12 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2025-08-22 00:22:12 +0200 |
| commit | 0d4f5f665f2e4b735fb4f978acc6c8d4a16fcb5d (patch) | |
| tree | dad79333f7026e19732fae0e35ca9dff8ea7ade4 /src | |
| parent | d8ba57f6d45b9467520377ffcc1f453154d77430 (diff) | |
feat: frame exporting
Diffstat (limited to 'src')
| -rw-r--r-- | src/engine/engine.cpp | 154 | ||||
| -rw-r--r-- | src/engine/engine.h | 1 | ||||
| -rw-r--r-- | src/platform/opengl/opengl_framebuffer.cpp | 237 | ||||
| -rw-r--r-- | src/platform/opengl/opengl_framebuffer.h | 35 | ||||
| -rw-r--r-- | src/platform/opengl/opengl_renderer_api.cpp | 6 | ||||
| -rw-r--r-- | src/platform/opengl/opengl_renderer_api.h | 4 | ||||
| -rw-r--r-- | src/platform/vulkan/vulkan_renderer_api.cpp | 5 | ||||
| -rw-r--r-- | src/platform/vulkan/vulkan_renderer_api.h | 2 | ||||
| -rw-r--r-- | src/rendering/framebuffer.cpp | 17 | ||||
| -rw-r--r-- | src/rendering/framebuffer.h | 65 | ||||
| -rw-r--r-- | src/rendering/renderer.h | 9 | ||||
| -rw-r--r-- | src/states/simulation_state.cpp | 48 |
12 files changed, 580 insertions, 3 deletions
diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index ef9b93d..3b5e320 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -3,6 +3,10 @@ #include <sstream> #include <GLFW/glfw3.h> +#include <glad/glad.h> + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" #include "engine.h" #include "core/log.h" @@ -325,7 +329,7 @@ namespace Donut for (size_t i = 0; i < m_objects.size(); ++i) { const auto& obj = m_objects[i]; - DONUT_INFO("Object {}: Pos=({:.2e}, {:.2e}, {:.2e}), Radius={:.2e}, Mass={:.2e}, Color=({:.2f}, {:.2f}, {:.2f})", + DONUT_INFO("Object {}: Pos=({}, {}, {}), Radius={}, Mass={}, Color=({}, {}, {})", i, obj.m_pos_radius.x, obj.m_pos_radius.y, obj.m_pos_radius.z, obj.m_pos_radius.w, @@ -333,12 +337,156 @@ namespace Donut obj.m_color.x, obj.m_color.y, obj.m_color.z ); } - DONUT_INFO("Camera position: ({:.2e}, {:.2e}, {:.2e})", + DONUT_INFO("Camera position: ({}, {}, {})", m_camera.get_orbital_position().x, m_camera.get_orbital_position().y, m_camera.get_orbital_position().z ); - DONUT_INFO("Camera radius: {:.2e}", m_camera.get_orbital_radius()); + DONUT_INFO("Camera radius: {}", m_camera.get_orbital_radius()); DONUT_INFO("========================"); } + + void Engine::export_high_res_frame(const std::string& filename, int width, int height) + { + DONUT_INFO("Exporting high-resolution frame: {}x{} to {}", width, height, filename); + + if (width <= 0 || height <= 0) + { + DONUT_ERROR("Invalid dimensions for export: {}x{}", width, height); + return; + } + + if (filename.empty()) + { + DONUT_ERROR("Invalid filename for export"); + return; + } + + int original_width = m_width; + int original_height = m_height; + int original_compute_height = m_compute_height; + + m_width = width; + m_height = height; + m_compute_height = height; + + int compute_height = height; + int compute_width = (width * compute_height) / height; + + if (compute_width <= 0 || compute_height <= 0) + { + DONUT_ERROR("Invalid compute dimensions: {}x{}", compute_width, compute_height); + return; + } + + FramebufferSpecification fb_spec; + fb_spec.Width = width; + fb_spec.Height = height; + fb_spec.attachments = { FramebufferTextureFormat::RGBA8 }; + + auto high_res_framebuffer = Framebuffer::create(fb_spec); + if (!high_res_framebuffer) + { + DONUT_ERROR("Failed to create high-resolution framebuffer"); + return; + } + + high_res_framebuffer->bind(); + + RenderCommand::set_viewport(0, 0, width, height); + RenderCommand::clear(); + + auto high_res_texture = Texture2D::create(compute_width, compute_height); + if (!high_res_texture) + { + DONUT_ERROR("Failed to create high-resolution texture"); + return; + } + + high_res_texture->set_data(nullptr, compute_width * compute_height * 4); + m_compute_program->bind(); + + struct UBOData + { + glm::vec3 pos; float _pad0; + glm::vec3 right; float _pad1; + glm::vec3 up; float _pad2; + glm::vec3 forward; float _pad3; + float tan_half_fov; + float aspect; + bool moving; + int _pad4; + } data; + + glm::vec3 fwd = glm::normalize(m_camera.get_orbital_target() - m_camera.get_orbital_position()); + glm::vec3 up = glm::vec3(0, 1, 0); + glm::vec3 right = glm::normalize(glm::cross(fwd, up)); + up = glm::cross(right, fwd); + + data.pos = m_camera.get_orbital_position(); + data.right = right; + data.up = up; + data.forward = fwd; + data.tan_half_fov = static_cast<float>(tan(glm::radians(60.0f * 0.5f))); + data.aspect = static_cast<float>(compute_width) / static_cast<float>(compute_height); + data.moving = m_camera.is_dragging() || m_camera.is_panning(); + + m_camera_ubo->set_data(&data, sizeof(UBOData)); + m_camera_ubo->bind(1); + + upload_disk_ubo(); + upload_objects_ubo(m_objects); + upload_simulation_ubo(); + high_res_texture->bind_as_image(0, false); + + uint32_t groups_x = static_cast<uint32_t>(std::ceil(compute_width / 16.0f)); + uint32_t groups_y = static_cast<uint32_t>(std::ceil(compute_height / 16.0f)); + m_compute_program->dispatch(groups_x, groups_y, 1); + m_compute_program->memory_barrier(IMAGE_ACCESS_BARRIER_BIT); + + m_shader_program->bind(); + m_quad_vao->bind(); + + high_res_texture->bind(0); + m_shader_program->set_int("u_ScreenTexture", 0); + + RenderCommand::disable_depth_test(); + RenderCommand::draw_arrays(6); + RenderCommand::enable_depth_test(); + + std::vector<unsigned char> pixels(width * height * 4); + DONUT_INFO("Reading {} pixels from framebuffer...", width * height); + RenderCommand::read_pixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + + DONUT_INFO("Flipping image vertically..."); + std::vector<unsigned char> flipped_pixels(width * height * 4); + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + int src_index = (y * width + x) * 4; + int dst_index = ((height - 1 - y) * width + x) * 4; + flipped_pixels[dst_index + 0] = pixels[src_index + 0]; + flipped_pixels[dst_index + 1] = pixels[src_index + 1]; + flipped_pixels[dst_index + 2] = pixels[src_index + 2]; + flipped_pixels[dst_index + 3] = pixels[src_index + 3]; + } + } + + DONUT_INFO("Saving PNG file: {}...", filename); + int result = stbi_write_png(filename.c_str(), width, height, 4, flipped_pixels.data(), width * 4); + + if (result) + DONUT_INFO("Successfully exported high-resolution frame to: {}", filename); + else + DONUT_ERROR("Failed to export high-resolution frame to: {}", filename); + + high_res_framebuffer->unbind(); + + m_width = original_width; + m_height = original_height; + m_compute_height = original_compute_height; + + RenderCommand::set_viewport(0, 0, original_width, original_height); + } } diff --git a/src/engine/engine.h b/src/engine/engine.h index b0e06f3..fab895e 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -101,6 +101,7 @@ namespace Donut void set_early_exit_distance(float distance) { m_early_exit_distance = distance; } void load_objects_from_scene(const std::vector<Donut::Object>& objects); + void export_high_res_frame(const std::string& filename, int width = 4096, int height = 3072); void print_object_info() const; private: Ref<Shader> create_compute_program(const char* path); diff --git a/src/platform/opengl/opengl_framebuffer.cpp b/src/platform/opengl/opengl_framebuffer.cpp new file mode 100644 index 0000000..c5b8fe2 --- /dev/null +++ b/src/platform/opengl/opengl_framebuffer.cpp @@ -0,0 +1,237 @@ +#include "opengl_framebuffer.h" +#include "core/log.h" + +#include <glad/glad.h> + +namespace Donut +{ + namespace Utils + { + static GLenum texture_target(bool multisampled) + { + return multisampled ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; + } + + static void bind_texture(bool multisampled, uint32_t id) + { + glBindTexture(texture_target(multisampled), id); + } + + static void create_textures(bool multisampled, uint32_t* out_id, uint32_t count) + { + glCreateTextures(texture_target(multisampled), count, out_id); + } + + static void attach_color_texture(uint32_t id, int samples, GLenum internal_format, GLenum format, uint32_t width, uint32_t height, int index) + { + bool multisampled = samples > 1; + if (multisampled) + { + glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, internal_format, width, height, GL_FALSE); + } + else + { + glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, GL_UNSIGNED_BYTE, nullptr); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + index, texture_target(multisampled), id, 0); + } + + static void attach_depth_texture(uint32_t id, int samples, GLenum format, GLenum attachment_type, uint32_t width, uint32_t height) + { + bool multisampled = samples > 1; + if (multisampled) + { + glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, format, width, height, GL_FALSE); + } + else + { + glTexStorage2D(GL_TEXTURE_2D, 1, format, width, height); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + glFramebufferTexture2D(GL_FRAMEBUFFER, attachment_type, texture_target(multisampled), id, 0); + } + + static bool is_depth_format(FramebufferTextureFormat format) + { + switch (format) + { + case FramebufferTextureFormat::DEPTH24STENCIL8: return true; + } + return false; + } + + static GLenum donut_fb_texture_format_to_gl(FramebufferTextureFormat format) + { + switch (format) + { + case FramebufferTextureFormat::RGBA8: return GL_RGBA8; + case FramebufferTextureFormat::RED_INTEGER: return GL_RED_INTEGER; + } + + return 0; + } + } + + OpenGLFramebuffer::OpenGLFramebuffer(const FramebufferSpecification& spec) + : m_specification(spec) + { + for (auto spec : m_specification.attachments.attachments) + { + if (!Utils::is_depth_format(spec.texture_format)) + m_color_attachment_specifications.emplace_back(spec); + else + m_depth_attachment_specification = spec; + } + + invalidate(); + } + + OpenGLFramebuffer::~OpenGLFramebuffer() + { + glDeleteFramebuffers(1, &m_renderer_id); + glDeleteTextures(static_cast<GLsizei>(m_color_attachments.size()), m_color_attachments.data()); + glDeleteTextures(1, &m_depth_attachment); + } + + void OpenGLFramebuffer::invalidate() + { + if (m_renderer_id) + { + glDeleteFramebuffers(1, &m_renderer_id); + glDeleteTextures(static_cast<GLsizei>(m_color_attachments.size()), m_color_attachments.data()); + glDeleteTextures(1, &m_depth_attachment); + + m_color_attachments.clear(); + m_depth_attachment = 0; + } + + glCreateFramebuffers(1, &m_renderer_id); + glBindFramebuffer(GL_FRAMEBUFFER, m_renderer_id); + + bool multisample = m_specification.Samples > 1; + + if (m_color_attachment_specifications.size()) + { + m_color_attachments.resize(m_color_attachment_specifications.size()); + Utils::create_textures(multisample, m_color_attachments.data(), static_cast<uint32_t>(m_color_attachments.size())); + + for (size_t i = 0; i < m_color_attachments.size(); i++) + { + Utils::bind_texture(multisample, m_color_attachments[i]); + switch (m_color_attachment_specifications[i].texture_format) + { + case FramebufferTextureFormat::RGBA8: + Utils::attach_color_texture(m_color_attachments[i], m_specification.Samples, GL_RGBA8, GL_RGBA, m_specification.Width, m_specification.Height, static_cast<int>(i)); + break; + case FramebufferTextureFormat::RED_INTEGER: + Utils::attach_color_texture(m_color_attachments[i], m_specification.Samples, GL_R32I, GL_RED_INTEGER, m_specification.Width, m_specification.Height, static_cast<int>(i)); + break; + } + } + } + + if (m_depth_attachment_specification.texture_format != FramebufferTextureFormat::None) + { + Utils::create_textures(multisample, &m_depth_attachment, 1); + Utils::bind_texture(multisample, m_depth_attachment); + switch (m_depth_attachment_specification.texture_format) + { + case FramebufferTextureFormat::DEPTH24STENCIL8: + Utils::attach_depth_texture(m_depth_attachment, m_specification.Samples, GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL_ATTACHMENT, m_specification.Width, m_specification.Height); + break; + } + } + + if (m_color_attachments.size() > 1) + { + GLenum buffers[4] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 }; + glDrawBuffers(static_cast<GLsizei>(m_color_attachments.size()), buffers); + } + else if (m_color_attachments.empty()) + glDrawBuffer(GL_NONE); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + switch (status) + { + case GL_FRAMEBUFFER_UNDEFINED: + DONUT_ERROR("Framebuffer is undefined"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + DONUT_ERROR("Framebuffer has incomplete attachment"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + DONUT_ERROR("Framebuffer is missing attachment"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: + DONUT_ERROR("Framebuffer has incomplete draw buffer"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: + DONUT_ERROR("Framebuffer has incomplete read buffer"); + break; + case GL_FRAMEBUFFER_UNSUPPORTED: + DONUT_ERROR("Framebuffer format is unsupported"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: + DONUT_ERROR("Framebuffer has incomplete multisample"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: + DONUT_ERROR("Framebuffer has incomplete layer targets"); + break; + default: + DONUT_ERROR("Framebuffer is incomplete (unknown error: {})", status); + break; + } + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + + void OpenGLFramebuffer::bind() + { + glBindFramebuffer(GL_FRAMEBUFFER, m_renderer_id); + glViewport(0, 0, m_specification.Width, m_specification.Height); + } + + void OpenGLFramebuffer::unbind() + { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + + void OpenGLFramebuffer::resize(uint32_t width, uint32_t height) + { + m_specification.Width = width; + m_specification.Height = height; + + invalidate(); + } + + int OpenGLFramebuffer::read_pixel(uint32_t attachment_index, int x, int y) + { + glReadBuffer(GL_COLOR_ATTACHMENT0 + attachment_index); + int pixel_data; + glReadPixels(x, y, 1, 1, GL_RED_INTEGER, GL_INT, &pixel_data); + return pixel_data; + } + + void OpenGLFramebuffer::clear_attachment(uint32_t attachment_index, int value) + { + auto& spec = m_color_attachment_specifications[attachment_index]; + glClearTexImage(m_color_attachments[attachment_index], 0, + Utils::donut_fb_texture_format_to_gl(spec.texture_format), GL_INT, &value); + } +}; diff --git a/src/platform/opengl/opengl_framebuffer.h b/src/platform/opengl/opengl_framebuffer.h new file mode 100644 index 0000000..7b07ddd --- /dev/null +++ b/src/platform/opengl/opengl_framebuffer.h @@ -0,0 +1,35 @@ +#pragma once + +#include "rendering/framebuffer.h" + +namespace Donut +{ + class OpenGLFramebuffer : public Framebuffer + { + public: + OpenGLFramebuffer(const FramebufferSpecification& spec); + virtual ~OpenGLFramebuffer(); + + void invalidate(); + + virtual void bind() override; + virtual void unbind() override; + + virtual void resize(uint32_t width, uint32_t height) override; + virtual int read_pixel(uint32_t attachment_index, int x, int y) override; + + virtual void clear_attachment(uint32_t attachment_index, int value) override; + virtual uint32_t get_color_attachment_renderer_id(uint32_t index = 0) const override { return m_color_attachments[index]; } + + virtual const FramebufferSpecification& get_specification() const override { return m_specification; } + private: + uint32_t m_renderer_id = 0; + FramebufferSpecification m_specification; + + std::vector<FramebufferTextureSpecification> m_color_attachment_specifications; + FramebufferTextureSpecification m_depth_attachment_specification = FramebufferTextureFormat::None; + + std::vector<uint32_t> m_color_attachments; + uint32_t m_depth_attachment = 0; + }; +}; diff --git a/src/platform/opengl/opengl_renderer_api.cpp b/src/platform/opengl/opengl_renderer_api.cpp index bfb79ac..0012911 100644 --- a/src/platform/opengl/opengl_renderer_api.cpp +++ b/src/platform/opengl/opengl_renderer_api.cpp @@ -106,4 +106,10 @@ namespace Donut glBindImageTexture(slot, texture_id, 0, GL_FALSE, 0, read_only ? GL_READ_ONLY : GL_WRITE_ONLY, GL_RGBA8); } + + void OpenGLRendererAPI::read_pixels(uint32_t x, uint32_t y, uint32_t width, uint32_t height, + uint32_t format, uint32_t type, void* pixels) + { + glReadPixels(x, y, width, height, format, type, pixels); + } }; diff --git a/src/platform/opengl/opengl_renderer_api.h b/src/platform/opengl/opengl_renderer_api.h index 45e6a7b..51985e0 100644 --- a/src/platform/opengl/opengl_renderer_api.h +++ b/src/platform/opengl/opengl_renderer_api.h @@ -36,5 +36,9 @@ namespace Donut virtual void bind_image_texture(uint32_t texture_id, uint32_t slot = 0, bool read_only = false) override; + virtual void read_pixels(uint32_t x, uint32_t y, + uint32_t width, uint32_t height, + uint32_t format, uint32_t type, + void* pixels) override; }; }; diff --git a/src/platform/vulkan/vulkan_renderer_api.cpp b/src/platform/vulkan/vulkan_renderer_api.cpp index 8a3069e..00344fb 100644 --- a/src/platform/vulkan/vulkan_renderer_api.cpp +++ b/src/platform/vulkan/vulkan_renderer_api.cpp @@ -57,4 +57,9 @@ namespace Donut void VulkanRendererAPI::bind_image_texture(uint32_t texture_id, uint32_t slot, bool read_only) { } + + void VulkanRendererAPI::read_pixels(uint32_t x, uint32_t y, uint32_t width, uint32_t height, + uint32_t format, uint32_t type, void* pixels) + { + } }; diff --git a/src/platform/vulkan/vulkan_renderer_api.h b/src/platform/vulkan/vulkan_renderer_api.h index e1d4566..72ea60a 100644 --- a/src/platform/vulkan/vulkan_renderer_api.h +++ b/src/platform/vulkan/vulkan_renderer_api.h @@ -32,5 +32,7 @@ namespace Donut virtual void bind_image_texture(uint32_t texture_id, uint32_t slot = 0, bool read_only = false) override; + virtual void read_pixels(uint32_t x, uint32_t y, uint32_t width, uint32_t height, + uint32_t format, uint32_t type, void* pixels) override; }; }; diff --git a/src/rendering/framebuffer.cpp b/src/rendering/framebuffer.cpp new file mode 100644 index 0000000..e227dcf --- /dev/null +++ b/src/rendering/framebuffer.cpp @@ -0,0 +1,17 @@ +#include "framebuffer.h" +#include "renderer.h" + +#include "platform/opengl/opengl_framebuffer.h" + +namespace Donut +{ + Ref<Framebuffer> Framebuffer::create(const FramebufferSpecification& spec) + { + switch (Renderer::get_api()) + { + case RendererAPI::API::OpenGL: return create_ref<OpenGLFramebuffer>(spec); + } + + return nullptr; + } +}; diff --git a/src/rendering/framebuffer.h b/src/rendering/framebuffer.h new file mode 100644 index 0000000..1d42d85 --- /dev/null +++ b/src/rendering/framebuffer.h @@ -0,0 +1,65 @@ +#pragma once + +#include "core/memory.h" +#include "texture.h" + +namespace Donut +{ + enum class FramebufferTextureFormat + { + None = 0, + + RGBA8, + RED_INTEGER, + + DEPTH24STENCIL8, + + Depth = DEPTH24STENCIL8 + }; + + struct FramebufferTextureSpecification + { + FramebufferTextureSpecification() = default; + FramebufferTextureSpecification(FramebufferTextureFormat format) + : texture_format(format) {} + + FramebufferTextureFormat texture_format = FramebufferTextureFormat::None; + }; + + struct FramebufferAttachmentSpecification + { + FramebufferAttachmentSpecification() = default; + FramebufferAttachmentSpecification(std::initializer_list<FramebufferTextureSpecification> attachments) + : attachments(attachments) {} + + std::vector<FramebufferTextureSpecification> attachments; + }; + + struct FramebufferSpecification + { + uint32_t Width = 0, Height = 0; + FramebufferAttachmentSpecification attachments; + uint32_t Samples = 1; + + bool SwapChainTarget = false; + }; + + class Framebuffer + { + public: + virtual ~Framebuffer() = default; + + virtual void bind() = 0; + virtual void unbind() = 0; + + virtual void resize(uint32_t width, uint32_t height) = 0; + virtual int read_pixel(uint32_t attachment_index, int x, int y) = 0; + + virtual void clear_attachment(uint32_t attachment_index, int value) = 0; + virtual uint32_t get_color_attachment_renderer_id(uint32_t index = 0) const = 0; + + virtual const FramebufferSpecification& get_specification() const = 0; + + static Ref<Framebuffer> create(const FramebufferSpecification& spec); + }; +}; diff --git a/src/rendering/renderer.h b/src/rendering/renderer.h index 67ad998..ea200db 100644 --- a/src/rendering/renderer.h +++ b/src/rendering/renderer.h @@ -3,6 +3,7 @@ #include "core/memory.h" #include "vertex_array.h" #include "shader.h" +#include "framebuffer.h" #include <glm/glm.hpp> @@ -41,6 +42,8 @@ namespace Donut virtual void bind_image_texture(uint32_t texture_id, uint32_t slot = 0, bool read_only = false) = 0; + virtual void read_pixels(uint32_t x, uint32_t y, uint32_t width, uint32_t height, + uint32_t format, uint32_t type, void* pixels) = 0; inline static API get_api() { return s_api; } inline static void set_api(API api) { s_api = api; } @@ -122,6 +125,12 @@ namespace Donut s_renderer_api->bind_image_texture(texture_id, slot, read_only); } + inline static void read_pixels(uint32_t x, uint32_t y, uint32_t width, uint32_t height, + uint32_t format, uint32_t type, void* pixels) + { + s_renderer_api->read_pixels(x, y, width, height, format, type, pixels); + } + private: static Scope<RendererAPI> s_renderer_api; }; diff --git a/src/states/simulation_state.cpp b/src/states/simulation_state.cpp index 6964852..41f7b36 100644 --- a/src/states/simulation_state.cpp +++ b/src/states/simulation_state.cpp @@ -7,6 +7,9 @@ #include <imgui.h> #include <GLFW/glfw3.h> +#include <chrono> +#include <iomanip> +#include <sstream> namespace Donut { @@ -230,6 +233,51 @@ namespace Donut ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Export"); + ImGui::Separator(); + + if (ImGui::Button("Export Frame (1080p)", ImVec2(-1, 30))) + { + auto& engine = Application::get().get_engine(); + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + std::stringstream ss; + ss << "frame_1080p_" << std::put_time(std::localtime(&time_t), "%Y-%m-%d_%H-%M-%S") << ".png"; + engine.export_high_res_frame(ss.str(), 1920, 1080); + } + + if (ImGui::Button("Export High-Res Frame (4K)", ImVec2(-1, 30))) + { + auto& engine = Application::get().get_engine(); + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + std::stringstream ss; + ss << "high_res_frame_4k_" << std::put_time(std::localtime(&time_t), "%Y-%m-%d_%H-%M-%S") << ".png"; + engine.export_high_res_frame(ss.str(), 4096, 3072); + } + + if (ImGui::Button("Export High-Res Frame (8K)", ImVec2(-1, 30))) + { + auto& engine = Application::get().get_engine(); + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + std::stringstream ss; + ss << "high_res_frame_8k_" << std::put_time(std::localtime(&time_t), "%Y-%m-%d_%H-%M-%S") << ".png"; + engine.export_high_res_frame(ss.str(), 8192, 6144); + } + + if (ImGui::Button("Export Ultra High-Res Frame (16K)", ImVec2(-1, 30))) + { + auto& engine = Application::get().get_engine(); + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + std::stringstream ss; + ss << "high_res_frame_16k_" << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S") << ".png"; + engine.export_high_res_frame(ss.str(), 16384, 12288); + } + + ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Controls"); ImGui::Separator(); |
