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/OpenGLFramebuffer.cpp238
-rw-r--r--src/Platform/OpenGL/OpenGLFramebuffer.h35
-rw-r--r--src/Platform/OpenGL/OpenGLRendererAPI.cpp6
-rw-r--r--src/Platform/OpenGL/OpenGLRendererAPI.h4
-rw-r--r--src/Platform/Vulkan/VulkanRendererAPI.cpp12
-rw-r--r--src/Platform/Vulkan/VulkanRendererAPI.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/SimulationState.cpp48
12 files changed, 588 insertions, 3 deletions
diff --git a/src/Engine/Engine.cpp b/src/Engine/Engine.cpp
index fa010a7..245f6fd 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"
@@ -326,7 +330,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_PosRadius.x, obj.m_PosRadius.y, obj.m_PosRadius.z,
obj.m_PosRadius.w,
@@ -334,12 +338,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.GetOrbitalPosition().x,
m_Camera.GetOrbitalPosition().y,
m_Camera.GetOrbitalPosition().z
);
- DONUT_INFO("Camera radius: {:.2e}", m_Camera.GetOrbitalRadius());
+ DONUT_INFO("Camera radius: {}", m_Camera.GetOrbitalRadius());
DONUT_INFO("========================");
}
+
+ void Engine::ExportHighResFrame(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 originalWidth = m_Width;
+ int originalHeight = m_Height;
+ int originalComputeHeight = m_ComputeHeight;
+
+ m_Width = width;
+ m_Height = height;
+ m_ComputeHeight = height;
+
+ int computeHeight = height;
+ int computeWidth = (width * computeHeight) / height;
+
+ if (computeWidth <= 0 || computeHeight <= 0)
+ {
+ DONUT_ERROR("Invalid compute dimensions: {}x{}", computeWidth, computeHeight);
+ return;
+ }
+
+ FramebufferSpecification fbSpec;
+ fbSpec.Width = width;
+ fbSpec.Height = height;
+ fbSpec.Attachments = { FramebufferTextureFormat::RGBA8 };
+
+ auto highResFramebuffer = Framebuffer::Create(fbSpec);
+ if (!highResFramebuffer)
+ {
+ DONUT_ERROR("Failed to create high-resolution framebuffer");
+ return;
+ }
+
+ highResFramebuffer->Bind();
+
+ RenderCommand::SetViewport(0, 0, width, height);
+ RenderCommand::Clear();
+
+ auto highResTexture = Texture2D::Create(computeWidth, computeHeight);
+ if (!highResTexture)
+ {
+ DONUT_ERROR("Failed to create high-resolution texture");
+ return;
+ }
+
+ highResTexture->SetData(nullptr, computeWidth * computeHeight * 4);
+ m_ComputeProgram->Bind();
+
+ struct UBOData
+ {
+ glm::vec3 pos; float _pad0;
+ glm::vec3 right; float _pad1;
+ glm::vec3 up; float _pad2;
+ glm::vec3 forward; float _pad3;
+ float tanHalfFov;
+ float aspect;
+ bool moving;
+ int _pad4;
+ } data;
+
+ glm::vec3 fwd = glm::normalize(m_Camera.GetOrbitalTarget() - m_Camera.GetOrbitalPosition());
+ 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.GetOrbitalPosition();
+ data.right = right;
+ data.up = up;
+ data.forward = fwd;
+ data.tanHalfFov = static_cast<float>(tan(glm::radians(60.0f * 0.5f)));
+ data.aspect = static_cast<float>(computeWidth) / static_cast<float>(computeHeight);
+ data.moving = m_Camera.IsDragging() || m_Camera.IsPanning();
+
+ m_CameraUBO->SetData(&data, sizeof(UBOData));
+ m_CameraUBO->Bind(1);
+
+ UploadDiskUBO();
+ UploadObjectsUBO(m_Objects);
+ UploadSimulationUBO();
+ highResTexture->BindAsImage(0, false);
+
+ uint32_t groupsX = static_cast<uint32_t>(std::ceil(computeWidth / 16.0f));
+ uint32_t groupsY = static_cast<uint32_t>(std::ceil(computeHeight / 16.0f));
+ m_ComputeProgram->Dispatch(groupsX, groupsY, 1);
+ m_ComputeProgram->MemoryBarrier(IMAGE_ACCESS_BARRIER_BIT);
+
+ m_ShaderProgram->Bind();
+ m_QuadVAO->Bind();
+
+ highResTexture->Bind(0);
+ m_ShaderProgram->SetInt("u_ScreenTexture", 0);
+
+ RenderCommand::DisableDepthTest();
+ RenderCommand::DrawArrays(6);
+ RenderCommand::EnableDepthTest();
+
+ std::vector<unsigned char> pixels(width * height * 4);
+ DONUT_INFO("Reading {} pixels from framebuffer...", width * height);
+ RenderCommand::ReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
+
+ DONUT_INFO("Flipping image vertically...");
+ std::vector<unsigned char> flippedPixels(width * height * 4);
+ for (int y = 0; y < height; ++y)
+ {
+ for (int x = 0; x < width; ++x)
+ {
+ int srcIndex = (y * width + x) * 4;
+ int dstIndex = ((height - 1 - y) * width + x) * 4;
+ flippedPixels[dstIndex + 0] = pixels[srcIndex + 0]; // R
+ flippedPixels[dstIndex + 1] = pixels[srcIndex + 1]; // G
+ flippedPixels[dstIndex + 2] = pixels[srcIndex + 2]; // B
+ flippedPixels[dstIndex + 3] = pixels[srcIndex + 3]; // A
+ }
+ }
+
+ DONUT_INFO("Saving PNG file: {}...", filename);
+ int result = stbi_write_png(filename.c_str(), width, height, 4, flippedPixels.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);
+
+ highResFramebuffer->Unbind();
+
+ m_Width = originalWidth;
+ m_Height = originalHeight;
+ m_ComputeHeight = originalComputeHeight;
+
+ RenderCommand::SetViewport(0, 0, originalWidth, originalHeight);
+ }
}
diff --git a/src/Engine/Engine.h b/src/Engine/Engine.h
index f3724ff..328435a 100644
--- a/src/Engine/Engine.h
+++ b/src/Engine/Engine.h
@@ -101,6 +101,7 @@ namespace Donut
void SetEarlyExitDistance(float distance) { m_EarlyExitDistance = distance; }
void LoadObjectsFromScene(const std::vector<Donut::Object>& objects);
+ void ExportHighResFrame(const std::string& filename, int width = 4096, int height = 3072);
void PrintObjectInfo() const;
private:
Ref<Shader> CreateComputeProgram(const char* path);
diff --git a/src/Platform/OpenGL/OpenGLFramebuffer.cpp b/src/Platform/OpenGL/OpenGLFramebuffer.cpp
new file mode 100644
index 0000000..523d748
--- /dev/null
+++ b/src/Platform/OpenGL/OpenGLFramebuffer.cpp
@@ -0,0 +1,238 @@
+#include "OpenGLFramebuffer.h"
+#include "Core/Log.h"
+
+#include <glad/glad.h>
+
+namespace Donut
+{
+ namespace Utils
+ {
+ static GLenum TextureTarget(bool multisampled)
+ {
+ return multisampled ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
+ }
+
+ static void BindTexture(bool multisampled, uint32_t id)
+ {
+ glBindTexture(TextureTarget(multisampled), id);
+ }
+
+ static void CreateTextures(bool multisampled, uint32_t* outID, uint32_t count)
+ {
+ glCreateTextures(TextureTarget(multisampled), count, outID);
+ }
+
+ static void AttachColorTexture(uint32_t id, int samples, GLenum internalFormat, GLenum format, uint32_t width, uint32_t height, int index)
+ {
+ bool multisampled = samples > 1;
+ if (multisampled)
+ {
+ glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, internalFormat, width, height, GL_FALSE);
+ }
+ else
+ {
+ glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, 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, TextureTarget(multisampled), id, 0);
+ }
+
+ static void AttachDepthTexture(uint32_t id, int samples, GLenum format, GLenum attachmentType, 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, attachmentType, TextureTarget(multisampled), id, 0);
+ }
+
+ static bool IsDepthFormat(FramebufferTextureFormat format)
+ {
+ switch (format)
+ {
+ case FramebufferTextureFormat::DEPTH24STENCIL8: return true;
+ }
+ return false;
+ }
+
+ static GLenum DonutFBTextureFormatToGL(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::IsDepthFormat(spec.TextureFormat))
+ m_ColorAttachmentSpecifications.emplace_back(spec);
+ else
+ m_DepthAttachmentSpecification = spec;
+ }
+
+ Invalidate();
+ }
+
+ OpenGLFramebuffer::~OpenGLFramebuffer()
+ {
+ glDeleteFramebuffers(1, &m_RendererID);
+ glDeleteTextures(static_cast<GLsizei>(m_ColorAttachments.size()), m_ColorAttachments.data());
+ glDeleteTextures(1, &m_DepthAttachment);
+ }
+
+ void OpenGLFramebuffer::Invalidate()
+ {
+ if (m_RendererID)
+ {
+ glDeleteFramebuffers(1, &m_RendererID);
+ glDeleteTextures(static_cast<GLsizei>(m_ColorAttachments.size()), m_ColorAttachments.data());
+ glDeleteTextures(1, &m_DepthAttachment);
+
+ m_ColorAttachments.clear();
+ m_DepthAttachment = 0;
+ }
+
+ glCreateFramebuffers(1, &m_RendererID);
+ glBindFramebuffer(GL_FRAMEBUFFER, m_RendererID);
+
+ bool multisample = m_Specification.Samples > 1;
+
+ // Attachments
+ if (m_ColorAttachmentSpecifications.size())
+ {
+ m_ColorAttachments.resize(m_ColorAttachmentSpecifications.size());
+ Utils::CreateTextures(multisample, m_ColorAttachments.data(), static_cast<uint32_t>(m_ColorAttachments.size()));
+
+ for (size_t i = 0; i < m_ColorAttachments.size(); i++)
+ {
+ Utils::BindTexture(multisample, m_ColorAttachments[i]);
+ switch (m_ColorAttachmentSpecifications[i].TextureFormat)
+ {
+ case FramebufferTextureFormat::RGBA8:
+ Utils::AttachColorTexture(m_ColorAttachments[i], m_Specification.Samples, GL_RGBA8, GL_RGBA, m_Specification.Width, m_Specification.Height, static_cast<int>(i));
+ break;
+ case FramebufferTextureFormat::RED_INTEGER:
+ Utils::AttachColorTexture(m_ColorAttachments[i], m_Specification.Samples, GL_R32I, GL_RED_INTEGER, m_Specification.Width, m_Specification.Height, static_cast<int>(i));
+ break;
+ }
+ }
+ }
+
+ if (m_DepthAttachmentSpecification.TextureFormat != FramebufferTextureFormat::None)
+ {
+ Utils::CreateTextures(multisample, &m_DepthAttachment, 1);
+ Utils::BindTexture(multisample, m_DepthAttachment);
+ switch (m_DepthAttachmentSpecification.TextureFormat)
+ {
+ case FramebufferTextureFormat::DEPTH24STENCIL8:
+ Utils::AttachDepthTexture(m_DepthAttachment, m_Specification.Samples, GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL_ATTACHMENT, m_Specification.Width, m_Specification.Height);
+ break;
+ }
+ }
+
+ if (m_ColorAttachments.size() > 1)
+ {
+ GLenum buffers[4] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 };
+ glDrawBuffers(static_cast<GLsizei>(m_ColorAttachments.size()), buffers);
+ }
+ else if (m_ColorAttachments.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_RendererID);
+ 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::ReadPixel(uint32_t attachmentIndex, int x, int y)
+ {
+ glReadBuffer(GL_COLOR_ATTACHMENT0 + attachmentIndex);
+ int pixelData;
+ glReadPixels(x, y, 1, 1, GL_RED_INTEGER, GL_INT, &pixelData);
+ return pixelData;
+ }
+
+ void OpenGLFramebuffer::ClearAttachment(uint32_t attachmentIndex, int value)
+ {
+ auto& spec = m_ColorAttachmentSpecifications[attachmentIndex];
+ glClearTexImage(m_ColorAttachments[attachmentIndex], 0,
+ Utils::DonutFBTextureFormatToGL(spec.TextureFormat), GL_INT, &value);
+ }
+};
diff --git a/src/Platform/OpenGL/OpenGLFramebuffer.h b/src/Platform/OpenGL/OpenGLFramebuffer.h
new file mode 100644
index 0000000..8cd376c
--- /dev/null
+++ b/src/Platform/OpenGL/OpenGLFramebuffer.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 ReadPixel(uint32_t attachmentIndex, int x, int y) override;
+
+ virtual void ClearAttachment(uint32_t attachmentIndex, int value) override;
+ virtual uint32_t GetColorAttachmentRendererID(uint32_t index = 0) const override { return m_ColorAttachments[index]; }
+
+ virtual const FramebufferSpecification& GetSpecification() const override { return m_Specification; }
+ private:
+ uint32_t m_RendererID = 0;
+ FramebufferSpecification m_Specification;
+
+ std::vector<FramebufferTextureSpecification> m_ColorAttachmentSpecifications;
+ FramebufferTextureSpecification m_DepthAttachmentSpecification = FramebufferTextureFormat::None;
+
+ std::vector<uint32_t> m_ColorAttachments;
+ uint32_t m_DepthAttachment = 0;
+ };
+};
diff --git a/src/Platform/OpenGL/OpenGLRendererAPI.cpp b/src/Platform/OpenGL/OpenGLRendererAPI.cpp
index cd54f82..230b19b 100644
--- a/src/Platform/OpenGL/OpenGLRendererAPI.cpp
+++ b/src/Platform/OpenGL/OpenGLRendererAPI.cpp
@@ -106,4 +106,10 @@ namespace Donut
glBindImageTexture(slot, textureID, 0, GL_FALSE, 0,
readOnly ? GL_READ_ONLY : GL_WRITE_ONLY, GL_RGBA8);
}
+
+ void OpenGLRendererAPI::ReadPixels(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/OpenGLRendererAPI.h b/src/Platform/OpenGL/OpenGLRendererAPI.h
index 0576db8..5a68622 100644
--- a/src/Platform/OpenGL/OpenGLRendererAPI.h
+++ b/src/Platform/OpenGL/OpenGLRendererAPI.h
@@ -36,5 +36,9 @@ namespace Donut
virtual void BindImageTexture(uint32_t textureID,
uint32_t slot = 0,
bool readOnly = false) override;
+ virtual void ReadPixels(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/VulkanRendererAPI.cpp b/src/Platform/Vulkan/VulkanRendererAPI.cpp
index 0dc9f0e..79f7633 100644
--- a/src/Platform/Vulkan/VulkanRendererAPI.cpp
+++ b/src/Platform/Vulkan/VulkanRendererAPI.cpp
@@ -71,4 +71,16 @@ namespace Donut
{
// TODO(Hachem): Implement Vulkan image texture binding
}
+
+ void VulkanRendererAPI::ReadPixels(uint32_t x, uint32_t y, uint32_t width, uint32_t height,
+ uint32_t format, uint32_t type, void* pixels)
+ {
+ // TODO(Hachem): Implement Vulkan pixel reading
+ // For now, this is a placeholder implementation
+ // In a real Vulkan implementation, this would involve:
+ // 1. Creating a staging buffer
+ // 2. Copying the framebuffer to the staging buffer
+ // 3. Mapping the staging buffer and copying to the pixels array
+ // 4. Unmapping and destroying the staging buffer
+ }
};
diff --git a/src/Platform/Vulkan/VulkanRendererAPI.h b/src/Platform/Vulkan/VulkanRendererAPI.h
index dba7fe6..63a623f 100644
--- a/src/Platform/Vulkan/VulkanRendererAPI.h
+++ b/src/Platform/Vulkan/VulkanRendererAPI.h
@@ -32,5 +32,7 @@ namespace Donut
virtual void BindImageTexture(uint32_t textureID,
uint32_t slot = 0,
bool readOnly = false) override;
+ virtual void ReadPixels(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..1e95c6d
--- /dev/null
+++ b/src/Rendering/Framebuffer.cpp
@@ -0,0 +1,17 @@
+#include "Framebuffer.h"
+#include "Renderer.h"
+
+#include "Platform/OpenGL/OpenGLFramebuffer.h"
+
+namespace Donut
+{
+ Ref<Framebuffer> Framebuffer::Create(const FramebufferSpecification& spec)
+ {
+ switch (Renderer::GetAPI())
+ {
+ case RendererAPI::API::OpenGL: return CreateRef<OpenGLFramebuffer>(spec);
+ }
+
+ return nullptr;
+ }
+};
diff --git a/src/Rendering/Framebuffer.h b/src/Rendering/Framebuffer.h
new file mode 100644
index 0000000..e4c1481
--- /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)
+ : TextureFormat(format) {}
+
+ FramebufferTextureFormat TextureFormat = 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 ReadPixel(uint32_t attachmentIndex, int x, int y) = 0;
+
+ virtual void ClearAttachment(uint32_t attachmentIndex, int value) = 0;
+ virtual uint32_t GetColorAttachmentRendererID(uint32_t index = 0) const = 0;
+
+ virtual const FramebufferSpecification& GetSpecification() const = 0;
+
+ static Ref<Framebuffer> Create(const FramebufferSpecification& spec);
+ };
+};
diff --git a/src/Rendering/Renderer.h b/src/Rendering/Renderer.h
index d6c4154..6e87411 100644
--- a/src/Rendering/Renderer.h
+++ b/src/Rendering/Renderer.h
@@ -3,6 +3,7 @@
#include "Core/Memory.h"
#include "VertexArray.h"
#include "Shader.h"
+#include "Framebuffer.h"
#include <glm/glm.hpp>
@@ -41,6 +42,8 @@ namespace Donut
virtual void BindImageTexture(uint32_t textureID,
uint32_t slot = 0,
bool readOnly = false) = 0;
+ virtual void ReadPixels(uint32_t x, uint32_t y, uint32_t width, uint32_t height,
+ uint32_t format, uint32_t type, void* pixels) = 0;
inline static API GetAPI() { return s_API; }
inline static void SetAPI(API api) { s_API = api; }
@@ -122,6 +125,12 @@ namespace Donut
s_RendererAPI->BindImageTexture(textureID, slot, readOnly);
}
+ inline static void ReadPixels(uint32_t x, uint32_t y, uint32_t width, uint32_t height,
+ uint32_t format, uint32_t type, void* pixels)
+ {
+ s_RendererAPI->ReadPixels(x, y, width, height, format, type, pixels);
+ }
+
private:
static Scope<RendererAPI> s_RendererAPI;
};
diff --git a/src/States/SimulationState.cpp b/src/States/SimulationState.cpp
index fa3c4f5..d961f9e 100644
--- a/src/States/SimulationState.cpp
+++ b/src/States/SimulationState.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().GetEngine();
+ 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.ExportHighResFrame(ss.str(), 1920, 1080);
+ }
+
+ if (ImGui::Button("Export High-Res Frame (4K)", ImVec2(-1, 30)))
+ {
+ auto& engine = Application::Get().GetEngine();
+ 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.ExportHighResFrame(ss.str(), 4096, 3072);
+ }
+
+ if (ImGui::Button("Export High-Res Frame (8K)", ImVec2(-1, 30)))
+ {
+ auto& engine = Application::Get().GetEngine();
+ 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.ExportHighResFrame(ss.str(), 8192, 6144);
+ }
+
+ if (ImGui::Button("Export Ultra High-Res Frame (16K)", ImVec2(-1, 30)))
+ {
+ auto& engine = Application::Get().GetEngine();
+ 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.ExportHighResFrame(ss.str(), 16384, 12288);
+ }
+
+ ImGui::Spacing();
+
ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Controls");
ImGui::Separator();