Skip to content

Commit 34439b0

Browse files
committed
Shader Objects
1 parent 39884df commit 34439b0

File tree

4 files changed

+240
-26
lines changed

4 files changed

+240
-26
lines changed

src/app.cpp

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ void App::run(std::string_view const assets_dir) {
4040
create_swapchain();
4141
create_render_sync();
4242
create_imgui();
43+
create_shader();
4344
create_pipeline_builder();
4445

4546
create_pipelines();
@@ -106,11 +107,16 @@ void App::create_device() {
106107
// and later device_ci.pNext => sync_feature.
107108
// this is 'pNext chaining'.
108109
sync_feature.setPNext(&dynamic_rendering_feature);
110+
auto shader_object_feature =
111+
vk::PhysicalDeviceShaderObjectFeaturesEXT{vk::True};
112+
dynamic_rendering_feature.setPNext(&shader_object_feature);
109113

110114
auto device_ci = vk::DeviceCreateInfo{};
111115
// we only need one device extension: Swapchain.
112-
static constexpr auto extensions_v =
113-
std::array{VK_KHR_SWAPCHAIN_EXTENSION_NAME};
116+
static constexpr auto extensions_v = std::array{
117+
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
118+
"VK_EXT_shader_object",
119+
};
114120
device_ci.setPEnabledExtensionNames(extensions_v)
115121
.setQueueCreateInfos(queue_ci)
116122
.setPEnabledFeatures(&enabled_features)
@@ -178,6 +184,19 @@ void App::create_imgui() {
178184
m_imgui.emplace(imgui_ci);
179185
}
180186

187+
void App::create_shader() {
188+
auto const vertex_spirv =
189+
to_spir_v(asset_path("shader.vert").string().c_str());
190+
auto const fragment_spirv =
191+
to_spir_v(asset_path("shader.frag").string().c_str());
192+
auto const shader_ci = ShaderProgram::CreateInfo{
193+
.device = *m_device,
194+
.vertex_spirv = vertex_spirv,
195+
.fragment_spirv = fragment_spirv,
196+
};
197+
m_shader.emplace(shader_ci);
198+
}
199+
181200
void App::create_pipeline_builder() {
182201
auto const pipeline_builder_ci = PipelineBuilder::CreateInfo{
183202
.device = *m_device,
@@ -305,7 +324,7 @@ void App::render(vk::CommandBuffer const command_buffer) {
305324

306325
command_buffer.beginRendering(rendering_info);
307326
inspect();
308-
draw(render_area, command_buffer);
327+
draw(command_buffer);
309328
command_buffer.endRendering();
310329

311330
m_imgui->end_frame();
@@ -367,37 +386,23 @@ void App::inspect() {
367386

368387
ImGui::SetNextWindowSize({200.0f, 100.0f}, ImGuiCond_Once);
369388
if (ImGui::Begin("Inspect")) {
370-
ImGui::Checkbox("wireframe", &m_wireframe);
389+
if (ImGui::Checkbox("wireframe", &m_wireframe)) {
390+
m_shader->polygon_mode =
391+
m_wireframe ? vk::PolygonMode::eLine : vk::PolygonMode::eFill;
392+
}
371393
if (m_wireframe) {
372394
auto const& line_width_range =
373395
m_gpu.properties.limits.lineWidthRange;
374396
ImGui::SetNextItemWidth(100.0f);
375-
ImGui::DragFloat("line width", &m_line_width, 0.25f,
397+
ImGui::DragFloat("line width", &m_shader->line_width, 0.25f,
376398
line_width_range[0], line_width_range[1]);
377399
}
378400
}
379401
ImGui::End();
380402
}
381403

382-
void App::draw(vk::Rect2D const& render_area,
383-
vk::CommandBuffer const command_buffer) const {
384-
auto const pipeline =
385-
m_wireframe ? *m_pipelines.wireframe : *m_pipelines.standard;
386-
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
387-
// we are creating pipelines with dynamic viewport and scissor states.
388-
// they must be set here after binding (before drawing).
389-
auto viewport = vk::Viewport{};
390-
// flip the viewport about the X-axis (negative height):
391-
// https://www.saschawillems.de/blog/2019/03/29/flipping-the-vulkan-viewport/
392-
viewport.setX(0.0f)
393-
.setY(static_cast<float>(m_render_target->extent.height))
394-
.setWidth(static_cast<float>(m_render_target->extent.width))
395-
.setHeight(-viewport.y);
396-
command_buffer.setViewport(0, viewport);
397-
command_buffer.setScissor(0, render_area);
398-
// line width is also a dynamic state in our pipelines, but setting it is
399-
// not required (defaults to 1.0f).
400-
command_buffer.setLineWidth(m_line_width);
404+
void App::draw(vk::CommandBuffer const command_buffer) const {
405+
m_shader->bind(command_buffer, m_framebuffer_size);
401406
// current shader has hard-coded logic for 3 vertices.
402407
command_buffer.draw(3, 1, 0, 0);
403408
}

src/app.hpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <pipeline_builder.hpp>
55
#include <resource_buffering.hpp>
66
#include <scoped_waiter.hpp>
7+
#include <shader_program.hpp>
78
#include <swapchain.hpp>
89
#include <window.hpp>
910
#include <filesystem>
@@ -35,6 +36,7 @@ class App {
3536
void create_swapchain();
3637
void create_render_sync();
3738
void create_imgui();
39+
void create_shader();
3840
void create_pipeline_builder();
3941
void create_pipelines();
4042

@@ -52,8 +54,7 @@ class App {
5254
// ImGui code goes here.
5355
void inspect();
5456
// Issue draw calls here.
55-
void draw(vk::Rect2D const& render_area,
56-
vk::CommandBuffer command_buffer) const;
57+
void draw(vk::CommandBuffer command_buffer) const;
5758

5859
fs::path m_assets_dir{};
5960

@@ -84,6 +85,8 @@ class App {
8485
float m_line_width{1.0f};
8586
bool m_wireframe{};
8687

88+
std::optional<ShaderProgram> m_shader{};
89+
8790
glm::ivec2 m_framebuffer_size{};
8891
std::optional<RenderTarget> m_render_target{};
8992

src/shader_program.cpp

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
#include <shader_program.hpp>
2+
#include <fstream>
3+
#include <print>
4+
#include <stdexcept>
5+
6+
namespace lvk {
7+
namespace {
8+
constexpr auto to_vkbool(bool const value) {
9+
return value ? vk::True : vk::False;
10+
}
11+
} // namespace
12+
13+
ShaderProgram::ShaderProgram(CreateInfo const& create_info)
14+
: m_device(create_info.device) {
15+
static auto const create_shader_ci =
16+
[](std::span<std::uint32_t const> spirv) {
17+
auto ret = vk::ShaderCreateInfoEXT{};
18+
ret.setCodeSize(spirv.size_bytes())
19+
.setPCode(spirv.data())
20+
// set common parameters.
21+
.setCodeType(vk::ShaderCodeTypeEXT::eSpirv)
22+
.setPName("main");
23+
return ret;
24+
};
25+
26+
auto shader_cis = std::array{
27+
create_shader_ci(create_info.vertex_spirv),
28+
create_shader_ci(create_info.fragment_spirv),
29+
};
30+
shader_cis[0]
31+
.setStage(vk::ShaderStageFlagBits::eVertex)
32+
.setNextStage(vk::ShaderStageFlagBits::eFragment);
33+
shader_cis[1].setStage(vk::ShaderStageFlagBits::eFragment);
34+
35+
auto result = m_device.createShadersEXTUnique(shader_cis);
36+
if (result.result != vk::Result::eSuccess) {
37+
throw std::runtime_error{"Failed to create Shader Objects"};
38+
}
39+
m_shaders = std::move(result.value);
40+
}
41+
42+
void ShaderProgram::bind(vk::CommandBuffer const command_buffer,
43+
glm::ivec2 const framebuffer_size) const {
44+
set_viewport_scissor(command_buffer, framebuffer_size);
45+
set_static_states(command_buffer);
46+
set_common_states(command_buffer);
47+
set_vertex_states(command_buffer);
48+
set_fragment_states(command_buffer);
49+
bind_shaders(command_buffer);
50+
}
51+
52+
void ShaderProgram::set_viewport_scissor(vk::CommandBuffer const command_buffer,
53+
glm::ivec2 const framebuffer_size) {
54+
auto const fsize = glm::vec2{framebuffer_size};
55+
auto viewport = vk::Viewport{};
56+
// flip the viewport about the X-axis (negative height):
57+
// https://www.saschawillems.de/blog/2019/03/29/flipping-the-vulkan-viewport/
58+
viewport.setX(0.0f).setY(fsize.y).setWidth(fsize.x).setHeight(-fsize.y);
59+
command_buffer.setViewportWithCount(viewport);
60+
61+
auto const usize = glm::uvec2{framebuffer_size};
62+
auto const scissor =
63+
vk::Rect2D{vk::Offset2D{}, vk::Extent2D{usize.x, usize.y}};
64+
command_buffer.setScissorWithCount(scissor);
65+
}
66+
67+
void ShaderProgram::set_static_states(vk::CommandBuffer const command_buffer) {
68+
command_buffer.setRasterizerDiscardEnable(vk::False);
69+
command_buffer.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1);
70+
command_buffer.setSampleMaskEXT(vk::SampleCountFlagBits::e1, 0xff);
71+
command_buffer.setAlphaToCoverageEnableEXT(vk::False);
72+
command_buffer.setCullMode(vk::CullModeFlagBits::eNone);
73+
command_buffer.setFrontFace(vk::FrontFace::eCounterClockwise);
74+
command_buffer.setDepthBiasEnable(vk::False);
75+
command_buffer.setStencilTestEnable(vk::False);
76+
command_buffer.setPrimitiveRestartEnable(vk::False);
77+
command_buffer.setColorWriteMaskEXT(0, ~vk::ColorComponentFlags{});
78+
}
79+
80+
void ShaderProgram::set_common_states(
81+
vk::CommandBuffer const command_buffer) const {
82+
auto const depth_test = to_vkbool((flags & DepthTest) == DepthTest);
83+
command_buffer.setDepthWriteEnable(depth_test);
84+
command_buffer.setDepthTestEnable(depth_test);
85+
command_buffer.setDepthCompareOp(depth_compare_op);
86+
command_buffer.setPolygonModeEXT(polygon_mode);
87+
command_buffer.setLineWidth(line_width);
88+
}
89+
90+
void ShaderProgram::set_vertex_states(
91+
vk::CommandBuffer const command_buffer) const {
92+
command_buffer.setVertexInputEXT(m_vertex_input.bindings,
93+
m_vertex_input.attributes);
94+
command_buffer.setPrimitiveTopology(topology);
95+
}
96+
97+
void ShaderProgram::set_fragment_states(
98+
vk::CommandBuffer const command_buffer) const {
99+
auto const alpha_blend = to_vkbool((flags & AlphaBlend) == AlphaBlend);
100+
command_buffer.setColorBlendEnableEXT(0, alpha_blend);
101+
command_buffer.setColorBlendEquationEXT(0, color_blend_equation);
102+
}
103+
104+
void ShaderProgram::bind_shaders(vk::CommandBuffer const command_buffer) const {
105+
static constexpr auto stages_v = std::array{
106+
vk::ShaderStageFlagBits::eVertex,
107+
vk::ShaderStageFlagBits::eFragment,
108+
};
109+
auto const shaders = std::array{
110+
*m_shaders[0],
111+
*m_shaders[1],
112+
};
113+
command_buffer.bindShadersEXT(stages_v, shaders);
114+
}
115+
} // namespace lvk
116+
117+
auto lvk::to_spir_v(char const* path) -> std::vector<std::uint32_t> {
118+
// open the file at the end, to get the total size.
119+
auto file = std::ifstream{path, std::ios::binary | std::ios::ate};
120+
if (!file.is_open()) {
121+
std::println(stderr, "Failed to open file: '{}'", path);
122+
return {};
123+
}
124+
125+
auto const size = file.tellg();
126+
auto const usize = static_cast<std::uint64_t>(size);
127+
// file data must be uint32 aligned.
128+
if (usize % sizeof(std::uint32_t) != 0) {
129+
std::println(stderr, "Invalid SPIR-V size: {}", usize);
130+
return {};
131+
}
132+
133+
// seek to the beginning before reading.
134+
file.seekg({}, std::ios::beg);
135+
auto ret = std::vector<std::uint32_t>{};
136+
ret.resize(usize / sizeof(std::uint32_t));
137+
void* data = ret.data();
138+
file.read(static_cast<char*>(data), size);
139+
return ret;
140+
}

src/shader_program.hpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#pragma once
2+
#include <vulkan/vulkan.hpp>
3+
#include <vector>
4+
5+
namespace lvk {
6+
struct ShaderVertexInput {
7+
std::span<vk::VertexInputAttributeDescription2EXT const> attributes{};
8+
std::span<vk::VertexInputBindingDescription2EXT const> bindings{};
9+
};
10+
11+
struct ShaderProgramCreateInfo {
12+
vk::Device device;
13+
std::span<std::uint32_t const> vertex_spirv;
14+
std::span<std::uint32_t const> fragment_spirv;
15+
ShaderVertexInput vertex_input{};
16+
};
17+
18+
class ShaderProgram {
19+
public:
20+
// bit flags for various binary states.
21+
enum : std::uint8_t {
22+
None = 0,
23+
AlphaBlend = 1 << 0, // turn on alpha blending.
24+
DepthTest = 1 << 1, // turn on depth write and test.
25+
};
26+
27+
static constexpr auto color_blend_equation_v = [] {
28+
auto ret = vk::ColorBlendEquationEXT{};
29+
ret.setColorBlendOp(vk::BlendOp::eAdd)
30+
.setSrcColorBlendFactor(vk::BlendFactor::eSrcAlpha)
31+
.setDstColorBlendFactor(vk::BlendFactor::eOneMinusSrcAlpha);
32+
return ret;
33+
}();
34+
35+
static constexpr auto flags_v = AlphaBlend | DepthTest;
36+
37+
using CreateInfo = ShaderProgramCreateInfo;
38+
39+
explicit ShaderProgram(CreateInfo const& create_info);
40+
41+
void bind(vk::CommandBuffer command_buffer,
42+
glm::ivec2 framebuffer_size) const;
43+
44+
vk::PrimitiveTopology topology{vk::PrimitiveTopology::eTriangleList};
45+
vk::PolygonMode polygon_mode{vk::PolygonMode::eFill};
46+
float line_width{1.0f};
47+
vk::ColorBlendEquationEXT color_blend_equation{color_blend_equation_v};
48+
vk::CompareOp depth_compare_op{vk::CompareOp::eLessOrEqual};
49+
std::uint8_t flags{flags_v};
50+
51+
private:
52+
static void set_viewport_scissor(vk::CommandBuffer command_buffer,
53+
glm::ivec2 framebuffer_size);
54+
static void set_static_states(vk::CommandBuffer command_buffer);
55+
void set_common_states(vk::CommandBuffer command_buffer) const;
56+
void set_vertex_states(vk::CommandBuffer command_buffer) const;
57+
void set_fragment_states(vk::CommandBuffer command_buffer) const;
58+
void bind_shaders(vk::CommandBuffer command_buffer) const;
59+
60+
vk::Device m_device{};
61+
ShaderVertexInput m_vertex_input{};
62+
std::vector<vk::UniqueShaderEXT> m_shaders{};
63+
};
64+
65+
[[nodiscard]] auto to_spir_v(char const* path) -> std::vector<std::uint32_t>;
66+
} // namespace lvk

0 commit comments

Comments
 (0)