First imgui trys
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
cimgui "github.com/AllenDang/cimgui-go/imgui"
|
||||||
vk "github.com/christerso/vulkan-go/vk"
|
vk "github.com/christerso/vulkan-go/vk"
|
||||||
"github.com/qvest-digital/go-mxl/mxl"
|
"github.com/qvest-digital/go-mxl/mxl"
|
||||||
pflag "github.com/spf13/pflag"
|
pflag "github.com/spf13/pflag"
|
||||||
@@ -325,6 +326,15 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer r.Destroy()
|
defer r.Destroy()
|
||||||
|
guiBackend, err := imgui.NewVulkanBackend(
|
||||||
|
vkPhysDevice, vkDevice, vkQueue, r.CmdPool(), r.RenderPass())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer guiBackend.Destroy()
|
||||||
|
r.ImGuiDraw = func(cmd vk.CommandBuffer) {
|
||||||
|
guiBackend.RecordDraw(cmd, gui.LastDrawData())
|
||||||
|
}
|
||||||
defer vkDevice.WaitIdle()
|
defer vkDevice.WaitIdle()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,7 +538,9 @@ func main() {
|
|||||||
dropped uint64
|
dropped uint64
|
||||||
frameCount uint64
|
frameCount uint64
|
||||||
lastReport time.Time
|
lastReport time.Time
|
||||||
|
lastFrame time.Time
|
||||||
)
|
)
|
||||||
|
lastFrame = time.Now()
|
||||||
for running {
|
for running {
|
||||||
frameStart := time.Now()
|
frameStart := time.Now()
|
||||||
var event [128]byte
|
var event [128]byte
|
||||||
@@ -589,6 +601,15 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r != nil {
|
if r != nil {
|
||||||
|
gui.BeginFrame(time.Since(lastFrame), int32(r.Extent().Width), int32(r.Extent().Height))
|
||||||
|
// test widget
|
||||||
|
cimgui.Begin("Test")
|
||||||
|
imguiLabel := fmt.Sprintf("%dx%d", 1920, 1080)
|
||||||
|
cimgui.SeparatorText(imguiLabel)
|
||||||
|
cimgui.End()
|
||||||
|
gui.EndFrame()
|
||||||
|
lastFrame = time.Now()
|
||||||
|
// end of test widget
|
||||||
var w, h, stride uint32
|
var w, h, stride uint32
|
||||||
if syncSrc != nil {
|
if syncSrc != nil {
|
||||||
w, h, stride = syncSrc.Width(), syncSrc.Height(), syncSrc.Stride()
|
w, h, stride = syncSrc.Width(), syncSrc.Height(), syncSrc.Stride()
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[Window][Debug##Default]
|
||||||
|
Pos=60,60
|
||||||
|
Size=400,400
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][Test]
|
||||||
|
Pos=352,204
|
||||||
|
Size=554,180
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
type Context struct {
|
type Context struct {
|
||||||
ctx *cimgui.Context
|
ctx *cimgui.Context
|
||||||
io *cimgui.IO
|
io *cimgui.IO
|
||||||
|
lastDrawData *cimgui.DrawData
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Context {
|
func New() *Context {
|
||||||
@@ -36,8 +37,11 @@ func (c *Context) BeginFrame(dt time.Duration, winW, winH int32) {
|
|||||||
func (c *Context) EndFrame() *cimgui.DrawData {
|
func (c *Context) EndFrame() *cimgui.DrawData {
|
||||||
cimgui.EndFrame()
|
cimgui.EndFrame()
|
||||||
cimgui.Render()
|
cimgui.Render()
|
||||||
|
c.lastDrawData = cimgui.CurrentDrawData()
|
||||||
return cimgui.CurrentDrawData()
|
return cimgui.CurrentDrawData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// expose io to input backend
|
// expose io to input backend
|
||||||
func (c *Context) IO() *cimgui.IO { return c.io }
|
func (c *Context) IO() *cimgui.IO { return c.io }
|
||||||
|
|
||||||
|
func (c *Context) LastDrawData() *cimgui.DrawData { return c.lastDrawData }
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ type Renderer struct {
|
|||||||
imageAvailable vk.Semaphore
|
imageAvailable vk.Semaphore
|
||||||
renderFinished vk.Semaphore
|
renderFinished vk.Semaphore
|
||||||
inFlight vk.Fence
|
inFlight vk.Fence
|
||||||
|
|
||||||
|
// Called inside render pass, after video draw
|
||||||
|
ImGuiDraw func(cmd vk.CommandBuffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// public params, needed to create a Renderer
|
// public params, needed to create a Renderer
|
||||||
@@ -441,6 +444,9 @@ func (r *Renderer) DrawFrame(videoW, videoH, stride uint32) error {
|
|||||||
}
|
}
|
||||||
cmd.PushConstants(r.decodeLayout, vk.ShaderStageFragment, 0, unsafe.Pointer(&pc), 20)
|
cmd.PushConstants(r.decodeLayout, vk.ShaderStageFragment, 0, unsafe.Pointer(&pc), 20)
|
||||||
cmd.Draw(3, 1, 0, 0)
|
cmd.Draw(3, 1, 0, 0)
|
||||||
|
if r.ImGuiDraw != nil {
|
||||||
|
r.ImGuiDraw(cmd)
|
||||||
|
}
|
||||||
cmd.EndRenderPass()
|
cmd.EndRenderPass()
|
||||||
if err := cmd.End(); err != nil {
|
if err := cmd.End(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -465,3 +471,6 @@ func (r *Renderer) DrawFrame(videoW, videoH, stride uint32) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) CmdPool() vk.CommandPool { return r.cmdPool }
|
||||||
|
func (r *Renderer) RenderPass() vk.RenderPass { return r.renderPass }
|
||||||
|
|||||||
Reference in New Issue
Block a user