Skip to content

Commit 879d949

Browse files
authored
Merge pull request #9 from rfdickerson/8-add-tonemapping
Add Tone Mapping
2 parents c4bce5e + 759c2f6 commit 879d949

23 files changed

Lines changed: 621 additions & 197 deletions

CMakeLists.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,14 @@ add_executable(Reactor src/main.cpp
3838
src/Imgui.hpp
3939
src/VulkanUtils.hpp
4040
src/Image.cpp
41-
src/Image.hpp)
41+
src/Image.hpp
42+
src/Sampler.cpp
43+
src/Sampler.hpp
44+
src/ImageStateTracker.cpp
45+
src/ImageStateTracker.h
46+
src/Uniforms.hpp
47+
src/UniformManager.cpp
48+
src/UniformManager.hpp)
4249

4350
target_link_libraries(Reactor PRIVATE Vulkan::Vulkan Vulkan::Headers glfw fmt::fmt spdlog::spdlog GPUOpen::VulkanMemoryAllocator glm::glm)
4451
target_link_libraries(Reactor PRIVATE imgui::imgui)

build-shaders.bat

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
glslc --target-env=vulkan1.3 -o shaders/triangle.vert.spv shaders/triangle.vert
2+
glslc --target-env=vulkan1.3 -o shaders/triangle.frag.spv shaders/triangle.frag
3+
4+
glslc --target-env=vulkan1.3 -o shaders/composite.vert.spv shaders/composite.vert
5+
glslc --target-env=vulkan1.3 -o shaders/composite.frag.spv shaders/composite.frag

shaders/composite.frag

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#version 450
2+
3+
layout(location = 0) in vec2 fragUV;
4+
layout(location = 0) out vec4 outColor;
5+
6+
layout(binding = 0) uniform sampler2D uInputImage;
7+
8+
layout(set = 0, binding = 1) uniform CompositeParams {
9+
float uExposure; // Default: 1.0
10+
float uContrast; // Default: 1.0
11+
float uSaturation; // Default: 1.0
12+
float uVignetteIntensity; // Default: 0.5
13+
float uVignetteFalloff; // Default 0.5
14+
};
15+
16+
// ACES Filmic Tone Mapping Curve
17+
vec3 tonemapACES(vec3 x) {
18+
const float a = 2.51;
19+
const float b = 0.03;
20+
const float c = 2.43;
21+
const float d = 0.59;
22+
const float e = 0.14;
23+
return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);
24+
}
25+
26+
void main() {
27+
// Sample the HDR input image
28+
vec3 hdrColor = texture(uInputImage, fragUV).rgb;
29+
30+
// Apply exposure
31+
vec3 exposedColor = hdrColor * uExposure;
32+
33+
// Tonemap (Reinhard)
34+
vec3 ldrColor = tonemapACES(exposedColor);
35+
36+
// Apply Color Grading
37+
vec3 finalColor = ldrColor;
38+
39+
// Contrast
40+
finalColor = pow(finalColor, vec3(uContrast));
41+
42+
// Saturation
43+
vec3 grayscale = vec3(dot(finalColor, vec3(0.299, 0.587, 0.114)));
44+
finalColor = mix(grayscale, finalColor, uSaturation);
45+
46+
outColor = vec4(finalColor, 1.0);
47+
}

shaders/composite.frag.spv

2.59 KB
Binary file not shown.

shaders/composite.vert

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#version 450
2+
3+
layout(location = 0) out vec2 fragUV;
4+
5+
void main() {
6+
// Fullscreen triangle using gl_VertexIndex (0, 1, 2)
7+
const vec2 pos[3] = vec2[](
8+
vec2(-1.0, -1.0), // bottom-left
9+
vec2( 3.0, -1.0), // bottom-right far (for covering entire screen)
10+
vec2(-1.0, 3.0) // top-left far (for covering entire screen)
11+
);
12+
const vec2 uv[3] = vec2[](
13+
vec2(0.0, 0.0),
14+
vec2(2.0, 0.0),
15+
vec2(0.0, 2.0)
16+
);
17+
18+
fragUV = uv[gl_VertexIndex];
19+
gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0);
20+
}

shaders/composite.vert.spv

1.42 KB
Binary file not shown.

src/DescriptorSet.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,8 @@ namespace reactor {
5757

5858
m_device.updateDescriptorSets(1, &write, 0, nullptr);
5959
}
60+
61+
void DescriptorSet::updateSet(const std::vector<vk::WriteDescriptorSet> &writes) {
62+
m_device.updateDescriptorSets(static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
63+
}
6064
} // reactor

