Created
October 26, 2018 16:11
-
-
Save Noofbiz/de83517b848f153750a43346dbdcd3e9 to your computer and use it in GitHub Desktop.
Drew a triangle with Vulkan!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"errors" | |
"fmt" | |
"io/ioutil" | |
"math" | |
"os" | |
"runtime" | |
"time" | |
"unsafe" | |
"github.com/veandco/go-sdl2/sdl" | |
vk "github.com/vulkan-go/vulkan" | |
) | |
type swapChainSupportDetails struct { | |
capabilities vk.SurfaceCapabilities | |
formats []vk.SurfaceFormat | |
presentModes []vk.PresentMode | |
} | |
type vulkanApp struct { | |
w *sdl.Window | |
i vk.Instance | |
pd vk.PhysicalDevice | |
graphicsIdx uint32 | |
graphicsQueue vk.Queue | |
presentIdx uint32 | |
presentQueue vk.Queue | |
d vk.Device | |
s vk.Surface | |
enabledExtensions []string | |
details swapChainSupportDetails | |
swapChain vk.Swapchain | |
images []vk.Image | |
swapChainImageFormat vk.Format | |
swapChainExtent vk.Extent2D | |
swapChainImageViews []vk.ImageView | |
renderPass vk.RenderPass | |
pipelineLayout vk.PipelineLayout | |
graphicsPipelines []vk.Pipeline | |
swapChainFramebuffers []vk.Framebuffer | |
commandPool vk.CommandPool | |
commandBuffers []vk.CommandBuffer | |
imageAvailableSemaphore vk.Semaphore | |
renderFinishedSemaphore vk.Semaphore | |
} | |
func (v *vulkanApp) run() error { | |
defer v.cleanup() | |
if err := v.initWindow(); err != nil { | |
return err | |
} | |
if err := v.initVulkan(); err != nil { | |
return err | |
} | |
if err := v.mainLoop(); err != nil { | |
return err | |
} | |
return nil | |
} | |
func (v *vulkanApp) initWindow() error { | |
var err error | |
if err = sdl.Init(sdl.INIT_VIDEO | sdl.INIT_EVENTS); err != nil { | |
return err | |
} | |
if err = sdl.VulkanLoadLibrary(""); err != nil { | |
return err | |
} | |
vk.SetGetInstanceProcAddr(sdl.VulkanGetVkGetInstanceProcAddr()) | |
if err = vk.Init(); err != nil { | |
return err | |
} | |
v.w, err = sdl.CreateWindow("Vulkan Play", | |
sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, 800, 600, sdl.WINDOW_VULKAN) | |
if err != nil { | |
return err | |
} | |
return nil | |
} | |
func (v *vulkanApp) initVulkan() error { | |
if err := v.createInstance(); err != nil { | |
return err | |
} | |
if err := v.createSurface(); err != nil { | |
return err | |
} | |
if err := v.pickPhysicalDevice(); err != nil { | |
return err | |
} | |
if err := v.createLogicalDevice(); err != nil { | |
return err | |
} | |
if err := v.createSwapChain(); err != nil { | |
return err | |
} | |
if err := v.createImageViews(); err != nil { | |
return err | |
} | |
if err := v.createRenderPass(); err != nil { | |
return err | |
} | |
if err := v.createGraphicsPipeline(); err != nil { | |
return err | |
} | |
if err := v.createFrameBuffers(); err != nil { | |
return err | |
} | |
if err := v.createCommandPool(); err != nil { | |
return err | |
} | |
if err := v.createCommandBuffers(); err != nil { | |
return err | |
} | |
if err := v.createSemaphores(); err != nil { | |
return err | |
} | |
return nil | |
} | |
func (v *vulkanApp) createSurface() error { | |
surfPtr, err := v.w.VulkanCreateSurface(v.i) | |
v.s = vk.SurfaceFromPointer(surfPtr) | |
return err | |
} | |
func (v *vulkanApp) createLogicalDevice() error { | |
dqci := []vk.DeviceQueueCreateInfo{{ | |
SType: vk.StructureTypeDeviceQueueCreateInfo, | |
QueueFamilyIndex: v.graphicsIdx, | |
QueueCount: 1, | |
PQueuePriorities: []float32{1.0}, | |
}} | |
pdf := vk.PhysicalDeviceFeatures{} | |
ci := vk.DeviceCreateInfo{ | |
SType: vk.StructureTypeDeviceCreateInfo, | |
PQueueCreateInfos: dqci, | |
QueueCreateInfoCount: 1, | |
PEnabledFeatures: []vk.PhysicalDeviceFeatures{pdf}, | |
EnabledExtensionCount: uint32(len(v.enabledExtensions)), | |
PpEnabledExtensionNames: safeStrings(v.enabledExtensions), | |
EnabledLayerCount: 0, | |
} | |
if res := vk.CreateDevice(v.pd, &ci, nil, &v.d); res != vk.Success { | |
return errors.New("unable to create device") | |
} | |
vk.GetDeviceQueue(v.d, v.graphicsIdx, 0, &v.graphicsQueue) | |
vk.GetDeviceQueue(v.d, v.presentIdx, 0, &v.presentQueue) | |
return nil | |
} | |
func (v *vulkanApp) createInstance() error { | |
appInfo := vk.ApplicationInfo{ | |
SType: vk.StructureTypeApplicationInfo, | |
PApplicationName: "Hello Triangle", | |
ApplicationVersion: vk.MakeVersion(1, 0, 0), | |
PEngineName: "No Engine", | |
EngineVersion: vk.MakeVersion(1, 0, 0), | |
ApiVersion: vk.ApiVersion10, | |
} | |
createInfo := vk.InstanceCreateInfo{} | |
createInfo.SType = vk.StructureTypeInstanceCreateInfo | |
createInfo.PApplicationInfo = &appInfo | |
exts := v.w.VulkanGetInstanceExtensions() | |
createInfo.EnabledExtensionCount = uint32(len(exts)) | |
createInfo.PpEnabledExtensionNames = exts | |
if res := vk.CreateInstance(&createInfo, nil, &v.i); res != vk.Success { | |
return errors.New("unable to create vulkan instance") | |
} | |
if err := vk.InitInstance(v.i); err != nil { | |
return err | |
} | |
return nil | |
} | |
func (v *vulkanApp) pickPhysicalDevice() error { | |
var deviceCount uint32 | |
if res := vk.EnumeratePhysicalDevices(v.i, &deviceCount, nil); res != vk.Success { | |
return errors.New("unable to get physical devices") | |
} | |
devices := make([]vk.PhysicalDevice, deviceCount) | |
if res := vk.EnumeratePhysicalDevices(v.i, &deviceCount, devices); res != vk.Success { | |
return errors.New("unable to get physical devices") | |
} | |
var deviceSelected bool | |
for _, device := range devices { | |
if v.isDeviceSuitable(device) { | |
deviceSelected = true | |
v.pd = device | |
break | |
} | |
} | |
if !deviceSelected { | |
return errors.New("failed to find a sutible GPU") | |
} | |
return nil | |
} | |
func (v *vulkanApp) isDeviceSuitable(d vk.PhysicalDevice) bool { | |
var graphicsSupport, presentSupport bool | |
var queueFamilyPropertyCount uint32 | |
vk.GetPhysicalDeviceQueueFamilyProperties(d, &queueFamilyPropertyCount, nil) | |
if queueFamilyPropertyCount == 0 { | |
return false | |
} | |
queueFamilyProperties := make([]vk.QueueFamilyProperties, queueFamilyPropertyCount) | |
vk.GetPhysicalDeviceQueueFamilyProperties(d, &queueFamilyPropertyCount, queueFamilyProperties) | |
for i, q := range queueFamilyProperties { | |
q.Deref() | |
if q.QueueFlags&vk.QueueFlags(vk.QueueGraphicsBit) != 0 { | |
v.graphicsIdx = uint32(i) | |
graphicsSupport = true | |
} | |
var b32PresentSupport vk.Bool32 | |
vk.GetPhysicalDeviceSurfaceSupport(d, uint32(i), v.s, &b32PresentSupport) | |
if b32PresentSupport.B() { | |
presentSupport = true | |
v.presentIdx = uint32(i) | |
} | |
} | |
extensionSupport := v.checkDeviceExtensionSupport(d) | |
swapChainSupport := v.isSwapChainSupported(d) | |
return graphicsSupport && presentSupport && extensionSupport && swapChainSupport | |
} | |
func (v *vulkanApp) checkDeviceExtensionSupport(d vk.PhysicalDevice) bool { | |
v.enabledExtensions = []string{ | |
vk.KhrSwapchainExtensionName, | |
} | |
var extensionCount uint32 | |
vk.EnumerateDeviceExtensionProperties(d, "", &extensionCount, nil) | |
if extensionCount == 0 { | |
return false | |
} | |
availableExtensions := make([]vk.ExtensionProperties, extensionCount) | |
vk.EnumerateDeviceExtensionProperties(d, "", &extensionCount, availableExtensions) | |
for _, req := range v.enabledExtensions { | |
extensionFound := false | |
for _, ext := range availableExtensions { | |
ext.Deref() | |
if vk.ToString(ext.ExtensionName[:]) == req { | |
extensionFound = true | |
break | |
} | |
} | |
if !extensionFound { | |
return false | |
} | |
} | |
return true | |
} | |
func (v *vulkanApp) isSwapChainSupported(d vk.PhysicalDevice) bool { | |
if res := vk.GetPhysicalDeviceSurfaceCapabilities(d, v.s, &v.details.capabilities); res != vk.Success { | |
return false | |
} | |
var formatCount uint32 | |
vk.GetPhysicalDeviceSurfaceFormats(d, v.s, &formatCount, nil) | |
if formatCount == 0 { | |
return false | |
} | |
v.details.formats = make([]vk.SurfaceFormat, formatCount) | |
vk.GetPhysicalDeviceSurfaceFormats(d, v.s, &formatCount, v.details.formats) | |
var presentModeCount uint32 | |
vk.GetPhysicalDeviceSurfacePresentModes(d, v.s, &presentModeCount, nil) | |
if presentModeCount == 0 { | |
return false | |
} | |
v.details.presentModes = make([]vk.PresentMode, presentModeCount) | |
vk.GetPhysicalDeviceSurfacePresentModes(d, v.s, &presentModeCount, v.details.presentModes) | |
return true | |
} | |
func (v *vulkanApp) chooseSwapSurfaceFormat() vk.SurfaceFormat { | |
if len(v.details.formats) == 1 { | |
v.details.formats[0].Deref() | |
if v.details.formats[0].Format == vk.FormatUndefined { | |
return vk.SurfaceFormat{ | |
Format: vk.FormatB8g8r8Unorm, | |
ColorSpace: vk.ColorSpaceSrgbNonlinear, | |
} | |
} | |
} | |
for _, f := range v.details.formats { | |
f.Deref() | |
if f.Format == vk.FormatB8g8r8Unorm && f.ColorSpace == vk.ColorSpaceSrgbNonlinear { | |
return f | |
} | |
} | |
return v.details.formats[0] | |
} | |
func (v *vulkanApp) chooseSwapPresentMode() vk.PresentMode { | |
bestMode := vk.PresentModeFifo | |
for _, p := range v.details.presentModes { | |
if p == vk.PresentModeMailbox { | |
return p | |
} | |
if p == vk.PresentModeImmediate { | |
bestMode = p | |
} | |
} | |
return bestMode | |
} | |
func (v *vulkanApp) chooseSwapExtent() vk.Extent2D { | |
v.details.capabilities.Deref() | |
if v.details.capabilities.CurrentExtent.Width != math.MaxUint32 { | |
return v.details.capabilities.CurrentExtent | |
} | |
actualExtent := vk.Extent2D{ | |
Width: 800, | |
Height: 600, | |
} | |
actualExtent.Width = clamp(v.details.capabilities.MaxImageExtent.Width, | |
v.details.capabilities.MinImageExtent.Width, actualExtent.Width) | |
actualExtent.Height = clamp(v.details.capabilities.MaxImageExtent.Height, | |
v.details.capabilities.MinImageExtent.Height, actualExtent.Height) | |
return actualExtent | |
} | |
func clamp(high, low, value uint32) uint32 { | |
var ret uint32 | |
if value > high { | |
ret = high | |
} else { | |
ret = value | |
} | |
if ret < low { | |
return low | |
} | |
return ret | |
} | |
func (v *vulkanApp) createRenderPass() error { | |
colorAttachment := vk.AttachmentDescription{ | |
Format: v.swapChainImageFormat, | |
Samples: vk.SampleCount1Bit, | |
LoadOp: vk.AttachmentLoadOpClear, | |
StoreOp: vk.AttachmentStoreOpStore, | |
StencilLoadOp: vk.AttachmentLoadOpDontCare, | |
StencilStoreOp: vk.AttachmentStoreOpDontCare, | |
InitialLayout: vk.ImageLayoutUndefined, | |
FinalLayout: vk.ImageLayoutPresentSrc, | |
} | |
colorAttachmentRef := vk.AttachmentReference{ | |
Attachment: 0, | |
Layout: vk.ImageLayoutColorAttachmentOptimal, | |
} | |
subpass := vk.SubpassDescription{ | |
PipelineBindPoint: vk.PipelineBindPointGraphics, | |
ColorAttachmentCount: 1, | |
PColorAttachments: []vk.AttachmentReference{colorAttachmentRef}, | |
} | |
dependency := vk.SubpassDependency{ | |
SrcSubpass: vk.SubpassExternal, | |
DstSubpass: 0, | |
SrcStageMask: vk.PipelineStageFlags(vk.PipelineStageColorAttachmentOutputBit), | |
SrcAccessMask: 0, | |
DstStageMask: vk.PipelineStageFlags(vk.AccessColorAttachmentReadBit | vk.AccessColorAttachmentWriteBit), | |
} | |
renderPassInfo := vk.RenderPassCreateInfo{ | |
SType: vk.StructureTypeRenderPassCreateInfo, | |
AttachmentCount: 1, | |
PAttachments: []vk.AttachmentDescription{colorAttachment}, | |
SubpassCount: 1, | |
PSubpasses: []vk.SubpassDescription{subpass}, | |
DependencyCount: 1, | |
PDependencies: []vk.SubpassDependency{dependency}, | |
} | |
if res := vk.CreateRenderPass(v.d, &renderPassInfo, nil, &v.renderPass); res != vk.Success { | |
return errors.New("failed to create render pass") | |
} | |
return nil | |
} | |
func (v *vulkanApp) createSwapChain() error { | |
surfaceFormat := v.chooseSwapSurfaceFormat() | |
surfaceFormat.Deref() | |
presentMode := v.chooseSwapPresentMode() | |
extent := v.chooseSwapExtent() | |
extent.Deref() | |
v.details.capabilities.Deref() | |
imageCount := v.details.capabilities.MinImageCount + 1 | |
if v.details.capabilities.MaxImageCount > 0 { | |
if imageCount > v.details.capabilities.MaxImageCount { | |
imageCount = v.details.capabilities.MaxImageCount | |
} | |
} | |
createInfo := vk.SwapchainCreateInfo{ | |
SType: vk.StructureTypeSwapchainCreateInfo, | |
Surface: v.s, | |
MinImageCount: imageCount, | |
ImageFormat: surfaceFormat.Format, | |
ImageColorSpace: surfaceFormat.ColorSpace, | |
ImageExtent: extent, | |
ImageArrayLayers: 1, | |
ImageUsage: vk.ImageUsageFlags(vk.ImageUsageColorAttachmentBit), | |
} | |
if v.graphicsIdx != v.presentIdx { | |
createInfo.ImageSharingMode = vk.SharingModeConcurrent | |
createInfo.QueueFamilyIndexCount = 2 | |
createInfo.PQueueFamilyIndices = []uint32{v.graphicsIdx, v.presentIdx} | |
} else { | |
createInfo.ImageSharingMode = vk.SharingModeExclusive | |
createInfo.QueueFamilyIndexCount = 0 | |
createInfo.PQueueFamilyIndices = []uint32{} | |
} | |
createInfo.PreTransform = v.details.capabilities.CurrentTransform | |
createInfo.CompositeAlpha = vk.CompositeAlphaOpaqueBit | |
createInfo.PresentMode = presentMode | |
createInfo.Clipped = vk.True | |
createInfo.OldSwapchain = vk.Swapchain(vk.NullHandle) | |
if res := vk.CreateSwapchain(v.d, &createInfo, nil, &v.swapChain); res != vk.Success { | |
return errors.New("failed to create swap chain") | |
} | |
var numImgs uint32 | |
vk.GetSwapchainImages(v.d, v.swapChain, &numImgs, nil) | |
v.images = make([]vk.Image, numImgs) | |
if res := vk.GetSwapchainImages(v.d, v.swapChain, &numImgs, v.images); res != vk.Success { | |
return errors.New("failed to get swap chain images") | |
} | |
v.swapChainImageFormat = surfaceFormat.Format | |
v.swapChainExtent = extent | |
return nil | |
} | |
func (v *vulkanApp) createImageViews() error { | |
v.swapChainImageViews = make([]vk.ImageView, len(v.images)) | |
for i, image := range v.images { | |
createInfo := vk.ImageViewCreateInfo{ | |
SType: vk.StructureTypeImageViewCreateInfo, | |
Image: image, | |
ViewType: vk.ImageViewType2d, | |
Format: v.swapChainImageFormat, | |
} | |
createInfo.Components.R = vk.ComponentSwizzleIdentity | |
createInfo.Components.G = vk.ComponentSwizzleIdentity | |
createInfo.Components.B = vk.ComponentSwizzleIdentity | |
createInfo.Components.A = vk.ComponentSwizzleIdentity | |
createInfo.SubresourceRange.BaseMipLevel = 0 | |
createInfo.SubresourceRange.LevelCount = 1 | |
createInfo.SubresourceRange.BaseArrayLayer = 0 | |
createInfo.SubresourceRange.LayerCount = 1 | |
if res := vk.CreateImageView(v.d, &createInfo, nil, &v.swapChainImageViews[i]); res != vk.Success { | |
return errors.New("unable to create image view from swap chain images") | |
} | |
} | |
return nil | |
} | |
func (v *vulkanApp) createGraphicsPipeline() error { | |
vertShaderData, err := readFile("shaders/vert.spv") | |
if err != nil { | |
return err | |
} | |
fragShaderData, err := readFile("shaders/frag.spv") | |
if err != nil { | |
return err | |
} | |
vertShaderModule, err := v.loadShaderModule(vertShaderData) | |
if err != nil { | |
return err | |
} | |
fragShaderModule, err := v.loadShaderModule(fragShaderData) | |
if err != nil { | |
return err | |
} | |
vertShaderStageInfo := vk.PipelineShaderStageCreateInfo{ | |
SType: vk.StructureTypePipelineShaderStageCreateInfo, | |
Stage: vk.ShaderStageVertexBit, | |
Module: vertShaderModule, | |
PName: safeString("main"), | |
} | |
fragShaderStageInfo := vk.PipelineShaderStageCreateInfo{ | |
SType: vk.StructureTypePipelineShaderStageCreateInfo, | |
Stage: vk.ShaderStageFragmentBit, | |
Module: fragShaderModule, | |
PName: safeString("main"), | |
} | |
shaderStages := []vk.PipelineShaderStageCreateInfo{ | |
vertShaderStageInfo, | |
fragShaderStageInfo, | |
} | |
vertexInputInfo := vk.PipelineVertexInputStateCreateInfo{ | |
SType: vk.StructureTypePipelineVertexInputStateCreateInfo, | |
} | |
inputAssembly := vk.PipelineInputAssemblyStateCreateInfo{ | |
SType: vk.StructureTypePipelineInputAssemblyStateCreateInfo, | |
Topology: vk.PrimitiveTopologyTriangleList, | |
PrimitiveRestartEnable: vk.False, | |
} | |
viewport := vk.Viewport{ | |
X: 0, | |
Y: 0, | |
Width: float32(v.swapChainExtent.Width), | |
Height: float32(v.swapChainExtent.Height), | |
MinDepth: 0, | |
MaxDepth: 1, | |
} | |
scissor := vk.Rect2D{ | |
Offset: vk.Offset2D{ | |
X: 0, | |
Y: 0, | |
}, | |
Extent: v.swapChainExtent, | |
} | |
viewportState := vk.PipelineViewportStateCreateInfo{ | |
SType: vk.StructureTypePipelineViewportStateCreateInfo, | |
ViewportCount: 1, | |
PViewports: []vk.Viewport{viewport}, | |
ScissorCount: 1, | |
PScissors: []vk.Rect2D{scissor}, | |
} | |
rasterizer := vk.PipelineRasterizationStateCreateInfo{ | |
SType: vk.StructureTypePipelineRasterizationStateCreateInfo, | |
DepthClampEnable: vk.False, | |
RasterizerDiscardEnable: vk.False, | |
PolygonMode: vk.PolygonModeFill, | |
LineWidth: 1, | |
CullMode: vk.CullModeFlags(vk.CullModeBackBit), | |
FrontFace: vk.FrontFaceClockwise, | |
DepthBiasEnable: vk.False, | |
} | |
multisampling := vk.PipelineMultisampleStateCreateInfo{ | |
SType: vk.StructureTypePipelineMultisampleStateCreateInfo, | |
SampleShadingEnable: vk.False, | |
RasterizationSamples: vk.SampleCount1Bit, | |
MinSampleShading: 1, | |
AlphaToCoverageEnable: vk.False, | |
AlphaToOneEnable: vk.False, | |
} | |
colorBlendAttachment := vk.PipelineColorBlendAttachmentState{ | |
ColorWriteMask: vk.ColorComponentFlags(vk.ColorComponentRBit | vk.ColorComponentGBit | vk.ColorComponentBBit | vk.ColorComponentABit), | |
BlendEnable: vk.False, | |
SrcColorBlendFactor: vk.BlendFactorOne, | |
DstColorBlendFactor: vk.BlendFactorZero, | |
ColorBlendOp: vk.BlendOpAdd, | |
SrcAlphaBlendFactor: vk.BlendFactorOne, | |
DstAlphaBlendFactor: vk.BlendFactorZero, | |
AlphaBlendOp: vk.BlendOpAdd, | |
} | |
colorBlending := vk.PipelineColorBlendStateCreateInfo{ | |
SType: vk.StructureTypePipelineColorBlendStateCreateInfo, | |
LogicOpEnable: vk.False, | |
AttachmentCount: 1, | |
PAttachments: []vk.PipelineColorBlendAttachmentState{colorBlendAttachment}, | |
} | |
pipelineLayoutInfo := vk.PipelineLayoutCreateInfo{ | |
SType: vk.StructureTypePipelineLayoutCreateInfo, | |
} | |
if res := vk.CreatePipelineLayout(v.d, &pipelineLayoutInfo, nil, &v.pipelineLayout); res != vk.Success { | |
return errors.New("failed to create pipeline layout") | |
} | |
pipelineInfo := vk.GraphicsPipelineCreateInfo{ | |
SType: vk.StructureTypeGraphicsPipelineCreateInfo, | |
StageCount: 2, | |
PStages: shaderStages, | |
PVertexInputState: &vertexInputInfo, | |
PInputAssemblyState: &inputAssembly, | |
PViewportState: &viewportState, | |
PRasterizationState: &rasterizer, | |
PMultisampleState: &multisampling, | |
PColorBlendState: &colorBlending, | |
Layout: v.pipelineLayout, | |
RenderPass: v.renderPass, | |
Subpass: 0, | |
} | |
v.graphicsPipelines = make([]vk.Pipeline, 1) | |
if res := vk.CreateGraphicsPipelines(v.d, nil, 1, []vk.GraphicsPipelineCreateInfo{pipelineInfo}, nil, v.graphicsPipelines); res != vk.Success { | |
errors.New("failed to create graphics pipeline") | |
} | |
vk.DestroyShaderModule(v.d, vertShaderModule, nil) | |
vk.DestroyShaderModule(v.d, fragShaderModule, nil) | |
return nil | |
} | |
func readFile(url string) ([]byte, error) { | |
buf := make([]byte, 0) | |
f, err := os.Open(url) | |
if err != nil { | |
return buf, err | |
} | |
defer f.Close() | |
return ioutil.ReadAll(f) | |
} | |
func (v *vulkanApp) loadShaderModule(data []byte) (vk.ShaderModule, error) { | |
var module vk.ShaderModule | |
if res := vk.CreateShaderModule(v.d, &vk.ShaderModuleCreateInfo{ | |
SType: vk.StructureTypeShaderModuleCreateInfo, | |
CodeSize: uint(len(data)), | |
PCode: sliceUint32(data), | |
}, nil, &module); res != vk.Success { | |
return vk.NullShaderModule, errors.New("unable to create shader module") | |
} | |
return module, nil | |
} | |
func sliceUint32(data []byte) []uint32 { | |
const m = 0x7fffffff | |
return (*[m / 4]uint32)(unsafe.Pointer((*sliceHeader)(unsafe.Pointer(&data)).Data))[:len(data)/4] | |
} | |
type sliceHeader struct { | |
Data uintptr | |
Len int | |
Cap int | |
} | |
func (v *vulkanApp) createFrameBuffers() error { | |
v.swapChainFramebuffers = make([]vk.Framebuffer, len(v.swapChainImageViews)) | |
for idx, view := range v.swapChainImageViews { | |
attachments := []vk.ImageView{view} | |
framebufferInfo := vk.FramebufferCreateInfo{ | |
SType: vk.StructureTypeFramebufferCreateInfo, | |
RenderPass: v.renderPass, | |
AttachmentCount: 1, | |
PAttachments: attachments, | |
Width: v.swapChainExtent.Width, | |
Height: v.swapChainExtent.Height, | |
Layers: 1, | |
} | |
if res := vk.CreateFramebuffer(v.d, &framebufferInfo, nil, &v.swapChainFramebuffers[idx]); res != vk.Success { | |
return errors.New("failed to create framebuffer") | |
} | |
} | |
return nil | |
} | |
func (v *vulkanApp) createCommandPool() error { | |
poolInfo := vk.CommandPoolCreateInfo{ | |
SType: vk.StructureTypeCommandPoolCreateInfo, | |
QueueFamilyIndex: v.graphicsIdx, | |
} | |
if res := vk.CreateCommandPool(v.d, &poolInfo, nil, &v.commandPool); res != vk.Success { | |
return errors.New("failed to create command pool") | |
} | |
return nil | |
} | |
func (v *vulkanApp) createCommandBuffers() error { | |
v.commandBuffers = make([]vk.CommandBuffer, len(v.swapChainFramebuffers)) | |
allocInfo := vk.CommandBufferAllocateInfo{ | |
SType: vk.StructureTypeCommandBufferAllocateInfo, | |
CommandPool: v.commandPool, | |
Level: vk.CommandBufferLevelPrimary, | |
CommandBufferCount: uint32(len(v.commandBuffers)), | |
} | |
if res := vk.AllocateCommandBuffers(v.d, &allocInfo, v.commandBuffers); res != vk.Success { | |
errors.New("failed to allocate command buffers") | |
} | |
for idx, buffer := range v.commandBuffers { | |
beginInfo := vk.CommandBufferBeginInfo{ | |
SType: vk.StructureTypeCommandBufferBeginInfo, | |
Flags: vk.CommandBufferUsageFlags(vk.CommandBufferUsageSimultaneousUseBit), | |
} | |
if res := vk.BeginCommandBuffer(buffer, &beginInfo); res != vk.Success { | |
return errors.New("failed to begin recording command buffers") | |
} | |
clearValue := vk.NewClearValue([]float32{0, 0, 0, 1}) | |
renderPassInfo := vk.RenderPassBeginInfo{ | |
SType: vk.StructureTypeRenderPassBeginInfo, | |
RenderPass: v.renderPass, | |
Framebuffer: v.swapChainFramebuffers[idx], | |
ClearValueCount: 1, | |
PClearValues: []vk.ClearValue{clearValue}, | |
} | |
renderPassInfo.RenderArea.Offset = vk.Offset2D{X: 0, Y: 0} | |
renderPassInfo.RenderArea.Extent = v.swapChainExtent | |
vk.CmdBeginRenderPass(buffer, &renderPassInfo, vk.SubpassContentsInline) | |
vk.CmdBindPipeline(buffer, vk.PipelineBindPointGraphics, v.graphicsPipelines[0]) | |
vk.CmdDraw(buffer, 3, 1, 0, 0) | |
vk.CmdEndRenderPass(buffer) | |
if vk.EndCommandBuffer(buffer) != vk.Success { | |
return errors.New("failed to record command buffer!") | |
} | |
} | |
return nil | |
} | |
func (v *vulkanApp) createSemaphores() error { | |
semaphoreInfo := vk.SemaphoreCreateInfo{ | |
SType: vk.StructureTypeSemaphoreCreateInfo, | |
} | |
if vk.CreateSemaphore(v.d, &semaphoreInfo, nil, &v.imageAvailableSemaphore) != vk.Success { | |
return errors.New("failed to create image semaphore") | |
} | |
if vk.CreateSemaphore(v.d, &semaphoreInfo, nil, &v.renderFinishedSemaphore) != vk.Success { | |
return errors.New("failed to create render finished semaphore") | |
} | |
return nil | |
} | |
func (v *vulkanApp) mainLoop() error { | |
fpsDelay := time.Second / 60 | |
fpsTicker := time.NewTicker(fpsDelay) | |
MainLoop: | |
for { | |
select { | |
case <-fpsTicker.C: | |
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { | |
switch t := event.(type) { | |
case *sdl.KeyboardEvent: | |
if t.Keysym.Sym == sdl.K_ESCAPE { | |
break MainLoop | |
} | |
case *sdl.QuitEvent: | |
break MainLoop | |
} | |
} | |
if err := v.drawFrame(); err != nil { | |
panic(err) | |
} | |
} | |
} | |
vk.DeviceWaitIdle(v.d) | |
return nil | |
} | |
func (v *vulkanApp) drawFrame() error { | |
var imageIndex uint32 | |
vk.AcquireNextImage(v.d, v.swapChain, math.MaxUint64, v.imageAvailableSemaphore, vk.NullFence, &imageIndex) | |
waitSemaphores := []vk.Semaphore{v.imageAvailableSemaphore} | |
waitStages := []vk.PipelineStageFlags{vk.PipelineStageFlags(vk.PipelineStageColorAttachmentOutputBit)} | |
signalSemaphores := []vk.Semaphore{v.renderFinishedSemaphore} | |
submitInfo := []vk.SubmitInfo{vk.SubmitInfo{ | |
SType: vk.StructureTypeSubmitInfo, | |
WaitSemaphoreCount: 1, | |
PWaitSemaphores: waitSemaphores, | |
PWaitDstStageMask: waitStages, | |
CommandBufferCount: 1, | |
PCommandBuffers: []vk.CommandBuffer{v.commandBuffers[imageIndex]}, | |
SignalSemaphoreCount: 1, | |
PSignalSemaphores: signalSemaphores, | |
}} | |
if vk.QueueSubmit(v.graphicsQueue, 1, submitInfo, vk.NullFence) != vk.Success { | |
return errors.New("failed to submit draw command buffer!") | |
} | |
presentInfo := vk.PresentInfo{ | |
SType: vk.StructureTypePresentInfo, | |
WaitSemaphoreCount: 1, | |
PWaitSemaphores: signalSemaphores, | |
SwapchainCount: 1, | |
PSwapchains: []vk.Swapchain{v.swapChain}, | |
PImageIndices: []uint32{imageIndex}, | |
} | |
if vk.QueuePresent(v.presentQueue, &presentInfo) != vk.Success { | |
return errors.New("failed to present draw") | |
} | |
return nil | |
} | |
func (v *vulkanApp) cleanup() { | |
vk.DestroySemaphore(v.d, v.imageAvailableSemaphore, nil) | |
vk.DestroySemaphore(v.d, v.renderFinishedSemaphore, nil) | |
vk.DestroyCommandPool(v.d, v.commandPool, nil) | |
for _, framebuffer := range v.swapChainFramebuffers { | |
vk.DestroyFramebuffer(v.d, framebuffer, nil) | |
} | |
for _, pipeline := range v.graphicsPipelines { | |
vk.DestroyPipeline(v.d, pipeline, nil) | |
} | |
vk.DestroyPipelineLayout(v.d, v.pipelineLayout, nil) | |
vk.DestroyRenderPass(v.d, v.renderPass, nil) | |
for _, view := range v.swapChainImageViews { | |
vk.DestroyImageView(v.d, view, nil) | |
} | |
vk.DestroySwapchain(v.d, v.swapChain, nil) | |
vk.DestroySurface(v.i, v.s, nil) | |
vk.DestroyDevice(v.d, nil) | |
vk.DestroyInstance(v.i, nil) | |
v.w.Destroy() | |
sdl.VulkanUnloadLibrary() | |
sdl.Quit() | |
} | |
var end = "\x00" | |
var endChar byte = '\x00' | |
func safeString(s string) string { | |
if len(s) == 0 { | |
return end | |
} | |
if s[len(s)-1] != endChar { | |
return s + end | |
} | |
return s | |
} | |
func safeStrings(list []string) []string { | |
for i := range list { | |
list[i] = safeString(list[i]) | |
} | |
return list | |
} | |
func init() { | |
runtime.LockOSThread() | |
} | |
func main() { | |
v := new(vulkanApp) | |
if err := v.run(); err != nil { | |
fmt.Println(err) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment