zvulkan/zvulkan-go/steps/11_render_passes/main.go
2023-09-17 21:01:13 +08:00

758 lines
19 KiB
Go

package main
import (
"embed"
"github.com/pkg/errors"
"github.com/veandco/go-sdl2/sdl"
"github.com/vkngwrapper/core/v2"
"github.com/vkngwrapper/core/v2/common"
"github.com/vkngwrapper/core/v2/core1_0"
"github.com/vkngwrapper/extensions/v2/ext_debug_utils"
"github.com/vkngwrapper/extensions/v2/khr_portability_enumeration"
"github.com/vkngwrapper/extensions/v2/khr_portability_subset"
"github.com/vkngwrapper/extensions/v2/khr_surface"
"github.com/vkngwrapper/extensions/v2/khr_swapchain"
vkng_sdl2 "github.com/vkngwrapper/integrations/sdl2/v2"
"log"
)
//go:embed shaders
var shaders embed.FS
var validationLayers = []string{"VK_LAYER_KHRONOS_validation"}
var deviceExtensions = []string{khr_swapchain.ExtensionName}
const enableValidationLayers = true
type QueueFamilyIndices struct {
GraphicsFamily *int
PresentFamily *int
}
func (i *QueueFamilyIndices) IsComplete() bool {
return i.GraphicsFamily != nil && i.PresentFamily != nil
}
type SwapChainSupportDetails struct {
Capabilities *khr_surface.SurfaceCapabilities
Formats []khr_surface.SurfaceFormat
PresentModes []khr_surface.PresentMode
}
type HelloTriangleApplication struct {
window *sdl.Window
loader core.Loader
instance core1_0.Instance
debugMessenger ext_debug_utils.DebugUtilsMessenger
surface khr_surface.Surface
physicalDevice core1_0.PhysicalDevice
device core1_0.Device
graphicsQueue core1_0.Queue
presentQueue core1_0.Queue
swapchainExtension khr_swapchain.Extension
swapchain khr_swapchain.Swapchain
swapchainImages []core1_0.Image
swapchainImageFormat core1_0.Format
swapchainExtent core1_0.Extent2D
swapchainImageViews []core1_0.ImageView
renderPass core1_0.RenderPass
pipelineLayout core1_0.PipelineLayout
}
func (app *HelloTriangleApplication) Run() error {
err := app.initWindow()
if err != nil {
return err
}
err = app.initVulkan()
if err != nil {
return err
}
defer app.cleanup()
return app.mainLoop()
}
func (app *HelloTriangleApplication) initWindow() error {
if err := sdl.Init(sdl.INIT_VIDEO); err != nil {
return err
}
window, err := sdl.CreateWindow("Vulkan", sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, 800, 600, sdl.WINDOW_SHOWN|sdl.WINDOW_VULKAN)
if err != nil {
return err
}
app.window = window
app.loader, err = core.CreateLoaderFromProcAddr(sdl.VulkanGetVkGetInstanceProcAddr())
if err != nil {
return err
}
return nil
}
func (app *HelloTriangleApplication) initVulkan() error {
err := app.createInstance()
if err != nil {
return err
}
err = app.setupDebugMessenger()
if err != nil {
return err
}
err = app.createSurface()
if err != nil {
return err
}
err = app.pickPhysicalDevice()
if err != nil {
return err
}
err = app.createLogicalDevice()
if err != nil {
return err
}
err = app.createSwapchain()
if err != nil {
return err
}
err = app.createRenderPass()
if err != nil {
return err
}
return app.createGraphicsPipeline()
}
func (app *HelloTriangleApplication) mainLoop() error {
appLoop:
for true {
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
switch event.(type) {
case *sdl.QuitEvent:
break appLoop
}
}
}
return nil
}
func (app *HelloTriangleApplication) cleanup() {
if app.pipelineLayout != nil {
app.pipelineLayout.Destroy(nil)
}
if app.renderPass != nil {
app.renderPass.Destroy(nil)
}
for _, imageView := range app.swapchainImageViews {
imageView.Destroy(nil)
}
if app.swapchain != nil {
app.swapchain.Destroy(nil)
}
if app.device != nil {
app.device.Destroy(nil)
}
if app.debugMessenger != nil {
app.debugMessenger.Destroy(nil)
}
if app.surface != nil {
app.surface.Destroy(nil)
}
if app.instance != nil {
app.instance.Destroy(nil)
}
if app.window != nil {
app.window.Destroy()
}
sdl.Quit()
}
func (app *HelloTriangleApplication) createInstance() error {
instanceOptions := core1_0.InstanceCreateInfo{
ApplicationName: "Hello Triangle",
ApplicationVersion: common.CreateVersion(1, 0, 0),
EngineName: "No Engine",
EngineVersion: common.CreateVersion(1, 0, 0),
APIVersion: common.Vulkan1_2,
}
// Add extensions
sdlExtensions := app.window.VulkanGetInstanceExtensions()
extensions, _, err := app.loader.AvailableExtensions()
if err != nil {
return err
}
for _, ext := range sdlExtensions {
_, hasExt := extensions[ext]
if !hasExt {
return errors.Errorf("createinstance: cannot initialize sdl: missing extension %s", ext)
}
instanceOptions.EnabledExtensionNames = append(instanceOptions.EnabledExtensionNames, ext)
}
if enableValidationLayers {
instanceOptions.EnabledExtensionNames = append(instanceOptions.EnabledExtensionNames, ext_debug_utils.ExtensionName)
}
_, enumerationSupported := extensions[khr_portability_enumeration.ExtensionName]
if enumerationSupported {
instanceOptions.EnabledExtensionNames = append(instanceOptions.EnabledExtensionNames, khr_portability_enumeration.ExtensionName)
instanceOptions.Flags |= khr_portability_enumeration.InstanceCreateEnumeratePortability
}
// Add layers
layers, _, err := app.loader.AvailableLayers()
if err != nil {
return err
}
if enableValidationLayers {
for _, layer := range validationLayers {
_, hasValidation := layers[layer]
if !hasValidation {
return errors.Errorf("createInstance: cannot add validation- layer %s not available- install LunarG Vulkan SDK", layer)
}
instanceOptions.EnabledLayerNames = append(instanceOptions.EnabledLayerNames, layer)
}
// Add debug messenger
instanceOptions.Next = app.debugMessengerOptions()
}
app.instance, _, err = app.loader.CreateInstance(nil, instanceOptions)
if err != nil {
return err
}
return nil
}
func (app *HelloTriangleApplication) debugMessengerOptions() ext_debug_utils.DebugUtilsMessengerCreateInfo {
return ext_debug_utils.DebugUtilsMessengerCreateInfo{
MessageSeverity: ext_debug_utils.SeverityError | ext_debug_utils.SeverityWarning,
MessageType: ext_debug_utils.TypeGeneral | ext_debug_utils.TypeValidation | ext_debug_utils.TypePerformance,
UserCallback: app.logDebug,
}
}
func (app *HelloTriangleApplication) setupDebugMessenger() error {
if !enableValidationLayers {
return nil
}
var err error
debugLoader := ext_debug_utils.CreateExtensionFromInstance(app.instance)
app.debugMessenger, _, err = debugLoader.CreateDebugUtilsMessenger(app.instance, nil, app.debugMessengerOptions())
if err != nil {
return err
}
return nil
}
func (app *HelloTriangleApplication) createSurface() error {
surfaceLoader := khr_surface.CreateExtensionFromInstance(app.instance)
surface, err := vkng_sdl2.CreateSurface(app.instance, surfaceLoader, app.window)
if err != nil {
return err
}
app.surface = surface
return nil
}
func (app *HelloTriangleApplication) pickPhysicalDevice() error {
physicalDevices, _, err := app.instance.EnumeratePhysicalDevices()
if err != nil {
return err
}
for _, device := range physicalDevices {
if app.isDeviceSuitable(device) {
app.physicalDevice = device
break
}
}
if app.physicalDevice == nil {
return errors.New("failed to find a suitable GPU!")
}
return nil
}
func (app *HelloTriangleApplication) createLogicalDevice() error {
indices, err := app.findQueueFamilies(app.physicalDevice)
if err != nil {
return err
}
uniqueQueueFamilies := []int{*indices.GraphicsFamily}
if uniqueQueueFamilies[0] != *indices.PresentFamily {
uniqueQueueFamilies = append(uniqueQueueFamilies, *indices.PresentFamily)
}
var queueFamilyOptions []core1_0.DeviceQueueCreateInfo
queuePriority := float32(1.0)
for _, queueFamily := range uniqueQueueFamilies {
queueFamilyOptions = append(queueFamilyOptions, core1_0.DeviceQueueCreateInfo{
QueueFamilyIndex: queueFamily,
QueuePriorities: []float32{queuePriority},
})
}
var extensionNames []string
extensionNames = append(extensionNames, deviceExtensions...)
// Makes this example compatible with vulkan portability, necessary to run on mobile & mac
extensions, _, err := app.physicalDevice.EnumerateDeviceExtensionProperties()
if err != nil {
return err
}
_, supported := extensions[khr_portability_subset.ExtensionName]
if supported {
extensionNames = append(extensionNames, khr_portability_subset.ExtensionName)
}
app.device, _, err = app.physicalDevice.CreateDevice(nil, core1_0.DeviceCreateInfo{
QueueCreateInfos: queueFamilyOptions,
EnabledFeatures: &core1_0.PhysicalDeviceFeatures{},
EnabledExtensionNames: extensionNames,
})
if err != nil {
return err
}
app.graphicsQueue = app.device.GetQueue(*indices.GraphicsFamily, 0)
app.presentQueue = app.device.GetQueue(*indices.PresentFamily, 0)
return nil
}
func (app *HelloTriangleApplication) createSwapchain() error {
app.swapchainExtension = khr_swapchain.CreateExtensionFromDevice(app.device)
swapchainSupport, err := app.querySwapChainSupport(app.physicalDevice)
if err != nil {
return err
}
surfaceFormat := app.chooseSwapSurfaceFormat(swapchainSupport.Formats)
presentMode := app.chooseSwapPresentMode(swapchainSupport.PresentModes)
extent := app.chooseSwapExtent(swapchainSupport.Capabilities)
imageCount := swapchainSupport.Capabilities.MinImageCount + 1
if swapchainSupport.Capabilities.MaxImageCount > 0 && swapchainSupport.Capabilities.MaxImageCount < imageCount {
imageCount = swapchainSupport.Capabilities.MaxImageCount
}
sharingMode := core1_0.SharingModeExclusive
var queueFamilyIndices []int
indices, err := app.findQueueFamilies(app.physicalDevice)
if err != nil {
return err
}
if *indices.GraphicsFamily != *indices.PresentFamily {
sharingMode = core1_0.SharingModeConcurrent
queueFamilyIndices = append(queueFamilyIndices, *indices.GraphicsFamily, *indices.PresentFamily)
}
swapchain, _, err := app.swapchainExtension.CreateSwapchain(app.device, nil, khr_swapchain.SwapchainCreateInfo{
Surface: app.surface,
MinImageCount: imageCount,
ImageFormat: surfaceFormat.Format,
ImageColorSpace: surfaceFormat.ColorSpace,
ImageExtent: extent,
ImageArrayLayers: 1,
ImageUsage: core1_0.ImageUsageColorAttachment,
ImageSharingMode: sharingMode,
QueueFamilyIndices: queueFamilyIndices,
PreTransform: swapchainSupport.Capabilities.CurrentTransform,
CompositeAlpha: khr_surface.CompositeAlphaOpaque,
PresentMode: presentMode,
Clipped: true,
})
if err != nil {
return err
}
app.swapchainExtent = extent
app.swapchain = swapchain
images, _, err := swapchain.SwapchainImages()
if err != nil {
return err
}
app.swapchainImages = images
var imageViews []core1_0.ImageView
for _, image := range images {
view, _, err := app.device.CreateImageView(nil, core1_0.ImageViewCreateInfo{
ViewType: core1_0.ImageViewType2D,
Image: image,
Format: surfaceFormat.Format,
Components: core1_0.ComponentMapping{
R: core1_0.ComponentSwizzleIdentity,
G: core1_0.ComponentSwizzleIdentity,
B: core1_0.ComponentSwizzleIdentity,
A: core1_0.ComponentSwizzleIdentity,
},
SubresourceRange: core1_0.ImageSubresourceRange{
AspectMask: core1_0.ImageAspectColor,
BaseMipLevel: 0,
LevelCount: 1,
BaseArrayLayer: 0,
LayerCount: 1,
},
})
if err != nil {
return err
}
imageViews = append(imageViews, view)
}
app.swapchainImageViews = imageViews
app.swapchainImageFormat = surfaceFormat.Format
return nil
}
func (app *HelloTriangleApplication) createRenderPass() error {
renderPass, _, err := app.device.CreateRenderPass(nil, core1_0.RenderPassCreateInfo{
Attachments: []core1_0.AttachmentDescription{
{
Format: app.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
}
app.renderPass = renderPass
return nil
}
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
}
func (app *HelloTriangleApplication) createGraphicsPipeline() error {
// Load vertex shader
vertShaderBytes, err := shaders.ReadFile("shaders/vert.spv")
if err != nil {
return err
}
vertShader, _, err := app.device.CreateShaderModule(nil, core1_0.ShaderModuleCreateInfo{
Code: bytesToBytecode(vertShaderBytes),
})
if err != nil {
return err
}
defer vertShader.Destroy(nil)
// Load fragment shader
fragShaderBytes, err := shaders.ReadFile("shaders/frag.spv")
if err != nil {
return err
}
fragShader, _, err := app.device.CreateShaderModule(nil, core1_0.ShaderModuleCreateInfo{
Code: bytesToBytecode(fragShaderBytes),
})
if err != nil {
return err
}
defer fragShader.Destroy(nil)
_ = &core1_0.PipelineVertexInputStateCreateInfo{}
_ = &core1_0.PipelineInputAssemblyStateCreateInfo{
Topology: core1_0.PrimitiveTopologyTriangleList,
PrimitiveRestartEnable: false,
}
_ = &core1_0.PipelineShaderStageCreateInfo{
Stage: core1_0.StageVertex,
Module: vertShader,
Name: "main",
}
_ = &core1_0.PipelineShaderStageCreateInfo{
Stage: core1_0.StageFragment,
Module: fragShader,
Name: "main",
}
_ = &core1_0.PipelineViewportStateCreateInfo{
Viewports: []core1_0.Viewport{
{
X: 0,
Y: 0,
Width: float32(app.swapchainExtent.Width),
Height: float32(app.swapchainExtent.Height),
MinDepth: 0,
MaxDepth: 1,
},
},
Scissors: []core1_0.Rect2D{
{
Offset: core1_0.Offset2D{X: 0, Y: 0},
Extent: app.swapchainExtent,
},
},
}
_ = &core1_0.PipelineRasterizationStateCreateInfo{
DepthClampEnable: false,
RasterizerDiscardEnable: false,
PolygonMode: core1_0.PolygonModeFill,
CullMode: core1_0.CullModeBack,
FrontFace: core1_0.FrontFaceClockwise,
DepthBiasEnable: false,
LineWidth: 1.0,
}
_ = &core1_0.PipelineMultisampleStateCreateInfo{
SampleShadingEnable: false,
RasterizationSamples: core1_0.Samples1,
MinSampleShading: 1.0,
}
_ = &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,
},
},
}
app.pipelineLayout, _, err = app.device.CreatePipelineLayout(nil, core1_0.PipelineLayoutCreateInfo{})
if err != nil {
return err
}
return nil
}
func (app *HelloTriangleApplication) chooseSwapSurfaceFormat(availableFormats []khr_surface.SurfaceFormat) khr_surface.SurfaceFormat {
for _, format := range availableFormats {
if format.Format == core1_0.FormatB8G8R8A8SRGB && format.ColorSpace == khr_surface.ColorSpaceSRGBNonlinear {
return format
}
}
return availableFormats[0]
}
func (app *HelloTriangleApplication) chooseSwapPresentMode(availablePresentModes []khr_surface.PresentMode) khr_surface.PresentMode {
for _, presentMode := range availablePresentModes {
if presentMode == khr_surface.PresentModeMailbox {
return presentMode
}
}
return khr_surface.PresentModeFIFO
}
func (app *HelloTriangleApplication) chooseSwapExtent(capabilities *khr_surface.SurfaceCapabilities) core1_0.Extent2D {
if capabilities.CurrentExtent.Width != -1 {
return capabilities.CurrentExtent
}
widthInt, heightInt := app.window.VulkanGetDrawableSize()
width := int(widthInt)
height := int(heightInt)
if width < capabilities.MinImageExtent.Width {
width = capabilities.MinImageExtent.Width
}
if width > capabilities.MaxImageExtent.Width {
width = capabilities.MaxImageExtent.Width
}
if height < capabilities.MinImageExtent.Height {
height = capabilities.MinImageExtent.Height
}
if height > capabilities.MaxImageExtent.Height {
height = capabilities.MaxImageExtent.Height
}
return core1_0.Extent2D{Width: width, Height: height}
}
func (app *HelloTriangleApplication) querySwapChainSupport(device core1_0.PhysicalDevice) (SwapChainSupportDetails, error) {
var details SwapChainSupportDetails
var err error
details.Capabilities, _, err = app.surface.PhysicalDeviceSurfaceCapabilities(device)
if err != nil {
return details, err
}
details.Formats, _, err = app.surface.PhysicalDeviceSurfaceFormats(device)
if err != nil {
return details, err
}
details.PresentModes, _, err = app.surface.PhysicalDeviceSurfacePresentModes(device)
return details, err
}
func (app *HelloTriangleApplication) isDeviceSuitable(device core1_0.PhysicalDevice) bool {
indices, err := app.findQueueFamilies(device)
if err != nil {
return false
}
extensionsSupported := app.checkDeviceExtensionSupport(device)
var swapChainAdequate bool
if extensionsSupported {
swapChainSupport, err := app.querySwapChainSupport(device)
if err != nil {
return false
}
swapChainAdequate = len(swapChainSupport.Formats) > 0 && len(swapChainSupport.PresentModes) > 0
}
return indices.IsComplete() && extensionsSupported && swapChainAdequate
}
func (app *HelloTriangleApplication) checkDeviceExtensionSupport(device core1_0.PhysicalDevice) bool {
extensions, _, err := device.EnumerateDeviceExtensionProperties()
if err != nil {
return false
}
for _, extension := range deviceExtensions {
_, hasExtension := extensions[extension]
if !hasExtension {
return false
}
}
return true
}
func (app *HelloTriangleApplication) findQueueFamilies(device core1_0.PhysicalDevice) (QueueFamilyIndices, error) {
indices := QueueFamilyIndices{}
queueFamilies := device.QueueFamilyProperties()
for queueFamilyIdx, queueFamily := range queueFamilies {
if (queueFamily.QueueFlags & core1_0.QueueGraphics) != 0 {
indices.GraphicsFamily = new(int)
*indices.GraphicsFamily = queueFamilyIdx
}
supported, _, err := app.surface.PhysicalDeviceSurfaceSupport(device, queueFamilyIdx)
if err != nil {
return indices, err
}
if supported {
indices.PresentFamily = new(int)
*indices.PresentFamily = queueFamilyIdx
}
if indices.IsComplete() {
break
}
}
return indices, nil
}
func (app *HelloTriangleApplication) logDebug(msgType ext_debug_utils.DebugUtilsMessageTypeFlags, severity ext_debug_utils.DebugUtilsMessageSeverityFlags, data *ext_debug_utils.DebugUtilsMessengerCallbackData) bool {
log.Printf("[%s %s] - %s", severity, msgType, data.Message)
return false
}
func main() {
app := &HelloTriangleApplication{}
err := app.Run()
if err != nil {
log.Fatalf("%+v\n", err)
}
}