First imgui trys
This commit is contained in:
@@ -0,0 +1,301 @@
|
||||
package imgui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
cimgui "github.com/AllenDang/cimgui-go/imgui"
|
||||
"github.com/christerso/vulkan-go/vk"
|
||||
)
|
||||
|
||||
// Renders ImGui draw data inside an existing Vulkan render pass
|
||||
type VulkanBackend struct {
|
||||
pd vk.PhysicalDevice
|
||||
dev vk.Device
|
||||
queue vk.Queue
|
||||
cmdPool vk.CommandPool
|
||||
|
||||
pipeline vk.Pipeline
|
||||
pipelineLayout vk.PipelineLayout
|
||||
descSetLayout vk.DescriptorSetLayout
|
||||
descPool vk.DescriptorPool
|
||||
descSet vk.DescriptorSet
|
||||
vertMod vk.ShaderModule
|
||||
fragMod vk.ShaderModule
|
||||
|
||||
fontImg vk.AllocImage
|
||||
fontView vk.ImageView
|
||||
fontSampler vk.Sampler
|
||||
|
||||
vertBuf vk.AllocBuffer
|
||||
idxBuf vk.AllocBuffer
|
||||
vertSize vk.DeviceSize
|
||||
idxSize vk.DeviceSize
|
||||
}
|
||||
|
||||
// NewVulkanBackend creates the ImGui pipeline, font atlas, and buffers.
|
||||
// rp is the existing render pass; format is the swapchain color format.
|
||||
func NewVulkanBackend(pd vk.PhysicalDevice, dev vk.Device, queue vk.Queue, cmdPool vk.CommandPool, rp vk.RenderPass) (*VulkanBackend, error) {
|
||||
b := &VulkanBackend{pd: pd, dev: dev, queue: queue, cmdPool: cmdPool}
|
||||
|
||||
// 1. Font atlas — build and upload via CreateTexture2D (handles staging).
|
||||
io := cimgui.CurrentIO()
|
||||
fontAtlas := io.Fonts()
|
||||
cimgui.InternalImFontAtlasBuildMain(fontAtlas)
|
||||
texData := fontAtlas.TexData()
|
||||
w, h := texData.Width(), texData.Height()
|
||||
pixelCount := int(w) * int(h) * 4 // RGBA32 = 4 bytes/pixel
|
||||
pixels := unsafe.Slice((*byte)(unsafe.Pointer(texData.Pixels())), pixelCount)
|
||||
|
||||
var err error
|
||||
b.fontImg, b.fontView, err = dev.CreateTexture2D(pd, queue, cmdPool, uint32(w), uint32(h), pixels)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("imgui font atlas: %w", err)
|
||||
}
|
||||
|
||||
// Font sampler: linear, clamp to edge.
|
||||
b.fontSampler, err = dev.CreateSampler(vk.SamplerConfig{
|
||||
MagFilter: vk.FilterLinear,
|
||||
MinFilter: vk.FilterLinear,
|
||||
AddressModeU: vk.SamplerAddressModeClampToEdge,
|
||||
AddressModeV: vk.SamplerAddressModeClampToEdge,
|
||||
})
|
||||
if err != nil {
|
||||
b.destroyAll()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2. Shaders.
|
||||
b.vertMod, err = dev.CreateShaderModule(vertSPV)
|
||||
if err != nil {
|
||||
b.destroyAll()
|
||||
return nil, err
|
||||
}
|
||||
b.fragMod, err = dev.CreateShaderModule(fragSPV)
|
||||
if err != nil {
|
||||
b.destroyAll()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 3. Descriptor set: one combined image sampler (font), fragment stage.
|
||||
b.descSetLayout, err = dev.CreateDescriptorSetLayout([]vk.DescriptorBinding{
|
||||
{Binding: 0, Type: vk.DescriptorCombinedImageSampler, Count: 1, Stages: vk.ShaderStageFragment},
|
||||
})
|
||||
if err != nil {
|
||||
b.destroyAll()
|
||||
return nil, err
|
||||
}
|
||||
b.descPool, err = dev.CreateDescriptorPool(1, map[vk.DescriptorType]uint32{
|
||||
vk.DescriptorCombinedImageSampler: 1,
|
||||
})
|
||||
if err != nil {
|
||||
b.destroyAll()
|
||||
return nil, err
|
||||
}
|
||||
b.descSet, err = dev.AllocateDescriptorSet(b.descPool, b.descSetLayout)
|
||||
if err != nil {
|
||||
b.destroyAll()
|
||||
return nil, err
|
||||
}
|
||||
dev.UpdateImageDescriptor(b.descSet, 0, b.fontView, b.fontSampler)
|
||||
|
||||
// 4. Pipeline layout: one set + 16 bytes push constants (vertex+fragment).
|
||||
b.pipelineLayout, err = dev.CreatePipelineLayout(
|
||||
[]vk.DescriptorSetLayout{b.descSetLayout},
|
||||
vk.ShaderStageVertex|vk.ShaderStageFragment, 16,
|
||||
)
|
||||
if err != nil {
|
||||
b.destroyAll()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 5. Graphics pipeline: vertex input (pos+uv+col = 20 bytes), alpha blend.
|
||||
b.pipeline, err = dev.CreateGraphicsPipeline(vk.GraphicsPipelineConfig{
|
||||
Layout: b.pipelineLayout,
|
||||
RenderPass: rp,
|
||||
VertexShader: b.vertMod,
|
||||
FragShader: b.fragMod,
|
||||
Bindings: []vk.VertexInputBinding{
|
||||
{Binding: 0, Stride: 20, InputRate: vk.VertexInputRateVertex},
|
||||
},
|
||||
Attributes: []vk.VertexInputAttribute{
|
||||
{Location: 0, Binding: 0, Format: vk.Format(103), Offset: 0}, // pos: R32G32Sfloat
|
||||
{Location: 1, Binding: 0, Format: vk.Format(103), Offset: 8}, // uv: R32G32Sfloat
|
||||
{Location: 2, Binding: 0, Format: vk.FormatR8G8B8A8Unorm, Offset: 16}, // col: R8G8B8A8Unorm
|
||||
},
|
||||
Topology: vk.TopologyTriangleList,
|
||||
PolygonMode: vk.PolygonFill,
|
||||
CullMode: vk.CullNone,
|
||||
FrontFace: vk.FrontFaceCounterClockwise,
|
||||
Blend: true,
|
||||
})
|
||||
if err != nil {
|
||||
b.destroyAll()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 6. Vertex/index buffers: host-visible, mapped. Start at 64KB/16KB, grow if needed.
|
||||
b.vertSize = 1 << 16
|
||||
b.idxSize = 1 << 14
|
||||
b.vertBuf, err = dev.CreateBuffer(pd, vk.BufferConfig{
|
||||
Size: b.vertSize,
|
||||
Usage: vk.BufferUsageVertexBuffer,
|
||||
Properties: vk.MemoryHostVisible | vk.MemoryHostCoherent,
|
||||
Map: true,
|
||||
})
|
||||
if err != nil {
|
||||
b.destroyAll()
|
||||
return nil, err
|
||||
}
|
||||
b.idxBuf, err = dev.CreateBuffer(pd, vk.BufferConfig{
|
||||
Size: vk.DeviceSize(b.idxSize),
|
||||
Usage: vk.BufferUsageIndexBuffer,
|
||||
Properties: vk.MemoryHostVisible | vk.MemoryHostCoherent,
|
||||
Map: true,
|
||||
})
|
||||
if err != nil {
|
||||
b.destroyAll()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// RecordDraw records ImGui draw commands into the command buffer.
|
||||
// Must be called inside the render pass, after your scene draw, before EndRenderPass.
|
||||
func (b *VulkanBackend) RecordDraw(cmd vk.CommandBuffer, drawData *cimgui.DrawData) {
|
||||
if !drawData.Valid() || drawData.CmdListsCount() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
totalVtx := int(drawData.TotalVtxCount())
|
||||
totalIdx := int(drawData.TotalIdxCount())
|
||||
vertBytes := totalVtx * 20
|
||||
idxBytes := totalIdx * 2
|
||||
|
||||
// Grow vertex buffer if needed.
|
||||
if vk.DeviceSize(vertBytes) > b.vertSize {
|
||||
b.dev.DestroyBuffer(b.vertBuf)
|
||||
b.vertSize = vk.DeviceSize(vertBytes) * 2
|
||||
b.vertBuf, _ = b.dev.CreateBuffer(b.pd, vk.BufferConfig{
|
||||
Size: b.vertSize,
|
||||
Usage: vk.BufferUsageVertexBuffer,
|
||||
Properties: vk.MemoryHostVisible | vk.MemoryHostCoherent,
|
||||
Map: true,
|
||||
})
|
||||
}
|
||||
// Grow index buffer if needed.
|
||||
if vk.DeviceSize(idxBytes) > vk.DeviceSize(b.idxBuf.Size) {
|
||||
b.dev.DestroyBuffer(b.idxBuf)
|
||||
b.idxBuf, _ = b.dev.CreateBuffer(b.pd, vk.BufferConfig{
|
||||
Size: vk.DeviceSize(idxBytes) * 2,
|
||||
Usage: vk.BufferUsageIndexBuffer,
|
||||
Properties: vk.MemoryHostVisible | vk.MemoryHostCoherent,
|
||||
Map: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Copy all vertex/index data into the mapped buffers.
|
||||
vertOffset := 0
|
||||
idxOffset := 0
|
||||
cmdLists := drawData.CommandLists()
|
||||
for _, list := range cmdLists {
|
||||
// Vertices: GetVertexBuffer returns raw C pointer + byte size.
|
||||
vtxPtr, vtxBytes2 := list.GetVertexBuffer()
|
||||
if vtxBytes2 > 0 {
|
||||
src := unsafe.Slice((*byte)(vtxPtr), vtxBytes2)
|
||||
dst := unsafe.Slice((*byte)(b.vertBuf.Mapped), vertBytes)
|
||||
copy(dst[vertOffset:], src)
|
||||
vertOffset += vtxBytes2
|
||||
}
|
||||
// Indices: GetIndexBuffer returns raw C pointer + byte size.
|
||||
idxPtr, idxBytes2 := list.GetIndexBuffer()
|
||||
if idxBytes2 > 0 {
|
||||
src := unsafe.Slice((*byte)(idxPtr), idxBytes2)
|
||||
dst := unsafe.Slice((*byte)(b.idxBuf.Mapped), idxBytes)
|
||||
copy(dst[idxOffset:], src)
|
||||
idxOffset += idxBytes2
|
||||
}
|
||||
}
|
||||
|
||||
// Push constants: scale + translate (transforms ImGui pixels to clip space).
|
||||
displaySize := drawData.DisplaySize()
|
||||
displayPos := drawData.DisplayPos()
|
||||
scale := [2]float32{2.0 / displaySize.X, 2.0 / displaySize.Y}
|
||||
translate := [2]float32{
|
||||
-1.0 - 2.0*displayPos.X/displaySize.X,
|
||||
-1.0 - 2.0*displayPos.Y/displaySize.Y,
|
||||
}
|
||||
pc := struct {
|
||||
Scale [2]float32
|
||||
Translate [2]float32
|
||||
}{
|
||||
Scale: scale,
|
||||
Translate: translate,
|
||||
}
|
||||
cmd.PushConstants(b.pipelineLayout, vk.ShaderStageVertex|vk.ShaderStageFragment, 0, unsafe.Pointer(&pc), 16)
|
||||
|
||||
// Bind pipeline + descriptor set + vertex/index buffers.
|
||||
cmd.BindPipeline(b.pipeline)
|
||||
cmd.BindDescriptorSet(b.pipelineLayout, 0, b.descSet)
|
||||
offsets := []vk.DeviceSize{0}
|
||||
cmd.BindVertexBuffers(0, []vk.Buffer{b.vertBuf.Buffer}, offsets)
|
||||
cmd.BindIndexBuffer(b.idxBuf.Buffer, 0, vk.IndexTypeUint16)
|
||||
|
||||
// Draw each command list, translating clip rects to scissors.
|
||||
vtxOff := uint32(0)
|
||||
idxOff := uint32(0)
|
||||
for _, list := range cmdLists {
|
||||
cmds := list.Commands()
|
||||
for _, dc := range cmds {
|
||||
if dc.HasUserCallback() {
|
||||
dc.CallUserCallback(list)
|
||||
continue
|
||||
}
|
||||
clip := dc.ClipRect()
|
||||
sx := int32(clip.X - displayPos.X)
|
||||
sy := int32(clip.Y - displayPos.Y)
|
||||
ex := int32(clip.Z - displayPos.X)
|
||||
ey := int32(clip.W - displayPos.Y)
|
||||
if sx < 0 {
|
||||
sx = 0
|
||||
}
|
||||
if sy < 0 {
|
||||
sy = 0
|
||||
}
|
||||
if ex > int32(displaySize.X) {
|
||||
ex = int32(displaySize.X)
|
||||
}
|
||||
if ey > int32(displaySize.Y) {
|
||||
ey = int32(displaySize.Y)
|
||||
}
|
||||
if ex > sx && ey > sy {
|
||||
cmd.SetScissor(vk.Rect2D{
|
||||
Offset: vk.Offset2D{X: sx, Y: sy},
|
||||
Extent: vk.Extent2D{Width: uint32(ex - sx), Height: uint32(ey - sy)},
|
||||
})
|
||||
}
|
||||
cmd.DrawIndexed(dc.ElemCount(), 1, idxOff+dc.IdxOffset(), int32(vtxOff+dc.VtxOffset()), 0)
|
||||
}
|
||||
vtxOff += uint32(list.VtxBuffer().Size)
|
||||
idxOff += uint32(list.IdxBuffer().Size)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *VulkanBackend) destroyAll() {
|
||||
b.dev.DestroyBuffer(b.idxBuf)
|
||||
b.dev.DestroyBuffer(b.vertBuf)
|
||||
b.dev.DestroyPipeline(b.pipeline)
|
||||
b.dev.DestroyPipelineLayout(b.pipelineLayout)
|
||||
b.dev.DestroyDescriptorPool(b.descPool)
|
||||
b.dev.DestroyDescriptorSetLayout(b.descSetLayout)
|
||||
b.dev.DestroyShaderModule(b.fragMod)
|
||||
b.dev.DestroyShaderModule(b.vertMod)
|
||||
b.dev.DestroySampler(b.fontSampler)
|
||||
b.dev.DestroyImageView(b.fontView)
|
||||
b.dev.DestroyImage(b.fontImg)
|
||||
}
|
||||
|
||||
func (b *VulkanBackend) Destroy() {
|
||||
b.destroyAll()
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ import (
|
||||
|
||||
// ImGui context & IO wrapper
|
||||
type Context struct {
|
||||
ctx *cimgui.Context
|
||||
io *cimgui.IO
|
||||
ctx *cimgui.Context
|
||||
io *cimgui.IO
|
||||
lastDrawData *cimgui.DrawData
|
||||
}
|
||||
|
||||
func New() *Context {
|
||||
@@ -36,8 +37,11 @@ func (c *Context) BeginFrame(dt time.Duration, winW, winH int32) {
|
||||
func (c *Context) EndFrame() *cimgui.DrawData {
|
||||
cimgui.EndFrame()
|
||||
cimgui.Render()
|
||||
c.lastDrawData = cimgui.CurrentDrawData()
|
||||
return cimgui.CurrentDrawData()
|
||||
}
|
||||
|
||||
// expose io to input backend
|
||||
func (c *Context) IO() *cimgui.IO { return c.io }
|
||||
|
||||
func (c *Context) LastDrawData() *cimgui.DrawData { return c.lastDrawData }
|
||||
|
||||
Reference in New Issue
Block a user