Skip to content

Vector path rendering for Vulkan#1264

Merged
bryanedds merged 17 commits intobryanedds:vulkanfrom
Happypig375:patch-57
Feb 18, 2026
Merged

Vector path rendering for Vulkan#1264
bryanedds merged 17 commits intobryanedds:vulkanfrom
Happypig375:patch-57

Conversation

@Happypig375
Copy link
Copy Markdown
Collaborator

@Happypig375 Happypig375 commented Dec 24, 2025

image

Added vector path rendering.

Also added -

  • CircleDispatcher
  • RectangleDispatcher
  • SpiralDispatcher
    with adjustable stroke and fill parameters.

Assets not propagated yet, awaiting review.

@bryanedds
Copy link
Copy Markdown
Owner

bryanedds commented Dec 25, 2025

Super cool stuff! Dean will be taking a look at this!

Perhaps this also frees me up on the OpenGL side to look at supporting vector graphics without having to worry about the Vulkan side needing additional work to match.

@deanjl
Copy link
Copy Markdown
Collaborator

deanjl commented Dec 27, 2025

Very exciting seeing someone else use this for the first time! The Vulkan code on the whole is spot on except for 2 issues with the buffer use.

The first issue is here:
let uniformBufferSize = sizeof<single> * 16 * Constants.Render.SpriteBatchSize
The SpriteBatchSize is of no relevance here. What you're specifying is the size of an actual VkBuffer for a given drawing instance, which is just the uniform (4 * 16). All the SpriteBatchSize is doing is making that buffer unnecessarily huge. The multiple instances represented by drawIndex use completely separate VkBuffers which are created as needed when you call Buffer.upload with a given index. You don't need to do anything for that. Think of these buffer layers as an implementation detail and just treat it as a normal buffer that needs an index. So just put sizeof<single> * 16.

The second issue is that you CAN'T destroy the vertex/index buffers at the end of the frame. This is completely invalid because the commands using the buffers haven't been executed yet. They're not submitted until the end of the frame, and the execution itself will take much longer because all the 3d shit is executed first! The only reason it's working at all is that the implementation is taking the liberty to defer the actual data destruction. This should also be triggering a validation error (though not with certainty), so make sure you've installed the Vulkan SDK.

Fortunately, you don't need to worry about buffer disposal either. The Staged buffers you're using take more time to create so are only intended for long-term data like the text-quad. Creating new vertex data on the fly works the same way as uniforms. Just do this (I just tweaked it so be sure to pull):

// create the buffers at init; size doesn't particularly matter here (see below) just guess the likely maximum
let vertexBuffer = Buffer.Buffer.create size (Buffer.Vertex true) vkc
let indexBuffer = Buffer.Buffer.create size (Buffer.Index true) vkc

// upload the data in frame; as with the buffer count, this will create a bigger VkBuffer if necessary
Buffer.Buffer.uploadArray drawIndex 0 vertices vertexBuffer vkc
Buffer.Buffer.uploadArray drawIndex 0 indices indexBuffer vkc

