hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/engine/engine.cpp154
-rw-r--r--src/engine/engine.h1
-rw-r--r--src/platform/opengl/opengl_framebuffer.cpp237
-rw-r--r--src/platform/opengl/opengl_framebuffer.h35
-rw-r--r--src/platform/opengl/opengl_renderer_api.cpp6
-rw-r--r--src/platform/opengl/opengl_renderer_api.h4
-rw-r--r--src/platform/vulkan/vulkan_renderer_api.cpp5
-rw-r--r--src/platform/vulkan/vulkan_renderer_api.h2
-rw-r--r--src/rendering/framebuffer.cpp17
-rw-r--r--src/rendering/framebuffer.h65
-rw-r--r--src/rendering/renderer.h9
-rw-r--r--src/states/simulation_state.cpp48
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();