diff --git a/asset/shaders/frag.spv b/asset/shaders/frag.spv new file mode 100644 index 0000000..8a4cc40 Binary files /dev/null and b/asset/shaders/frag.spv differ diff --git a/asset/shaders/shader.frag b/asset/shaders/shader.frag new file mode 100644 index 0000000..382f3f2 --- /dev/null +++ b/asset/shaders/shader.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(location=0) in vec3 fragColor; + +layout(location=0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} diff --git a/asset/shaders/shader.vert b/asset/shaders/shader.vert new file mode 100644 index 0000000..f5b2f8d --- /dev/null +++ b/asset/shaders/shader.vert @@ -0,0 +1,20 @@ +#version 450 + +layout(location = 0) out vec3 fragColor; + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +vec3 colors[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +} diff --git a/asset/shaders/vert.spv b/asset/shaders/vert.spv new file mode 100644 index 0000000..ef8a465 Binary files /dev/null and b/asset/shaders/vert.spv differ diff --git a/cmd/main.go b/cmd/main.go index 7be8baf..9b24425 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,6 +1,7 @@ package main import ( + "github.com/veandco/go-sdl2/sdl" "zworld/engine" "zworld/engine/core/zlog" "zworld/engine/render/vulkan/instance" @@ -26,7 +27,25 @@ func main() { err = vulkan.Init() if err != nil { zlog.Infof("err", err) + return } +appLoop: + for true { + for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { + switch event.(type) { + case *sdl.QuitEvent: + break appLoop + } + } + err := vulkan.DrawFrame() + if err != nil { + zlog.Infof("err", err) + break appLoop + } + } + + _, err = vulkan.WaitIdle() + zlog.Infof("err", err) for i := 1; i < 100; i++ { w.Tick() } diff --git a/engine/render/vulkan/instance/instance.go b/engine/render/vulkan/instance/instance.go index bcddaea..fec1772 100644 --- a/engine/render/vulkan/instance/instance.go +++ b/engine/render/vulkan/instance/instance.go @@ -16,7 +16,9 @@ type T interface { EnumeratePhysicalDevices() []core1_0.PhysicalDevice Destroy() Init() error + DrawFrame() error Ptr() core1_0.Instance + WaitIdle() (common.VkResult, error) } type FLoaderCreate func() *core.VulkanLoader type FInstance struct { @@ -38,6 +40,19 @@ type FInstance struct { swapchainExtent core1_0.Extent2D swapchainImageViews []core1_0.ImageView swapchainFramebuffers []core1_0.Framebuffer + + renderPass core1_0.RenderPass + pipelineLayout core1_0.PipelineLayout + graphicsPipeline core1_0.Pipeline + + commandPool core1_0.CommandPool + commandBuffers []core1_0.CommandBuffer + + imageAvailableSemaphore []core1_0.Semaphore + renderFinishedSemaphore []core1_0.Semaphore + inFlightFence []core1_0.Fence + imagesInFlight []core1_0.Fence + currentFrame int } func New(appName string, win *window.FSDLWindow, LoaderCreate FLoaderCreate) T { @@ -84,3 +99,57 @@ func (e *FInstance) EnumeratePhysicalDevices() []core1_0.PhysicalDevice { } return r } +func (e *FInstance) WaitIdle() (common.VkResult, error) { + return e.device.WaitIdle() +} +func (e *FInstance) DrawFrame() error { + fences := []core1_0.Fence{e.inFlightFence[e.currentFrame]} + + _, err := e.device.WaitForFences(true, common.NoTimeout, fences) + if err != nil { + return err + } + + imageIndex, _, err := e.swapchain.AcquireNextImage(common.NoTimeout, e.imageAvailableSemaphore[e.currentFrame], nil) + if err != nil { + return err + } + + if e.imagesInFlight[imageIndex] != nil { + _, err := e.device.WaitForFences(true, common.NoTimeout, []core1_0.Fence{e.imagesInFlight[imageIndex]}) + if err != nil { + return err + } + } + e.imagesInFlight[imageIndex] = e.inFlightFence[e.currentFrame] + + _, err = e.device.ResetFences(fences) + if err != nil { + return err + } + + _, err = e.graphicsQueue.Submit(e.inFlightFence[e.currentFrame], []core1_0.SubmitInfo{ + { + WaitSemaphores: []core1_0.Semaphore{e.imageAvailableSemaphore[e.currentFrame]}, + WaitDstStageMask: []core1_0.PipelineStageFlags{core1_0.PipelineStageColorAttachmentOutput}, + CommandBuffers: []core1_0.CommandBuffer{e.commandBuffers[imageIndex]}, + SignalSemaphores: []core1_0.Semaphore{e.renderFinishedSemaphore[e.currentFrame]}, + }, + }) + if err != nil { + return err + } + + _, err = e.swapchainExtension.QueuePresent(e.presentQueue, khr_swapchain.PresentInfo{ + WaitSemaphores: []core1_0.Semaphore{e.renderFinishedSemaphore[e.currentFrame]}, + Swapchains: []khr_swapchain.Swapchain{e.swapchain}, + ImageIndices: []int{imageIndex}, + }) + if err != nil { + return err + } + + e.currentFrame = (e.currentFrame + 1) % MaxFramesInFlight + + return nil +} diff --git a/engine/render/vulkan/instance/instance_init.go b/engine/render/vulkan/instance/instance_init.go index 02dc1e9..b2ca441 100644 --- a/engine/render/vulkan/instance/instance_init.go +++ b/engine/render/vulkan/instance/instance_init.go @@ -7,6 +7,7 @@ import ( "github.com/vkngwrapper/extensions/v2/khr_surface" "github.com/vkngwrapper/extensions/v2/khr_swapchain" vkng_sdl2 "github.com/vkngwrapper/integrations/sdl2/v2" + "os" ) func (e *FInstance) createSurface() error { @@ -178,6 +179,299 @@ func (e *FInstance) createSwapchain() error { return nil } +func (e *FInstance) createRenderPass() error { + renderPass, _, err := e.device.CreateRenderPass(nil, core1_0.RenderPassCreateInfo{ + Attachments: []core1_0.AttachmentDescription{ + { + Format: e.swapchainImageFormat, + Samples: core1_0.Samples1, + LoadOp: core1_0.AttachmentLoadOpClear, + StoreOp: core1_0.AttachmentStoreOpStore, + StencilLoadOp: core1_0.AttachmentLoadOpDontCare, + StencilStoreOp: core1_0.AttachmentStoreOpDontCare, + InitialLayout: core1_0.ImageLayoutUndefined, + FinalLayout: khr_swapchain.ImageLayoutPresentSrc, + }, + }, + Subpasses: []core1_0.SubpassDescription{ + { + PipelineBindPoint: core1_0.PipelineBindPointGraphics, + ColorAttachments: []core1_0.AttachmentReference{ + { + Attachment: 0, + Layout: core1_0.ImageLayoutColorAttachmentOptimal, + }, + }, + }, + }, + SubpassDependencies: []core1_0.SubpassDependency{ + { + SrcSubpass: core1_0.SubpassExternal, + DstSubpass: 0, + + SrcStageMask: core1_0.PipelineStageColorAttachmentOutput, + SrcAccessMask: 0, + + DstStageMask: core1_0.PipelineStageColorAttachmentOutput, + DstAccessMask: core1_0.AccessColorAttachmentWrite, + }, + }, + }) + if err != nil { + return err + } + + e.renderPass = renderPass + + return nil +} + +func (e *FInstance) createGraphicsPipeline() error { + // Load vertex shader + vertShaderBytes, err := os.ReadFile("asset/shaders/vert.spv") + if err != nil { + return err + } + + vertShader, _, err := e.device.CreateShaderModule(nil, core1_0.ShaderModuleCreateInfo{ + Code: bytesToBytecode(vertShaderBytes), + }) + if err != nil { + return err + } + defer vertShader.Destroy(nil) + + // Load fragment shader + fragShaderBytes, err := os.ReadFile("asset/shaders/frag.spv") + if err != nil { + return err + } + + fragShader, _, err := e.device.CreateShaderModule(nil, core1_0.ShaderModuleCreateInfo{ + Code: bytesToBytecode(fragShaderBytes), + }) + if err != nil { + return err + } + defer fragShader.Destroy(nil) + + vertexInput := &core1_0.PipelineVertexInputStateCreateInfo{} + + inputAssembly := &core1_0.PipelineInputAssemblyStateCreateInfo{ + Topology: core1_0.PrimitiveTopologyTriangleList, + PrimitiveRestartEnable: false, + } + + vertStage := core1_0.PipelineShaderStageCreateInfo{ + Stage: core1_0.StageVertex, + Module: vertShader, + Name: "main", + } + + fragStage := core1_0.PipelineShaderStageCreateInfo{ + Stage: core1_0.StageFragment, + Module: fragShader, + Name: "main", + } + + viewport := &core1_0.PipelineViewportStateCreateInfo{ + Viewports: []core1_0.Viewport{ + { + X: 0, + Y: 0, + Width: float32(e.swapchainExtent.Width), + Height: float32(e.swapchainExtent.Height), + MinDepth: 0, + MaxDepth: 1, + }, + }, + Scissors: []core1_0.Rect2D{ + { + Offset: core1_0.Offset2D{X: 0, Y: 0}, + Extent: e.swapchainExtent, + }, + }, + } + + rasterization := &core1_0.PipelineRasterizationStateCreateInfo{ + DepthClampEnable: false, + RasterizerDiscardEnable: false, + + PolygonMode: core1_0.PolygonModeFill, + CullMode: core1_0.CullModeBack, + FrontFace: core1_0.FrontFaceClockwise, + + DepthBiasEnable: false, + + LineWidth: 1.0, + } + + multisample := &core1_0.PipelineMultisampleStateCreateInfo{ + SampleShadingEnable: false, + RasterizationSamples: core1_0.Samples1, + MinSampleShading: 1.0, + } + + colorBlend := &core1_0.PipelineColorBlendStateCreateInfo{ + LogicOpEnabled: false, + LogicOp: core1_0.LogicOpCopy, + + BlendConstants: [4]float32{0, 0, 0, 0}, + Attachments: []core1_0.PipelineColorBlendAttachmentState{ + { + BlendEnabled: false, + ColorWriteMask: core1_0.ColorComponentRed | core1_0.ColorComponentGreen | core1_0.ColorComponentBlue | core1_0.ColorComponentAlpha, + }, + }, + } + + e.pipelineLayout, _, err = e.device.CreatePipelineLayout(nil, core1_0.PipelineLayoutCreateInfo{}) + if err != nil { + return err + } + + pipelines, _, err := e.device.CreateGraphicsPipelines(nil, nil, []core1_0.GraphicsPipelineCreateInfo{ + { + Stages: []core1_0.PipelineShaderStageCreateInfo{ + vertStage, + fragStage, + }, + VertexInputState: vertexInput, + InputAssemblyState: inputAssembly, + ViewportState: viewport, + RasterizationState: rasterization, + MultisampleState: multisample, + ColorBlendState: colorBlend, + Layout: e.pipelineLayout, + RenderPass: e.renderPass, + Subpass: 0, + BasePipelineIndex: -1, + }, + }) + if err != nil { + return err + } + e.graphicsPipeline = pipelines[0] + + return nil +} + +func (e *FInstance) createFramebuffers() error { + for _, imageView := range e.swapchainImageViews { + framebuffer, _, err := e.device.CreateFramebuffer(nil, core1_0.FramebufferCreateInfo{ + RenderPass: e.renderPass, + Layers: 1, + Attachments: []core1_0.ImageView{ + imageView, + }, + Width: e.swapchainExtent.Width, + Height: e.swapchainExtent.Height, + }) + if err != nil { + return err + } + + e.swapchainFramebuffers = append(e.swapchainFramebuffers, framebuffer) + } + + return nil +} + +func (e *FInstance) createCommandPool() error { + indices, err := help.findQueueFamilies(e.physicalDevice) + if err != nil { + return err + } + + pool, _, err := e.device.CreateCommandPool(nil, core1_0.CommandPoolCreateInfo{ + QueueFamilyIndex: *indices.GraphicsFamily, + }) + + if err != nil { + return err + } + e.commandPool = pool + + return nil +} + +func (e *FInstance) createCommandBuffers() error { + buffers, _, err := e.device.AllocateCommandBuffers(core1_0.CommandBufferAllocateInfo{ + CommandPool: e.commandPool, + Level: core1_0.CommandBufferLevelPrimary, + CommandBufferCount: len(e.swapchainImages), + }) + if err != nil { + return err + } + e.commandBuffers = buffers + + for bufferIdx, buffer := range buffers { + _, err = buffer.Begin(core1_0.CommandBufferBeginInfo{}) + if err != nil { + return err + } + + err = buffer.CmdBeginRenderPass(core1_0.SubpassContentsInline, + core1_0.RenderPassBeginInfo{ + RenderPass: e.renderPass, + Framebuffer: e.swapchainFramebuffers[bufferIdx], + RenderArea: core1_0.Rect2D{ + Offset: core1_0.Offset2D{X: 0, Y: 0}, + Extent: e.swapchainExtent, + }, + ClearValues: []core1_0.ClearValue{ + core1_0.ClearValueFloat{0, 0, 0, 1}, + }, + }) + if err != nil { + return err + } + + buffer.CmdBindPipeline(core1_0.PipelineBindPointGraphics, e.graphicsPipeline) + buffer.CmdDraw(3, 1, 0, 0) + buffer.CmdEndRenderPass() + + _, err = buffer.End() + if err != nil { + return err + } + } + + return nil +} +func (e *FInstance) createSyncObjects() error { + for i := 0; i < MaxFramesInFlight; i++ { + semaphore, _, err := e.device.CreateSemaphore(nil, core1_0.SemaphoreCreateInfo{}) + if err != nil { + return err + } + + e.imageAvailableSemaphore = append(e.imageAvailableSemaphore, semaphore) + + semaphore, _, err = e.device.CreateSemaphore(nil, core1_0.SemaphoreCreateInfo{}) + if err != nil { + return err + } + + e.renderFinishedSemaphore = append(e.renderFinishedSemaphore, semaphore) + + fence, _, err := e.device.CreateFence(nil, core1_0.FenceCreateInfo{ + Flags: core1_0.FenceCreateSignaled, + }) + if err != nil { + return err + } + + e.inFlightFence = append(e.inFlightFence, fence) + } + + for i := 0; i < len(e.swapchainImages); i++ { + e.imagesInFlight = append(e.imagesInFlight, nil) + } + + return nil +} func (e *FInstance) Init() error { help.vulkan = e err := e.createSurface() @@ -196,5 +490,25 @@ func (e *FInstance) Init() error { if err != nil { return err } - return nil + err = e.createRenderPass() + if err != nil { + return err + } + err = e.createGraphicsPipeline() + if err != nil { + return err + } + err = e.createFramebuffers() + if err != nil { + return err + } + err = e.createCommandPool() + if err != nil { + return err + } + err = e.createCommandBuffers() + if err != nil { + return err + } + return e.createSyncObjects() } diff --git a/engine/render/vulkan/instance/type.go b/engine/render/vulkan/instance/type.go index e627087..8912bcb 100644 --- a/engine/render/vulkan/instance/type.go +++ b/engine/render/vulkan/instance/type.go @@ -1,8 +1,23 @@ package instance const enableValidationLayers = true +const MaxFramesInFlight = 2 var ( help FVulkanHelp engineName = "goworld" ) + +func bytesToBytecode(b []byte) []uint32 { + byteCode := make([]uint32, len(b)/4) + for i := 0; i < len(byteCode); i++ { + byteIndex := i * 4 + byteCode[i] = 0 + byteCode[i] |= uint32(b[byteIndex]) + byteCode[i] |= uint32(b[byteIndex+1]) << 8 + byteCode[i] |= uint32(b[byteIndex+2]) << 16 + byteCode[i] |= uint32(b[byteIndex+3]) << 24 + } + + return byteCode +} diff --git a/main.exe b/main.exe new file mode 100644 index 0000000..ba89da2 Binary files /dev/null and b/main.exe differ