src/DescriptorSet.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ namespace reactor {
2424
[[nodiscard]] vk::DescriptorSet getCurrentSet(size_t currentFrame) const { return m_sets[currentFrame]; }
2525

2626
// helper for updating sets
27-
void updateSet(size_t frame, const std::vector<vk::WriteDescriptorSet>& writes);
27+
void updateSet(const std::vector<vk::WriteDescriptorSet>& writes);
2828

2929
void updateUniformBuffer(size_t frame, const Buffer& buffer);
3030

src/ImageStateTracker.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//
2+
// Created by rfdic on 6/29/2025.
3+
//
4+
5+
#include "ImageStateTracker.h"
6+
#include <stdexcept>
7+
8+
namespace reactor {
9+
10+
void ImageStateTracker::recordState(vk::Image image, vk::ImageLayout initialState) {
11+
m_imageStates[image] = initialState;
12+
}
13+
14+
vk::ImageLayout ImageStateTracker::getCurrentLayout(vk::Image image) const {
15+
auto it = m_imageStates.find(image);
16+
if (it == m_imageStates.end()) {
17+
// Or you could default to eUndefined and record it, depending on desired strictness
18+
throw std::runtime_error("Attempted to get layout of an untracked image.");
19+
}
20+
return it->second;
21+
}
22+
23+
void ImageStateTracker::transition(
24+
vk::CommandBuffer cmd,
25+
vk::Image image,
26+
vk::ImageLayout newLayout,
27+
vk::PipelineStageFlags srcStage,
28+
vk::PipelineStageFlags dstStage,
29+
vk::AccessFlags srcAccess,
30+
vk::AccessFlags dstAccess,
31+
vk::ImageAspectFlags aspectFlags
32+
) {
33+
const vk::ImageLayout oldLayout = getCurrentLayout(image);
34+
35+
// If the layout is already correct, we don't need a barrier.
36+
if (oldLayout == newLayout) {
37+
return;
38+
}
39+
40+
vk::ImageMemoryBarrier barrier{};
41+
barrier.oldLayout = oldLayout;
42+
barrier.newLayout = newLayout;
43+
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
44+
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
45+
barrier.image = image;
46+
barrier.subresourceRange.aspectMask = aspectFlags;
47+
barrier.subresourceRange.baseMipLevel = 0;
48+
barrier.subresourceRange.levelCount = 1;
49+
barrier.subresourceRange.baseArrayLayer = 0;
50+
barrier.subresourceRange.layerCount = 1;
51+
barrier.srcAccessMask = srcAccess;
52+
barrier.dstAccessMask = dstAccess;
53+
54+
cmd.pipelineBarrier(
55+
srcStage, // Stage where operations occurred before the barrier
56+
dstStage, // Stage where operations will wait for the barrier
57+
{}, // Dependency flags
58+
nullptr, // Memory barriers
59+
nullptr, // Buffer memory barriers
60+
barrier // Image memory barriers
61+
);
62+
63+
// IMPORTANT: Update the internal state to the new layout.
64+
m_imageStates[image] = newLayout;
65+
}
66+
67+
68+
} // reactor

src/ImageStateTracker.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#pragma once
2+
3+
#include <vulkan/vulkan.hpp>
4+
#include <map>
5+
6+
namespace reactor {
7+
8+
class ImageStateTracker {
9+
public:
10+
ImageStateTracker() = default;
11+
12+
void recordState(vk::Image image, vk::ImageLayout initialState = vk::ImageLayout::eUndefined);
13+
14+
void transition(
15+
vk::CommandBuffer cmd,
16+
vk::Image image,
17+
vk::ImageLayout newLayout,
18+
vk::PipelineStageFlags srcStage,
19+
vk::PipelineStageFlags dstStage,
20+
vk::AccessFlags srcAccess,
21+
vk::AccessFlags dstAccess,
22+
vk::ImageAspectFlags aspectFlags = vk::ImageAspectFlagBits::eColor);
23+
24+
vk::ImageLayout getCurrentLayout(vk::Image image) const;
25+
26+
private:
27+
std::map<vk::Image, vk::ImageLayout> m_imageStates;
28+
};
29+
30+
} // namespace reactor
31+

0 commit comments

Comments
 (0)