Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add headless rendering #48

Open
cone-forest opened this issue Feb 10, 2025 · 0 comments
Open

Add headless rendering #48

cone-forest opened this issue Feb 10, 2025 · 0 comments

Comments

@cone-forest
Copy link
Collaborator

cone-forest commented Feb 10, 2025

Headless means using an image instead of a swapchain.
Need to first create the target image on GPU memory, then transfer it to CPU for saving to file.
Code outline:

// 1. Create a target image and allocate memory
vk::ImageCreateInfo imageInfo(
    vk::ImageCreateFlags(),
    vk::ImageType::e2D,
    vk::Format::eR8G8B8A8Unorm,          // Choose your format
    vk::Extent3D(width, height, 1),
    1, 1,
    vk::SampleCountFlagBits::e1,
    vk::ImageTiling::eOptimal,
    vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc,
    vk::SharingMode::eExclusive
);
vk::Image targetImage = device.createImage(imageInfo);

// Allocate and bind device memory for the image
vk::MemoryRequirements memReq = device.getImageMemoryRequirements(targetImage);
vk::MemoryAllocateInfo allocInfo(
    memReq.size,
    findMemoryType(physicalDevice, memReq.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal)
);
vk::DeviceMemory imageMemory = device.allocateMemory(allocInfo);
device.bindImageMemory(targetImage, imageMemory, 0);

// 2. Create image view
vk::ImageViewCreateInfo viewInfo(
    {},
    targetImage,
    vk::ImageViewType::e2D,
    imageInfo.format,
    {},
    {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}
);
vk::ImageView targetImageView = device.createImageView(viewInfo);

// 3. Create render pass targeting our image format
vk::AttachmentDescription colorAttachment(
    {},
    imageInfo.format,
    vk::SampleCountFlagBits::e1,
    vk::AttachmentLoadOp::eClear,        // Clear at start
    vk::AttachmentStoreOp::eStore,       // Keep after rendering
    vk::AttachmentLoadOp::eDontCare,
    vk::AttachmentStoreOp::eDontCare,
    vk::ImageLayout::eUndefined,         // Initial layout
    vk::ImageLayout::eTransferSrcOptimal // Final layout (for copy)
);

vk::AttachmentReference colorRef(0, vk::ImageLayout::eColorAttachmentOptimal);

vk::SubpassDescription subpass(
    {},
    vk::PipelineBindPoint::eGraphics,
    0, nullptr,
    1, &colorRef
);

vk::RenderPassCreateInfo renderPassInfo({}, 1, &colorAttachment, 1, &subpass);
vk::RenderPass renderPass = device.createRenderPass(renderPassInfo);

// 4. Create framebuffer
vk::FramebufferCreateInfo fbInfo(
    {},
    renderPass,
    1, &targetImageView,
    width, height, 1
);
vk::Framebuffer framebuffer = device.createFramebuffer(fbInfo);

// 5. Create staging buffer for data readback
vk::BufferCreateInfo bufferInfo(
    {},
    width * height * 4,                  // RGBA8
    vk::BufferUsageFlagBits::eTransferDst,
    vk::SharingMode::eExclusive
);
vk::Buffer stagingBuffer = device.createBuffer(bufferInfo);

// Allocate host-visible memory for staging buffer
vk::MemoryRequirements stagingMemReq = device.getBufferMemoryRequirements(stagingBuffer);
vk::MemoryAllocateInfo stagingAlloc(
    stagingMemReq.size,
    findMemoryType(physicalDevice, stagingMemReq.memoryTypeBits, 
        vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent)
);
vk::DeviceMemory stagingMemory = device.allocateMemory(stagingAlloc);
device.bindBufferMemory(stagingBuffer, stagingMemory, 0);

// 6. Record command buffer
vk::CommandBuffer cmdBuffer = ...; // Allocate from command pool

cmdBuffer.begin(vk::CommandBufferBeginInfo());

// Begin render pass (automatically transitions image layout)
vk::ClearValue clearColor(vk::ClearColorValue(std::array<float, 4>{0.0f, 0.0f, 0.0f, 1.0f}));
vk::RenderPassBeginInfo rpBeginInfo(
    renderPass,
    framebuffer,
    vk::Rect2D({0, 0}, {width, height}),
    1, &clearColor
);
cmdBuffer.beginRenderPass(rpBeginInfo, vk::SubpassContents::eInline);

// Draw commands (bind pipeline, set viewport, draw geometry)
cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, graphicsPipeline);
cmdBuffer.setViewport(0, {vk::Viewport(0, 0, width, height, 0.0f, 1.0f)});
cmdBuffer.setScissor(0, {vk::Rect2D({0, 0}, {width, height})});
cmdBuffer.draw(3, 1, 0, 0); // Example triangle

cmdBuffer.endRenderPass();

// Copy rendered image to staging buffer
vk::BufferImageCopy copyRegion(
    0, 0, 0,
    {vk::ImageAspectFlagBits::eColor, 0, 0, 1},
    {0, 0, 0},
    {width, height, 1}
);
cmdBuffer.copyImageToBuffer(
    targetImage,
    vk::ImageLayout::eTransferSrcOptimal, // Layout from render pass finalLayout
    stagingBuffer,
    copyRegion
);

cmdBuffer.end();

// 7. Submit and wait for completion
vk::Fence fence = device.createFence({});
queue.submit(vk::SubmitInfo(0, nullptr, 0, nullptr, 1, &cmdBuffer), fence);
device.waitForFences(fence, true, UINT64_MAX);

// 8. Access pixel data
void* data = device.mapMemory(stagingMemory, 0, VK_WHOLE_SIZE);
// Save data to file (e.g., as PNG) or process further
device.unmapMemory(stagingMemory);

// Cleanup
device.destroyFence(fence);
device.destroyBuffer(stagingBuffer);
device.freeMemory(stagingMemory);
device.destroyRenderPass(renderPass);
device.destroyFramebuffer(framebuffer);
device.destroyImageView(targetImageView);
device.destroyImage(targetImage);
device.freeMemory(imageMemory);
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant