aboutsummaryrefslogtreecommitdiff
path: root/src/platform
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/opengl/opengl_device.cpp34
-rw-r--r--src/platform/vulkan/vulkan_common.h4
-rw-r--r--src/platform/vulkan/vulkan_device.cpp98
3 files changed, 135 insertions, 1 deletions
diff --git a/src/platform/opengl/opengl_device.cpp b/src/platform/opengl/opengl_device.cpp
index 82fdae1..7ef0ba1 100644
--- a/src/platform/opengl/opengl_device.cpp
+++ b/src/platform/opengl/opengl_device.cpp
@@ -7,6 +7,7 @@
#include <glad/glad.h>
#include <GLFW/glfw3.h>
+#include <algorithm>
#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
@@ -18,7 +19,7 @@ namespace Donut::RHI
auto gl_topology(Topology t) -> GLenum { return t == Topology::Lines ? GL_LINES : GL_TRIANGLES; }
auto gl_compare(CompareOp o) -> GLenum { return o == CompareOp::Always ? GL_ALWAYS : o == CompareOp::LessEqual ? GL_LEQUAL : GL_LESS; }
auto gl_filter(Filter f) -> GLint { return f == Filter::Nearest ? GL_NEAREST : GL_LINEAR; }
- auto gl_internal(Format f) -> GLint { return f == Format::RGBA16F ? GL_RGBA16F : f == Format::D32 ? GL_DEPTH_COMPONENT32F : GL_RGBA8; }
+ auto gl_internal(Format f) -> GLint { return f == Format::RGBA16F ? GL_RGBA16F : f == Format::RGBA32F ? GL_RGBA32F : f == Format::D32 ? GL_DEPTH_COMPONENT32F : GL_RGBA8; }
// ---- Buffer -------------------------------------------------------
class GLBuffer : public Buffer
@@ -255,6 +256,37 @@ namespace Donut::RHI
}
auto end_frame() -> void override { glfwSwapBuffers(static_cast<GLFWwindow*>(m_window)); }
+ auto run_offscreen(const std::function<void(CommandList&)>& record) -> void override
+ {
+ glBindVertexArray(m_vao); // GL is immediate: record executes now
+ record(*m_cmds);
+ glFinish();
+ }
+ auto read_render_target(RenderTarget* target, std::vector<uint8_t>& out) -> void override
+ {
+ auto* rt = static_cast<GLRenderTarget*>(target);
+ int w = rt->m_w, h = rt->m_h;
+ out.resize((size_t)w * h * 4);
+ glBindFramebuffer(GL_FRAMEBUFFER, rt->m_fbo);
+ glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, out.data());
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ const size_t row = (size_t)w * 4; // GL is bottom-up -> flip to top-left
+ for (int y = 0; y < h / 2; ++y)
+ std::swap_ranges(out.begin() + y * row, out.begin() + (y + 1) * row, out.begin() + (h - 1 - y) * row);
+ }
+ auto read_render_target_float(RenderTarget* target, std::vector<float>& out) -> void override
+ {
+ auto* rt = static_cast<GLRenderTarget*>(target);
+ int w = rt->m_w, h = rt->m_h;
+ out.resize((size_t)w * h * 4);
+ glBindFramebuffer(GL_FRAMEBUFFER, rt->m_fbo);
+ glReadPixels(0, 0, w, h, GL_RGBA, GL_FLOAT, out.data()); // GL converts to float32
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ const size_t row = (size_t)w * 4; // GL is bottom-up -> flip to top-left
+ for (int y = 0; y < h / 2; ++y)
+ std::swap_ranges(out.begin() + y * row, out.begin() + (y + 1) * row, out.begin() + (h - 1 - y) * row);
+ }
+
auto init_imgui() -> void override
{
IMGUI_CHECKVERSION(); ImGui::CreateContext();
diff --git a/src/platform/vulkan/vulkan_common.h b/src/platform/vulkan/vulkan_common.h
index 5ae257e..8f02f17 100644
--- a/src/platform/vulkan/vulkan_common.h
+++ b/src/platform/vulkan/vulkan_common.h
@@ -41,6 +41,7 @@ namespace Donut::RHI
{
switch (f) {
case Format::RGBA16F: return VK_FORMAT_R16G16B16A16_SFLOAT;
+ case Format::RGBA32F: return VK_FORMAT_R32G32B32A32_SFLOAT;
case Format::D32: return VK_FORMAT_D32_SFLOAT;
default: return VK_FORMAT_R8G8B8A8_UNORM;
}
@@ -277,6 +278,9 @@ namespace Donut::RHI
auto begin_frame(const glm::vec4& clear) -> CommandList* override;
auto end_frame() -> void override;
+ auto run_offscreen(const std::function<void(CommandList&)>& record) -> void override;
+ auto read_render_target(RenderTarget* target, std::vector<uint8_t>& out) -> void override;
+ auto read_render_target_float(RenderTarget* target, std::vector<float>& out) -> void override;
auto init_imgui() -> void override;
auto imgui_new_frame() -> void override;
diff --git a/src/platform/vulkan/vulkan_device.cpp b/src/platform/vulkan/vulkan_device.cpp
index 5d80cff..ff2e296 100644
--- a/src/platform/vulkan/vulkan_device.cpp
+++ b/src/platform/vulkan/vulkan_device.cpp
@@ -117,6 +117,104 @@ namespace Donut::RHI
m_current_frame = (m_current_frame + 1) % MAX_FRAMES_IN_FLIGHT;
}
+ auto VulkanDevice::run_offscreen(const std::function<void(CommandList&)>& record) -> void
+ {
+ vkDeviceWaitIdle(m_device);
+ vkResetDescriptorPool(m_device, m_frame_pools[0], 0); // per-draw sets for this pass
+
+ VkCommandBufferAllocateInfo cbai{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
+ cbai.commandPool = m_command_pool; cbai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cbai.commandBufferCount = 1;
+ VkCommandBuffer cmd; vkAllocateCommandBuffers(m_device, &cbai, &cmd);
+ VkCommandBufferBeginInfo bi{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+ vkBeginCommandBuffer(cmd, &bi);
+
+ m_cmds.m_device = m_device; m_cmds.m_cmd = cmd;
+ m_cmds.m_swapchain_rp = m_swapchain_rp; m_cmds.m_swapchain_fb = VK_NULL_HANDLE; m_cmds.m_extent = m_extent;
+ m_cmds.m_frame_pool = m_frame_pools[0]; m_cmds.m_pipe = nullptr;
+ record(m_cmds); // records its own off-screen render pass(es)
+
+ vkEndCommandBuffer(cmd);
+ VkSubmitInfo si{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; si.commandBufferCount = 1; si.pCommandBuffers = &cmd;
+ vkQueueSubmit(m_graphics_queue, 1, &si, VK_NULL_HANDLE);
+ vkQueueWaitIdle(m_graphics_queue);
+ vkFreeCommandBuffers(m_device, m_command_pool, 1, &cmd);
+ }
+
+ auto VulkanDevice::read_render_target(RenderTarget* target, std::vector<uint8_t>& out) -> void
+ {
+ auto* rt = static_cast<VkRenderTargetR*>(target);
+ uint32_t w = (uint32_t)rt->m_w, h = (uint32_t)rt->m_h;
+ VkDeviceSize sz = (VkDeviceSize)w * h * 4;
+ out.resize(sz);
+
+ VkBuffer buf; VkDeviceMemory mem;
+ create_buffer_raw(sz, VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, buf, mem);
+
+ VkCommandBufferAllocateInfo cbai{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
+ cbai.commandPool = m_command_pool; cbai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cbai.commandBufferCount = 1;
+ VkCommandBuffer cmd; vkAllocateCommandBuffers(m_device, &cbai, &cmd);
+ VkCommandBufferBeginInfo bi{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+ vkBeginCommandBuffer(cmd, &bi);
+ // The target ended its render pass in SHADER_READ_ONLY; move it to TRANSFER_SRC to copy.
+ VkImageMemoryBarrier b{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
+ b.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; b.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+ b.image = rt->m_image; b.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+ b.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; b.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+ vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
+ VkBufferImageCopy copy{}; copy.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; copy.imageExtent = { w, h, 1 };
+ vkCmdCopyImageToBuffer(cmd, rt->m_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buf, 1, &copy);
+ VkImageMemoryBarrier b2 = b; b2.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; b2.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ b2.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; b2.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+ vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b2);
+ vkEndCommandBuffer(cmd);
+ VkSubmitInfo si{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; si.commandBufferCount = 1; si.pCommandBuffers = &cmd;
+ vkQueueSubmit(m_graphics_queue, 1, &si, VK_NULL_HANDLE); vkQueueWaitIdle(m_graphics_queue);
+
+ void* mp = nullptr; vkMapMemory(m_device, mem, 0, sz, 0, &mp);
+ std::memcpy(out.data(), mp, sz); // RGBA8_UNORM, top-down (top-left origin)
+ vkUnmapMemory(m_device, mem);
+ vkFreeCommandBuffers(m_device, m_command_pool, 1, &cmd);
+ vkDestroyBuffer(m_device, buf, nullptr); vkFreeMemory(m_device, mem, nullptr);
+ }
+
+ auto VulkanDevice::read_render_target_float(RenderTarget* target, std::vector<float>& out) -> void
+ {
+ auto* rt = static_cast<VkRenderTargetR*>(target);
+ uint32_t w = (uint32_t)rt->m_w, h = (uint32_t)rt->m_h;
+ out.resize((size_t)w * h * 4);
+ VkDeviceSize sz = (VkDeviceSize)out.size() * sizeof(float);
+
+ VkBuffer buf; VkDeviceMemory mem;
+ create_buffer_raw(sz, VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, buf, mem);
+
+ VkCommandBufferAllocateInfo cbai{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
+ cbai.commandPool = m_command_pool; cbai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cbai.commandBufferCount = 1;
+ VkCommandBuffer cmd; vkAllocateCommandBuffers(m_device, &cbai, &cmd);
+ VkCommandBufferBeginInfo bi{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+ vkBeginCommandBuffer(cmd, &bi);
+ VkImageMemoryBarrier b{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
+ b.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; b.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+ b.image = rt->m_image; b.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+ b.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; b.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+ vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
+ VkBufferImageCopy copy{}; copy.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; copy.imageExtent = { w, h, 1 };
+ vkCmdCopyImageToBuffer(cmd, rt->m_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buf, 1, &copy);
+ VkImageMemoryBarrier b2 = b; b2.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; b2.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ b2.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; b2.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+ vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b2);
+ vkEndCommandBuffer(cmd);
+ VkSubmitInfo si{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; si.commandBufferCount = 1; si.pCommandBuffers = &cmd;
+ vkQueueSubmit(m_graphics_queue, 1, &si, VK_NULL_HANDLE); vkQueueWaitIdle(m_graphics_queue);
+
+ void* mp = nullptr; vkMapMemory(m_device, mem, 0, sz, 0, &mp);
+ std::memcpy(out.data(), mp, sz); // R32G32B32A32_SFLOAT, top-down
+ vkUnmapMemory(m_device, mem);
+ vkFreeCommandBuffers(m_device, m_command_pool, 1, &cmd);
+ vkDestroyBuffer(m_device, buf, nullptr); vkFreeMemory(m_device, mem, nullptr);
+ }
+
auto VulkanDevice::init_imgui() -> void
{
VkDescriptorPoolSize pool_size{ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 };