// here's the really cool part: you then bind the buffers like this
let mutable vertexBuf = vertexBuffer.[drawIndex]
...
Vulkan.vkCmdBindIndexBuffer (cb, indexBuffer.[drawIndex]...

// destroy at shutdown as usual
Buffer.Buffer.destroy vertexBuffer vkc
Buffer.Buffer.destroy indexBuffer vkc

Other than that, she's ready to merge as far as the Vulkan goes, great work!

@Happypig375
Copy link
Copy Markdown
Collaborator Author

The reviewed changes have been implemented.

@Happypig375
Copy link
Copy Markdown
Collaborator Author

Updated.

@Happypig375 Happypig375 requested review from bryanedds and deanjl and removed request for bryanedds February 9, 2026 15:13
@deanjl
Copy link
Copy Markdown
Collaborator

deanjl commented Feb 9, 2026

Good work once again. Just three minor changes needed:

  1. The vertex shader still uses the old and very bad uniform pattern with the alphabet naming. Just copy the relevant code from current SpriteBatch.vert and make sure all the bindings and other code blocks are in the correct order.
  2. We group uniform data together with pipelines but not vertex data, so the vertex and index buffers should be stored separately below TextQuad, and of course passed separately to drawVectorPath.
  3. Block comments need to be lower case, entirely except for type names etc.. This is obviously a general issue but affects the vulkan code.

@Happypig375
Copy link
Copy Markdown
Collaborator Author

@deanjl I implemented the suggested changes.

@deanjl
Copy link
Copy Markdown
Collaborator

deanjl commented Feb 10, 2026

The vert shader still doesn't conform to the right pattern. Just paste this in:

struct VectorPath
{
    mat4 modelViewProjection;
};

layout(push_constant) uniform PushConstant
{
    int drawId;
};

layout(binding = 0) uniform VectorPathBlock
{
    VectorPath vectorPath;
} vectorPath[];

...

mat4 modelViewProjection = vectorPath[drawId].vectorPath.modelViewProjection;

Vulkan uniforms suck.

@deanjl
Copy link
Copy Markdown
Collaborator

deanjl commented Feb 13, 2026

Update: multiple color attachments for rendering to has now been enabled so
vkc.SwapFormat None vkc and
let mutable rendering = Hl.makeRenderingInfo vkc.SwapchainImageView None renderArea None
in pipeline creation and drawing are now
[|vkc.SwapFormat|] None vkc and
let mutable rendering = Hl.makeRenderingInfo [|vkc.SwapchainImageView|] None renderArea None.

@Happypig375
Copy link
Copy Markdown
Collaborator Author

@deanjl Done!

@deanjl deanjl requested review from bryanedds and removed request for deanjl February 14, 2026 21:11
@bryanedds
Copy link
Copy Markdown
Owner

Currently going through a period of exhaustion, so I don't know when I'll get to this. I'll try to get to it tomorrow or Sunday. Sorry for being the one holding this up. I just need some rest.

Comment thread Nu/Nu/Render/Renderer2d.fs Outdated
descriptor.Stroke.StrokeColor descriptor.Stroke.StrokeThickness descriptor.Stroke.StrokeFringeWidth

// only render if we have geometry
if vertices.Length > 0 && indices.Length > 0 then
Copy link
Copy Markdown
Owner

@bryanedds bryanedds Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this scope, you've interrupted the sprite batch, yet you might not be rendering anything. That would be a waste to interrupt the sprite batch if nothing is rendered.

@@ -0,0 +1,382 @@
// Nu Game Engine.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new notice plz -

// Nu Game Engine.
// Required Notice:
// Copyright (C) Bryan Edds.
// Nu Game Engine is licensed under the Nu Game Engine Noncommercial License.
// See https://github.com/bryanedds/Nu/blob/master/License.md.

Comment thread Nu/Nu/Vulkan/Vulkan.VectorPath.fs Outdated
open System.Numerics
/// Describes a vector path command.
type [<Struct>] VectorPathCommand =
| MoveTo of endPoint: Vector2
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the standard formatting is | MoveTo of EndPoint : Vector2, but check Standard.md to make sure.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I treated it as a parameter but it should be treated as a Field instead.

Comment thread Nu/Nu/Vulkan/Vulkan.VectorPath.fs Outdated
| Positive
| Negative
| AbsGeqTwo
with static member Default = EvenOdd // default from LibTessDotNet
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The with is superfluous and not what our Standard.md prescribes.

member this.StrokeThickness = lens (nameof Entity.StrokeThickness) this this.GetStrokeThickness this.SetStrokeThickness

/// Gives an entity the base behavior of a circle render.
type CircleDispatcher () =
Copy link
Copy Markdown
Owner

@bryanedds bryanedds Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once this PR is merged, I'm going to do some surgery on all of these dispatchers. Nothing actionable for this PR here, tho.

@Happypig375
Copy link
Copy Markdown
Collaborator Author

All the raised issues should now be resolved.

@bryanedds bryanedds merged commit 1b9f80d into bryanedds:vulkan Feb 18, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants