diff --git a/engine/include/nova/render/render_driver.h b/engine/include/nova/render/render_driver.h index 497aeac..c3a99d9 100644 --- a/engine/include/nova/render/render_driver.h +++ b/engine/include/nova/render/render_driver.h @@ -15,6 +15,7 @@ #include namespace Nova { + class NOVA_API RenderDriver { public: static RenderDriver* create(RenderAPI api, WindowDriver* window_driver = nullptr); @@ -32,12 +33,6 @@ namespace Nova { [[nodiscard]] virtual SurfaceID create_surface(WindowID window) = 0; virtual void destroy_surface(SurfaceID surface) = 0; - // TODO: get_surface_size - // TODO: get_surface_mode - // TODO: get_surface_state - // TODO: set_surface_size - // TODO: set_surface_mode - // TODO: set_surface_state [[nodiscard]] virtual SwapchainID create_swapchain(SurfaceID surface) = 0; virtual void resize_swapchain(SwapchainID swapchain) = 0; @@ -45,5 +40,9 @@ namespace Nova { [[nodiscard]] virtual ShaderID create_shader(const std::span bytes) = 0; virtual void destroy_shader(ShaderID shader) = 0; + + [[nodiscard]] virtual PipelineID create_pipeline(GraphicsPipelineParams& params) = 0; + [[nodiscard]] virtual PipelineID create_pipeline(ComputePipelineParams& params) = 0; + virtual void destroy_pipeline(PipelineID pipeline) = 0; }; } // namespace Nova diff --git a/engine/include/nova/render/render_fwd.h b/engine/include/nova/render/render_fwd.h index 1514b84..73a8e8f 100644 --- a/engine/include/nova/render/render_fwd.h +++ b/engine/include/nova/render/render_fwd.h @@ -7,15 +7,27 @@ #pragma once namespace Nova { + enum class CullMode { NONE, FRONT, BACK }; + enum class FrontFace { CLOCKWISE, COUNTER_CLOCKWISE }; + enum class PipelineType { GRAPHICS, COMPUTE }; + enum class PrimitiveTopology { POINT_LIST, LINE_LIST, LINE_STRIP, TRIANGLE_LIST, TRIANGLE_STRIP }; enum class RenderAPI { DX12, VULKAN }; + enum class ShaderStage { VERTEX, FRAGMENT, GEOMETRY, TESS_CONTROL, TESS_EVAL, COMPUTE, MESH, TASK }; class RenderDriver; struct RenderDevice; + struct GraphicsPipelineParams; + struct ComputePipelineParams; + + struct Pipeline; + struct RenderPass; struct Shader; struct Surface; struct Swapchain; + using PipelineID = Pipeline*; + using RenderPassID = RenderPass*; using ShaderID = Shader*; using SurfaceID = Surface*; using SwapchainID = Swapchain*; diff --git a/engine/include/nova/render/render_params.h b/engine/include/nova/render/render_params.h new file mode 100644 index 0000000..2b3629c --- /dev/null +++ b/engine/include/nova/render/render_params.h @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2025, Jayden Grubb + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include +#include + +#include + +namespace Nova { + struct GraphicsPipelineParams { + std::unordered_map shaders; + + // TODO: Vertex input state + + PrimitiveTopology topology = PrimitiveTopology::TRIANGLE_LIST; + + // TODO: Tessellation state + + bool enable_depth_clamp = false; + bool discard_primitives = false; + bool wireframe = false; + CullMode cull_mode = CullMode::NONE; + FrontFace front_face = FrontFace::COUNTER_CLOCKWISE; + bool enable_depth_bias = false; + float depth_bias_constant = 0.0f; + float depth_bias_clamp = 0.0f; + float depth_bias_slope = 0.0f; + float line_width = 1.0f; + + // TODO: Multisample state + // TODO: Depth stencil state + // TODO: Color blend state + // TODO: Dynamic state + + RenderPassID render_pass = nullptr; + u32 subpass = 0; + }; + struct ComputePipelineParams {}; +} // namespace Nova diff --git a/engine/src/drivers/vulkan/render_driver.cpp b/engine/src/drivers/vulkan/render_driver.cpp index 7b5a9e6..ce6cc79 100644 --- a/engine/src/drivers/vulkan/render_driver.cpp +++ b/engine/src/drivers/vulkan/render_driver.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,40 @@ using namespace Nova; +// clang-format off + +static constexpr VkShaderStageFlagBits VK_SHADER_STAGE_MAP[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT, + VK_SHADER_STAGE_GEOMETRY_BIT, + VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, + VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, + VK_SHADER_STAGE_COMPUTE_BIT, + VK_SHADER_STAGE_MESH_BIT_EXT, + VK_SHADER_STAGE_TASK_BIT_EXT +}; + +static constexpr VkPrimitiveTopology VK_PRIMITIVE_TOPOLOGY_MAP[] = { + VK_PRIMITIVE_TOPOLOGY_POINT_LIST, + VK_PRIMITIVE_TOPOLOGY_LINE_LIST, + VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP +}; + +static constexpr VkCullModeFlags VK_CULL_MODE_MAP[] = { + VK_CULL_MODE_NONE, + VK_CULL_MODE_FRONT_BIT, + VK_CULL_MODE_BACK_BIT +}; + +static constexpr VkFrontFace VK_FRONT_FACE_MAP[] = { + VK_FRONT_FACE_COUNTER_CLOCKWISE, + VK_FRONT_FACE_CLOCKWISE +}; + +// clang-format on + VulkanRenderDriver::VulkanRenderDriver(WindowDriver* p_driver) : m_window_driver(p_driver) { NOVA_AUTO_TRACE(); _check_version(); @@ -320,6 +355,151 @@ void VulkanRenderDriver::destroy_shader(ShaderID p_shader) { delete p_shader; } +PipelineID VulkanRenderDriver::create_pipeline(GraphicsPipelineParams& p_params) { + NOVA_AUTO_TRACE(); + NOVA_ASSERT(p_params.render_pass); + + std::vector shader_stages; + for (const auto& [stage, shader] : p_params.shaders) { + VkPipelineShaderStageCreateInfo stage_create {}; + stage_create.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stage_create.stage = VK_SHADER_STAGE_MAP[static_cast(stage)]; + stage_create.module = shader->handle; + stage_create.pName = "main"; // TODO: Get from shader + // TODO: Get specialization info from shader + shader_stages.push_back(stage_create); + } + + // TODO: Properly set up vertex input state + VkPipelineVertexInputStateCreateInfo vertex_input {}; + vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertex_input.vertexBindingDescriptionCount = 0; + vertex_input.vertexAttributeDescriptionCount = 0; + + VkPipelineInputAssemblyStateCreateInfo input_assembly {}; + input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_MAP[static_cast(p_params.topology)]; + input_assembly.primitiveRestartEnable = VK_FALSE; + + // TODO: Tessellation state + + VkPipelineViewportStateCreateInfo viewport {}; + viewport.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewport.viewportCount = 1; // TODO: Support VR + viewport.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterization {}; + rasterization.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterization.depthClampEnable = p_params.enable_depth_clamp; + rasterization.rasterizerDiscardEnable = p_params.discard_primitives; + rasterization.polygonMode = p_params.wireframe ? VK_POLYGON_MODE_LINE : VK_POLYGON_MODE_FILL; + rasterization.cullMode = VK_CULL_MODE_MAP[static_cast(p_params.cull_mode)]; + rasterization.frontFace = VK_FRONT_FACE_MAP[static_cast(p_params.front_face)]; + rasterization.depthBiasEnable = p_params.enable_depth_bias; + rasterization.depthBiasConstantFactor = p_params.depth_bias_constant; + rasterization.depthBiasClamp = p_params.depth_bias_clamp; + rasterization.depthBiasSlopeFactor = p_params.depth_bias_slope; + rasterization.lineWidth = p_params.line_width; + + VkPipelineMultisampleStateCreateInfo multisample {}; + multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisample.sampleShadingEnable = VK_FALSE; // TODO: Support MSAA + multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; // TODO: Support MSAA + + // TODO: Depth stencil state + + // TODO: Properly set up color blend state + std::vector attachments; + VkPipelineColorBlendStateCreateInfo color_blend {}; + color_blend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + color_blend.logicOpEnable = VK_FALSE; + color_blend.logicOp = VK_LOGIC_OP_COPY; + color_blend.attachmentCount = static_cast(attachments.size()); + color_blend.pAttachments = attachments.data(); + color_blend.blendConstants[0] = 0.0f; + color_blend.blendConstants[1] = 0.0f; + color_blend.blendConstants[2] = 0.0f; + color_blend.blendConstants[3] = 0.0f; + + std::vector dynamic_states; + dynamic_states.push_back(VK_DYNAMIC_STATE_VIEWPORT); + dynamic_states.push_back(VK_DYNAMIC_STATE_SCISSOR); + // TODO: Add more dynamic states + VkPipelineDynamicStateCreateInfo dynamic_state {}; + dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamic_state.dynamicStateCount = static_cast(dynamic_states.size()); + dynamic_state.pDynamicStates = dynamic_states.data(); + + Pipeline* pipeline = new Pipeline(); + + // TODO: Move this to the shader + VkPipelineLayoutCreateInfo layout_create {}; + layout_create.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + layout_create.setLayoutCount = 0; // TODO: Add descriptor sets + layout_create.pushConstantRangeCount = 0; // TODO: Add push constants + if (vkCreatePipelineLayout(m_device, &layout_create, get_allocator(VK_OBJECT_TYPE_PIPELINE_LAYOUT), &pipeline->layout) + != VK_SUCCESS) { + throw std::runtime_error("Failed to create pipeline layout"); + } + + VkGraphicsPipelineCreateInfo pipeline_create {}; + pipeline_create.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipeline_create.stageCount = static_cast(shader_stages.size()); + pipeline_create.pStages = shader_stages.data(); + pipeline_create.pVertexInputState = &vertex_input; + pipeline_create.pInputAssemblyState = &input_assembly; + pipeline_create.pTessellationState = nullptr; // TODO: Add tessellation state + pipeline_create.pViewportState = &viewport; + pipeline_create.pRasterizationState = &rasterization; + pipeline_create.pMultisampleState = &multisample; + pipeline_create.pDepthStencilState = nullptr; // TODO: Add depth stencil state + pipeline_create.pColorBlendState = &color_blend; + pipeline_create.pDynamicState = &dynamic_state; + pipeline_create.layout = pipeline->layout; + pipeline_create.renderPass = p_params.render_pass->handle; + pipeline_create.subpass = p_params.subpass; + + if (vkCreateGraphicsPipelines( + m_device, + VK_NULL_HANDLE, + 1, + &pipeline_create, + get_allocator(VK_OBJECT_TYPE_PIPELINE), + &pipeline->handle + ) + != VK_SUCCESS) { + throw std::runtime_error("Failed to create graphics pipeline"); + } + return pipeline; +} + +PipelineID VulkanRenderDriver::create_pipeline(ComputePipelineParams& p_params) { + NOVA_AUTO_TRACE(); + Pipeline* pipeline = new Pipeline(); + (void)p_params; + + VkComputePipelineCreateInfo create {}; + create.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; + + if (vkCreateComputePipelines(m_device, VK_NULL_HANDLE, 1, &create, get_allocator(VK_OBJECT_TYPE_PIPELINE), &pipeline->handle) + != VK_SUCCESS) { + throw std::runtime_error("Failed to create compute pipeline"); + } + return pipeline; +} + +void VulkanRenderDriver::destroy_pipeline(PipelineID p_pipeline) { + NOVA_AUTO_TRACE(); + NOVA_ASSERT(p_pipeline); + if (p_pipeline->layout) { + vkDestroyPipelineLayout(m_device, p_pipeline->layout, get_allocator(VK_OBJECT_TYPE_PIPELINE_LAYOUT)); + } + if (p_pipeline->handle) { + vkDestroyPipeline(m_device, p_pipeline->handle, get_allocator(VK_OBJECT_TYPE_PIPELINE)); + } + delete p_pipeline; +} + VkInstance VulkanRenderDriver::get_instance() const { return m_instance; } diff --git a/engine/src/drivers/vulkan/render_driver.h b/engine/src/drivers/vulkan/render_driver.h index 55c302b..9880ccf 100644 --- a/engine/src/drivers/vulkan/render_driver.h +++ b/engine/src/drivers/vulkan/render_driver.h @@ -14,6 +14,16 @@ #include namespace Nova { + struct Pipeline { + PipelineType type; + VkPipeline handle = VK_NULL_HANDLE; + VkPipelineLayout layout = VK_NULL_HANDLE; + }; + + struct RenderPass { + VkRenderPass handle = VK_NULL_HANDLE; + }; + struct Shader { VkShaderModule handle = VK_NULL_HANDLE; }; @@ -60,6 +70,10 @@ namespace Nova { [[nodiscard]] ShaderID create_shader(const std::span bytes) override; void destroy_shader(ShaderID shader) override; + [[nodiscard]] PipelineID create_pipeline(GraphicsPipelineParams& params) override; + [[nodiscard]] PipelineID create_pipeline(ComputePipelineParams& params) override; + void destroy_pipeline(PipelineID pipeline) override; + [[nodiscard]] VkInstance get_instance() const; [[nodiscard]] VkAllocationCallbacks* get_allocator(VkObjectType type) const;