Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,18 @@ FetchContent_Declare(
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)

FetchContent_MakeAvailable(glfw googletest)
FetchContent_Declare(
openexr
GIT_REPOSITORY https://github.com/AcademySoftwareFoundation/openexr.git
GIT_TAG v3.2.1
)
set(OPENEXR_BUILD_TOOLS OFF CACHE BOOL "" FORCE)
set(OPENEXR_INSTALL_TOOLS OFF CACHE BOOL "" FORCE)
set(OPENEXR_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(OPENEXR_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(OPENEXR_INSTALL OFF CACHE BOOL "" FORCE)

FetchContent_MakeAvailable(glfw googletest openexr)

# --- Vendored Libraries ---
# Base DearImGui
Expand Down Expand Up @@ -171,6 +182,7 @@ add_library(LoomCore STATIC
src/ui/ImGuiRenderer.cpp
src/ui/NodeEditorPanel.cpp
src/core/Nodes.cpp
src/core/DeepExrLoader.cpp
src/core/RenderCache.cpp
)
if(HAS_SHADER_COMPILER)
Expand All @@ -190,6 +202,7 @@ target_link_libraries(LoomCore PUBLIC
vma
glfw
Vulkan::Vulkan
OpenEXR::OpenEXR
)

# Apple-specific config for MoltenVK
Expand Down
22 changes: 22 additions & 0 deletions include/core/DeepExrLoader.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include <cstdint>
#include <string>
#include <vector>

namespace loom::core {

struct DeepSampleBuffer {
uint32_t width, height;
std::vector<uint32_t> offsets; // exclusive prefix sum, 1 per pixel
std::vector<uint32_t> counts; // sample count per pixel
// Packed as [R, G, B, A, Z] per sample, std430-compatible (5 floats = 20 bytes)
std::vector<float> sampleData;
};

class DeepExrLoader {
public:
static DeepSampleBuffer load(const std::string& filepath);
};

} // namespace loom::core
4 changes: 4 additions & 0 deletions include/core/EvaluationContext.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@

namespace loom::gpu {
class TransientImagePool;
class TransientBufferPool;
class PipelineCache;
class VulkanContext;
} // namespace loom::gpu

namespace loom::core {
Expand All @@ -23,7 +25,9 @@ class RenderCache;
struct EvaluationContext {
VkExtent2D requestedExtent;
gpu::TransientImagePool* imagePool;
gpu::TransientBufferPool* bufferPool;
gpu::PipelineCache* pipelineCache;
gpu::VulkanContext* vkContext;
RenderCache* renderCache;
VmaAllocator allocator;
VkCommandBuffer cmd; // Shared command buffer for this frame
Expand Down
8 changes: 8 additions & 0 deletions include/core/Graph.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ class Graph {
case NodeType::Passthrough:
node = std::make_unique<PassthroughNode>(nodeHandle, name);
break;
case NodeType::DeepRead:
node = std::make_unique<DeepReadNode>(nodeHandle, name);
break;
default:
throw std::runtime_error("Unknown node type");
}
Expand Down Expand Up @@ -397,6 +400,8 @@ class Graph {
return "Viewer";
case NodeType::Passthrough:
return "Passthrough";
case NodeType::DeepRead:
return "Deep Read";
default:
return "Unknown";
}
Expand All @@ -419,6 +424,9 @@ class Graph {
createPin(node, PinDirection::Input, PinType::Float);
createPin(node, PinDirection::Output, PinType::Float);
break;
case NodeType::DeepRead:
createPin(node, PinDirection::Output, PinType::Float);
break;
}
}

Expand Down
19 changes: 19 additions & 0 deletions include/core/Nodes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,23 @@ class PassthroughNode : public Node {
void execute(EvaluationContext& ctx, const Region& region) override;
};

class DeepReadNode : public Node {
public:
DeepReadNode(NodeHandle h, std::string n) : Node(h, NodeType::DeepRead, std::move(n)) {}
~DeepReadNode() override;

void setFilepath(EvaluationContext& ctx, const std::string& path);

void markRequiredTiles(const Region& requestedRegion,
std::unordered_set<NodeHandle>& activeNodes) override;
void execute(EvaluationContext& ctx, const Region& region) override;

private:
std::string filepath;
gpu::DeepGpuBuffer deepBuffer;
bool needsUpload = false;

void releaseGpuResources(EvaluationContext& ctx);
};

} // namespace loom::core
2 changes: 1 addition & 1 deletion include/core/Types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ inline uint32_t decodeIndex(uint64_t id) {

enum class PinDirection { Input, Output };
enum class PinType { Float, DeepBuffer };
enum class NodeType { Constant, Merge, Viewer, Passthrough };
enum class NodeType { Constant, Merge, Viewer, Passthrough, DeepRead };

struct Tile {
uint32_t x, y;
Expand Down
7 changes: 7 additions & 0 deletions include/gpu/ResourceHandles.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,11 @@ struct BufferHandle {
bool isValid() const { return poolIndex != 0xFFFFFFFF; }
};

struct DeepGpuBuffer {
BufferHandle sampleBuffer; // SSBO holding [R,G,B,A,Z]
BufferHandle lookupBuffer; // SSBO holding interleaved [offset, count]
uint32_t width;
uint32_t height;
};

} // namespace loom::gpu
52 changes: 52 additions & 0 deletions shaders/DeepFlatten.comp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#version 460

layout(local_size_x = 16, local_size_y = 16) in;

layout(set = 0, binding = 0, rgba32f) uniform image2D bindlessImages[];

// std430 ensures 5 floats = 20 bytes (no std140 32-byte padding)
struct DeepSample { float r, g, b, a, z; };

layout(std430, set = 0, binding = 1) readonly buffer SampleBuffer {
DeepSample samples[];
} sampleSSBOs[];

layout(std430, set = 0, binding = 1) readonly buffer LookupBuffer {
uvec2 entries[]; // [offset, count] per pixel
} lookupSSBOs[];

layout(push_constant) uniform PushConstants {
uint lookupSSBOSlot;
uint sampleSSBOSlot;
uint outputImageSlot;
uint width;
uint height;
} pc;

void main() {
ivec2 coord = ivec2(gl_GlobalInvocationID.xy);
if (coord.x >= pc.width || coord.y >= pc.height) return;

uint pixelIndex = coord.y * pc.width + coord.x;

// Push constants are uniform, so nonuniformEXT is NOT required here.
uvec2 header = lookupSSBOs[pc.lookupSSBOSlot].entries[pixelIndex];
uint offset = header.x;
uint count = header.y;

vec4 accumColor = vec4(0.0);

for (uint i = 0; i < count; ++i) {
DeepSample s = sampleSSBOs[pc.sampleSSBOSlot].samples[offset + i];
vec3 srgb = vec3(s.r, s.g, s.b);

float alphaRemaining = 1.0 - accumColor.a;
// Straight alpha over-operation
accumColor.rgb += srgb * s.a * alphaRemaining;
accumColor.a += s.a * alphaRemaining;

if (accumColor.a >= 0.999) break; // early-out on full occlusion
}

imageStore(bindlessImages[pc.outputImageSlot], coord, accumColor);
}
105 changes: 105 additions & 0 deletions src/core/DeepExrLoader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#include "core/DeepExrLoader.hpp"

#include <ImathBox.h>
#include <ImfChannelList.h>
#include <ImfDeepFrameBuffer.h>
#include <ImfDeepScanLineInputFile.h>
#include <ImfHeader.h>
#include <ImfPartType.h>

#include <algorithm>
#include <iostream>
#include <stdexcept>

namespace loom::core {

struct Sample {
float r, g, b, a, z;
};

DeepSampleBuffer DeepExrLoader::load(const std::string& filepath) {
Imf::DeepScanLineInputFile file(filepath.c_str());
const Imf::Header& header = file.header();
const Imf::ChannelList& channels = header.channels();

Imath::Box2i dw = header.dataWindow();
int width = dw.max.x - dw.min.x + 1;
int height = dw.max.y - dw.min.y + 1;

DeepSampleBuffer result;
result.width = static_cast<uint32_t>(width);
result.height = static_cast<uint32_t>(height);
result.offsets.resize(width * height);
result.counts.resize(width * height);

// 1. Read sampleCount for every pixel
file.readPixelSampleCounts(dw.min.y, dw.max.y);
for (int y = dw.min.y; y <= dw.max.y; ++y) {
int idx = (y - dw.min.y) * width;
const unsigned int* rowCounts = file.getPixelSampleCounts(y);
for (int x = 0; x < width; ++x) {
result.counts[idx + x] = rowCounts[dw.min.x + x];
}
}

// 2. Compute offsets via exclusive prefix sum
result.offsets[0] = 0;
for (size_t i = 1; i < result.counts.size(); ++i) {
result.offsets[i] = result.offsets[i - 1] + result.counts[i - 1];
}
uint32_t totalSamples = result.offsets.back() + result.counts.back();

// 3. Allocate sampleData
result.sampleData.resize(totalSamples * 5);

// 4. DeepFrameBuffer setup
Imf::DeepFrameBuffer frameBuffer;
std::vector<std::vector<float*>> allPtrs(5, std::vector<float*>(width * height));

auto addChannel = [&](const char* name, int channelIdx) {
if (channels.findChannel(name)) {
for (int i = 0; i < width * height; ++i) {
allPtrs[channelIdx][i] =
result.sampleData.data() + result.offsets[i] * 5 + channelIdx;
}
frameBuffer.insert(
name, Imf::DeepSlice(Imf::FLOAT, (char*)allPtrs[channelIdx].data(), sizeof(float*),
width * sizeof(float*), 5 * sizeof(float)));
}
};

addChannel("R", 0);
addChannel("G", 1);
addChannel("B", 2);
addChannel("A", 3);
addChannel("Z", 4);

file.setFrameBuffer(frameBuffer);
file.readPixels(dw.min.y, dw.max.y);

// Handle missing channels (A default to 1.0, Z default to 0.0)
bool hasA = channels.findChannel("A") != nullptr;
bool hasZ = channels.findChannel("Z") != nullptr;
bool hasR = channels.findChannel("R") != nullptr;
bool hasG = channels.findChannel("G") != nullptr;
bool hasB = channels.findChannel("B") != nullptr;

for (uint32_t i = 0; i < totalSamples; ++i) {
if (!hasR) result.sampleData[i * 5 + 0] = 0.0f;
if (!hasG) result.sampleData[i * 5 + 1] = 0.0f;
if (!hasB) result.sampleData[i * 5 + 2] = 0.0f;
if (!hasA) result.sampleData[i * 5 + 3] = 1.0f;
if (!hasZ) result.sampleData[i * 5 + 4] = 0.0f;
}

// 5. Z-sort per pixel
for (uint32_t i = 0; i < result.width * result.height; ++i) {
Sample* begin = reinterpret_cast<Sample*>(result.sampleData.data() + result.offsets[i] * 5);
Sample* end = begin + result.counts[i];
std::sort(begin, end, [](const Sample& a, const Sample& b) { return a.z < b.z; });
}

return result;
}

} // namespace loom::core
Loading
Loading