|
| 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 | +} |
0 commit comments