Skip to content
Draft

Vulkan #2545

Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
d2315be
added AlertArmatureMask
codex128 Jul 24, 2023
2f1dae8
fixed docs and added another helper
codex128 Jul 24, 2023
a750f1c
made it easier to turn off priority checking
codex128 Jul 24, 2023
cd876d4
removed copy methods
codex128 Jul 25, 2023
9c9c2d8
updated license
codex128 Jan 19, 2024
83c95e5
updated javadoc
codex128 Jan 20, 2024
90e6cd8
renamed mask
codex128 Jan 21, 2024
0f410c0
Merge branch 'jMonkeyEngine:master' into master
codex128 Feb 29, 2024
9cf7dc4
Merge branch 'jMonkeyEngine:master' into master
codex128 Jun 7, 2024
7efa4c6
Merge remote-tracking branch 'origin/master'
codex128 Apr 26, 2025
0e25de2
Merge remote-tracking branch 'origin/master'
codex128 Jun 22, 2025
0c2d187
create vulkan context and prototyped vulkan instance creation in a te…
codex128 Jun 24, 2025
061a782
vulkan logical device
codex128 Jun 24, 2025
1b9f2e3
vulkan logical device
codex128 Jun 24, 2025
863e6a9
create interface over vulkan API
codex128 Jul 3, 2025
1b2ee3d
debug initial vulkan engine interface
codex128 Jul 27, 2025
e82ab06
debug initial vulkan engine interface
codex128 Jul 27, 2025
5aa391e
add shader attributes and gpu buffers
codex128 Jul 28, 2025
adb4e01
add uniforms; fix native crash and memory leaks
codex128 Aug 1, 2025
8e7ff36
added depth testing; added enums for image properties
codex128 Aug 1, 2025
beba5fe
added depth testing; added enums for image properties
codex128 Aug 1, 2025
4e31f0d
migrated instance, device, surface, and swapchain to new builder arc…
codex128 Aug 3, 2025
5f7d1ec
migrated instance, device, surface, and swapchain to new builder arc…
codex128 Aug 3, 2025
c4738ee
successfully abstracted all vulkan components
codex128 Aug 6, 2025
e0efc40
successfully abstracted all vulkan components
codex128 Aug 6, 2025
0c035ab
organized files
codex128 Aug 9, 2025
6a3171c
added MemorySize for clarity in allocating buffer sizes
codex128 Aug 9, 2025
bcb7014
managed to create acceptable prototype material system (still much to…
codex128 Aug 13, 2025
b168553
managed to create acceptable prototype material system (still much to…
codex128 Aug 13, 2025
6c0dbaf
altered material system slightly
codex128 Aug 13, 2025
f7d3679
basic material system is working
codex128 Aug 13, 2025
62882d4
basic material system is working
codex128 Aug 13, 2025
4a1970f
add device interfaces for different queue types
codex128 Aug 14, 2025
521c8a7
move pool management to logical device
codex128 Aug 14, 2025
a00411b
add stage mask to semaphores
codex128 Aug 15, 2025
d0340ab
figure out uniform details
codex128 Aug 16, 2025
ab0de0e
add Flag interface to handle bit flags
codex128 Aug 19, 2025
6657009
fix build error
codex128 Aug 19, 2025
72b0156
fix json error
codex128 Aug 19, 2025
ed6096e
extract GpuBuffer into an interface; create VersionedBuffer in prepar…
codex128 Aug 19, 2025
5587e17
fix build errors related to GpuBuffer interface extraction
codex128 Aug 19, 2025
6f97ff5
alter material system to support versions of resources per frame-in-f…
codex128 Aug 24, 2025
d4646f6
fix build and runtime errors (new material system is operational)
codex128 Aug 24, 2025
42a845a
fix warning on DescriptorSet destruction for DescriptorPools not crea…
codex128 Aug 24, 2025
ba4ea7f
use Flag for CommandPool creation instead of booleans
codex128 Aug 24, 2025
56a0a42
prototype meshes; improve usability; abstract images and buffers in p…
codex128 Aug 30, 2025
e29d2a9
fix image loader key type
codex128 Aug 30, 2025
85b3d95
mess around with meshes more
codex128 Sep 1, 2025
669ce8f
have uniforms use Resources/DataPipelines instead of VersionedResources
codex128 Sep 1, 2025
0fc9137
have uniforms use Resources/DataPipelines instead of VersionedResources
codex128 Sep 1, 2025
ab434fd
have uniforms use Resources/DataPipelines instead of VersionedResources
codex128 Sep 2, 2025
97750fd
simplified handling of DataPipe results by uniforms
codex128 Sep 2, 2025
bb7ab46
completed AdaptiveMesh; added CommandBatch system to properly update …
codex128 Sep 9, 2025
3099a26
fix vertex shader producing an unused varying
codex128 Sep 9, 2025
4756c04
rename updateStaticBuffers to updateSharedBuffers
codex128 Sep 9, 2025
519301e
add enum to represent common attribute names
codex128 Sep 9, 2025
20a2120
improve pipeline and sampler usability
codex128 Sep 9, 2025
2f3f8c5
improve pipeline and sampler usability
codex128 Sep 9, 2025
42d7525
preparing to alter Spatial
codex128 Sep 11, 2025
cb5c47a
move vulkan to core
codex128 Sep 12, 2025
29c1818
temporarily renamed Mesh to GLMesh to not conflict with the new Mesh …
codex128 Sep 12, 2025
1718bbf
Revert "temporarily renamed Mesh to GLMesh to not conflict with the n…
codex128 Sep 12, 2025
7fee9d2
temporarily renamed Mesh to GLMesh to not conflict with the new Mesh …
codex128 Sep 12, 2025
301b6bc
Revert "temporarily renamed Mesh to GLMesh to not conflict with the n…
codex128 Sep 12, 2025
e28d4df
Revert "Revert "temporarily renamed Mesh to GLMesh to not conflict wi…
codex128 Sep 12, 2025
d463d9c
Revert "temporarily renamed Mesh to GLMesh to not conflict with the n…
codex128 Sep 12, 2025
e3ee8be
Revert "Revert "temporarily renamed Mesh to GLMesh to not conflict wi…
codex128 Sep 12, 2025
65c076e
renamed new Mesh interface to NewMesh to not conflict with existing M…
codex128 Sep 12, 2025
115a985
renamed GLMesh back to Mesh
codex128 Sep 12, 2025
268988b
add BackedStaticBuffer
codex128 Sep 12, 2025
f23c5c8
make BIHTree compatible with NewMesh
codex128 Sep 12, 2025
c388da8
made BoundingVolume accept VertexReaders for computeFromPoints
codex128 Sep 13, 2025
e871c05
Geometry accepts NewMesh without errors
codex128 Sep 13, 2025
bc4ce97
Geometry accepts NewMesh without errors
codex128 Sep 13, 2025
d2e37e5
renamed vulkan material to NewMaterial
codex128 Sep 13, 2025
ef41a34
delete VertexModifier interface
codex128 Sep 13, 2025
1c45ac6
made Spatial iterable
codex128 Sep 13, 2025
49cc7f2
made Spatial iterable
codex128 Sep 13, 2025
1e2926d
setup vulkan test for rendering from the scene graph
codex128 Sep 13, 2025
a7c2f09
moved Mesh code to OldMesh; replaced NewMesh with Mesh, which has bee…
codex128 Sep 13, 2025
bd21a20
moved previous Mesh extensions to extend OldMesh
codex128 Sep 13, 2025
050e1df
moved Material to OldMaterial
codex128 Sep 13, 2025
93067db
renamed Texture to GlTexture and Image to GlImage, both implementing …
codex128 Sep 14, 2025
fe84d1e
added methods to Material interface for compatibility with old GlMate…
codex128 Sep 16, 2025
7e1f760
fix several incompatibilities
codex128 Sep 17, 2025
d3860c6
added GeometryBatch to cleanly and optimally handling renders; a lot …
codex128 Oct 10, 2025
ab514b7
added GeometryBatch to cleanly and optimally handling renders; a lot …
codex128 Oct 10, 2025
ebf9528
split Vulkan and OpenGL APIs on GeometryBatch
codex128 Oct 11, 2025
154d3ab
fixed build errors in GlVertexBuffer
codex128 Oct 11, 2025
d8dd146
changed command paradigm again; improved material set selection proce…
codex128 Oct 15, 2025
381b05d
start using Jackson to parse yaml matdefs
codex128 Oct 17, 2025
98163af
drafted a PipelineState system that I actually like and that will act…
codex128 Oct 18, 2025
eb7a6f1
drafted a PipelineState system that I actually like and that will act…
codex128 Oct 28, 2025
38b2ef2
deleted old deprecated animation system
codex128 Oct 28, 2025
c8db583
fixed build problems caused by new API
codex128 Oct 29, 2025
a574018
fix build problems, especially relating to FrameBuffers
codex128 Nov 1, 2025
b35e045
working on yet another rewrite of the mesh system
codex128 Nov 21, 2025
7235be7
working on yet another rewrite of the mesh system
codex128 Nov 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
preparing to alter Spatial
  • Loading branch information
codex128 committed Sep 11, 2025
commit 42d7525e3fc60d0d166525c1c8a8262fd61791b6
140 changes: 4 additions & 136 deletions VulkanDesign.txt
Original file line number Diff line number Diff line change
@@ -1,137 +1,5 @@
Some resources require copies per concurrent frame. There are some unresolved problems with implementing this:
Spatial: Current problems:

1. UniformSets need to have a DescriptorSet per concurrent frame, but only if at least one uniform requires resource copies.
2. Resources need to know the current frame's index so the correct copy is used.
3. Not all resources need to have copies per frame. This should be configurable by this user even after resources have been created (either by changing a state or replacing the resource entirely).

There are a couple different levels at which the copy split can happen:

1. Inside GpuBuffer and Image implementations (very low-level).
2. Each geometry has materials and meshes for each frame.

I like solution 1 because it gives more control over what resources are copied per-frame (neatly solving problem 3). If a mesh contains a buffer that doesn't need copies and another that does, solution 2 would require both buffers have copies. Using the buffer copy feature, users can swap out buffers with any other implementation.

Solution 1 could solve problem 2 by either setting the current frame when the frame begins, or pass as an argument for buffer functions. I lean toward setting at the start of the frame, because it would simplify the API. Each resource could have a method for that, and it is up to the implementors what to do with the current frame. The only problem here is how to ensure all resources get updated correctly.

Solution 1 struggles a bit with problem 1. UniformSet already has a system for choosing one DescriptorSet among several cached sets, so that shouldn't be a huge problem. The main problem is that uniforms need some way to indicate whether they use frame copies or not. Ideally, this shouldn't be a setting on the uniform, but rather on the underlying resource. This could be solved by providing a method for resources which returns their preference.

Here's the rough implementation plan for solution 1: make a low-level interface (maybe RenderResource) which supplies methods for frame interaction (knowing the current frame and indicating whether copies are being used). Uniforms and UniformSets will base their functionality on whether any resource is using frame copies. Other than that, copies are solely up to the specific resource implementations. With this in mind, the current problems are:

1. How to ensure all resources are properly updated. Not updating a resource may lead to read/write conflicts with the GPU.
2. I'm not happy with having an API method whose sole purpose is to communicate a boolean, where most implementations will simply return true or false without any calculation. It feels wrong, maybe because of how wishy-washy this is.
3. How will resources know how many frames are available. Sure, they could wait N frames to see where the index goes back to zero, but I don't like this limitation.

Problem 2 could be solved by having only resources that use copies to implement RenderResource (it would have to renamed). This would more rigidly enforce the API, but it doesn't completely solve the original problem of UniformSets knowing which resources use multiple copies. UniformSet could check if the resource implements RenderResource, but I'm not entirely happy with that either (I do think it's better than before, though).

Problem 1 could possibly be solved using a registry. The obvious problem with that is how the resources know about the registry, and I really don't want to make the registry static.

-------

I suppose I'm probably missing the real problem here. Requirements:

1. Some resources have multiple copies.
2. The copy that should be interacted with is determined by the active frame.
3. When a resource is destroyed, all its copies should also be destroyed.

We already have a system that can support requirement 1 and partially requirement 2. A buffer can delegate to the correct copy, perhaps through a chain of delegates.

1. Buffers delegate to a downstream copy depending on the current frame. Similar to a LinkedList.
PRO: Depends only on the implementation.
PRO: Automatically generates copies on demand.
CON: Too automatic. No way to indicate that no copies should be generated.
CON: Seems a bit too finicky. It'll probably outsmart itself.
2. Create a buffer implementation that delegates to one of many internal buffers depending on the current frame.
PRO: No need to alter current implementations. Works out of the box.
PRO: Simple to understand.

#1 is the way to go here. No downsides that I can see (although that does depend on future decisions).
The other half of requirement 2 is informing the buffer which copy should be interacted with. There are three options:

1. Inform the buffer at command time.
PRO: Flexible. Another copy besides the one for the active frame can be interacted with if desired.
PRO: Clear. It's easy to tell at a glance exactly what copy is being interacted with.
CON: Makes the API much messier. A lot of methods will have to take an extra argument.
CON: It is difficult to tell whether any given buffer supports concurrency via copies. You're basically dropped in a minefield.
2. Inform the buffer when the frame changes.
PRO: Doesn't touch the existing public API.
CON: Inflexible. Either impossible or difficult to interact with another copy.
CON: Unclear. You have to know context to know which copy is being interacted with.
CON: Accidental saving. Users may accidentally reference a copy thinking it is the managing buffer.
CON: Difficult to ensure all buffers are properly registered for such notifications.

Just by looking at the number of cons, #2 is clearly not the way to go. I'm liking #1 much better now, too. We still have to address the two cons #1 has. Starting with the first con, the methods that will require changing are:

1. GpuBuffer#map(MemoryStack, int, int int)
+ all related helper methods (map and copy)
2. GpuBuffer#unmap()
3. GpuBuffer#getId()
* Since GpuBuffer is not a Native<>, getNativeObject is left untouched :)
4. Image#createView(VkImageViewCreateInfo)
+ all related helper methods

Likewise, in order to avoid changing Native#getNativeObject, Image will gain getId() and will no longer implement Native<>. It's silly to expect an Image to be Native<> anyway. That's an implementation thing.

The second con for #1 is more serious, in my opinion. #1 is mostly an implementation solution, meaning an unsafely-mutable buffer (with no copies) could easily be passed into a mutator not designed for that sort of mutation.

1. Make a "SafelyMutableBuffer" interface extending GpuBuffer. The buffer manager will implement this.
PRO: Strictly enforces that unsafe buffers cannot be thrown into any mutator.
PLUS! Could even replace the need to change GpuBuffer API.
CON: Ends up having duplicate methods that have unclear functionality (what to do with getId() with no frame indicator?).
BUT... What about splitting SafelyMutableBuffer from GpuBuffer entirely? When would you want to treat a regular GpuBuffer like a SafelyMutableBuffer? For reading, of course. Nevermind.
BUT... This is not really a con. The methods GpuBuffer provides should only be used with special handling. SafelyMutableBuffer extends it to provide seperate safe methods. GpuBuffer's methods could act on, say, the first copy or the last interacted copy.
CON: Yeah, this isn't very maintainable... simply too many extra methods.
CON: And this whole thing is getting too complicated.
2. Add a method to GpuBuffer indicating safety.
CON: Weak, completely on the mutators to enforce.

---------

After more thought, I decided to move towards a "data pipeline" approach. The pipeline(s) transform the input data into the correct format. Pipelines, as I see them, would address several key issues:

1. Different versions per frame. Pipelines could descretely present different inputs/outputs depending on the frame.
2. Jankiness in the different buffer types in order to have staging buffers. Pipelines will handle transfers from the staging buffer to the GPU-side buffer(s), and it will be much more flexible, too, since pipelines can also handle per-frame versions.

There are a couple issues pipelines raise or don't adequately solve:

1. How do uniforms determine what resource version is currently being dealt with?
- The most straightforward approach would be to store the resources themselves in the frame index rather than the resource index. This seems all right to me, except we would need to use WeakReferences to not interfere with resources getting GC'd.
- Done. That turned out pretty well, actually.
2. Name conflict: Pipeline is already a thing. DataPipeline would just seem like a vulkan pipeline that processes data.
- DataGraph. Since I do envision it functioning similar to a graph.
- Resource. Rather bland, but it does encompass the general idea.
- Data. Ditto.
3. How should internals be properly synchronized?
- Pipelines requiring that commands be submitted will be given a command buffer on init. Pipelines only need to submit commands to the command buffer.

Well, that turned out much better than I anticipated. The only problem now is that I accidentally locked the input data behind an anonymous hierarchy of DataPipes.

1. Make uniforms able to store the necessary terminal data pipes.
- I don't like this method. The user would have to manually provide the terminal pipes, and they may not actually affect the uniform anyway.
2. Have a second structure (I'll call it Parameters, for now) mirroring Material which is specifically for storing the data pipes. The data pipes are configured for transfering data from slots in Parameters to corresponding uniforms.
- I like this method better. It's more structured than the other solution, but also allows uniforms to be informed by multiple parameter slots (or even none), if desired.
- I wonder if this could be transformed into something much bigger...

There is actually one more problem I overlooked with DataPipes. CommandBuffers need to be passed at pipe execution time, because each frame has its own set of CommandBuffers (for the most part). I could simply pass a CommandBuffer as an argument, except I'm not sure if all the pipes in the same pipeline are gauranteed to be happy submitting under to a single CommandBuffer. It'd be perfectly fine for them to do so (and they really shouldn't care), the question is more about performance.

1. Pass one CommandBuffer through the pipeline. All pipes submit commands to only that CommandBuffer.
- Could possibly be missing out on performance benefits, but I really can't think of a specific case off the top of my head.
2. Pass a CommandBuffer manager which will return the optimal CommandBuffer for the situation.
- Again, I'm fuzzy on exactly what situations would warrant multiple CommandBuffers, so I'm not willing to implement this at the moment.
- I could do a simple version of this just out of principle. Perhaps in the future we will know enough to implement such a system.

For now, I'll go with #1.

-----

Well, I've run into a new problem. I'm not sure what it is exactly, but the DataPipe system isn't working as smoothly as I'd hoped. It doesn't handle different forms of buffers very well (static, dynamic, adaptive). For example, in order for a static buffer to work, the staging buffer should be deleted after the transfer is complete. DataPipes have no clean way to handle that (and really, niether does any other available system). At the same time, DataPipes need a "needs update" check so that certain operations can be skipped if the incoming buffer hasn't changed.

My original idea was to have a Buffer implementation that deferred to one of N internal buffers depending on the frame could still possibly work.

-----

Yesterday, the occured to me that there are two main issues I'm trying to combat:

1. Updating items (buffers especially) at the correct time.
2. Ensuring the proper items are used at any given time (i.e. per-frame versions).

Now, a new solution to the first problem would be an update registry system. On creation, you register the item with a particular update batch, which will then update the item at the correct time. Furthermore, this will centralize CommandBuffer usage and make it easier to fine-tune synchronization (which would have otherwise been a fairly major problem).
* The transform refresh only works when transform setter methods are used. I propose that the world transform should be recalculated on every frame. It will make the system a lot simpler, I think.
* An explicit world light list is unnecessary. All that needs to be done to capture all lights affecting a spatial is to iterate up the hierarchy and collect lights from the local light lists. Iterating up the hierarchy is extremely cheap and I want to take more advantage of it.
* There should be at most one method in Spatial for iterating over an entire tree. It should use depth-first traversal. Breadth-first traversal isn't used within the engine and is obviously more expensive. collideWith() is also counted as a traversal method. If possible, I'd like to take more advantage of SceneGraphIterator, since I think it is the most powerful traversal available.
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ public void simpleInitApp() {
descriptorLayout = new DescriptorSetLayout(device,
new SetLayoutBinding(Descriptor.UniformBuffer, 0, 1, ShaderStage.Vertex),
new SetLayoutBinding(Descriptor.CombinedImageSampler, 1, 1, ShaderStage.Fragment));
descriptorPool = new DescriptorPool(device, 3,
descriptorPool = new DescriptorPool(device, 10,
new PoolSize(Descriptor.UniformBuffer, 3),
new PoolSize(Descriptor.StorageBuffer, 4),
new PoolSize(Descriptor.CombinedImageSampler, 2));
Expand Down
5 changes: 5 additions & 0 deletions jme3-lwjgl3/src/main/java/com/jme3/vulkan/scene/Geometry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.jme3.vulkan.scene;

public class Geometry {

}
32 changes: 32 additions & 0 deletions jme3-lwjgl3/src/main/java/com/jme3/vulkan/scene/Node.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.jme3.vulkan.scene;

import com.jme3.util.SafeArrayList;

public class Node extends Spatial {

private SafeArrayList<Spatial> children = new SafeArrayList<>(Spatial.class);

public void attachChild(Spatial child) {
children.add(child);
child.setParent(this);
}

public boolean detachChild(Spatial child) {
if (children.remove(child)) {
child.setParent(null);
return true;
}
return false;
}

@Override
protected void findNextIteration(GraphIterator iterator) {
int i = iterator.advanceIndex();
if (i >= children.size()) {
iterator.moveUp();
} else {
iterator.moveDown(children.get(i));
}
}

}
104 changes: 104 additions & 0 deletions jme3-lwjgl3/src/main/java/com/jme3/vulkan/scene/Spatial.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.jme3.vulkan.scene;

import java.util.*;

public abstract class Spatial implements Iterable<Spatial> {

private Node parent;

public void removeFromParent() {

}

public Node getParent() {
return parent;
}

protected void setParent(Node parent) {
this.parent = parent;
}

protected abstract void findNextIteration(GraphIterator iterator);

@Override
public Iterator<Spatial> iterator() {
return new GraphIterator(this);
}

public static class GraphIterator implements Iterator<Spatial> {

private final Stack<Integer> childIndices = new Stack<>();
private Spatial current;
private int currentIndex = 0;
private int iteration = -1;

public GraphIterator(Spatial start) {
current = Objects.requireNonNull(start);
}

@Override
public boolean hasNext() {
return current != null;
}

@Override
public Spatial next() {
if (++iteration > 0) {
current.findNextIteration(this);
}
return current;
}

@Override
public void remove() {
if (current.getParent() != null) {
current.removeFromParent();
moveUp();
currentIndex--;
}
}

protected void moveUp() {
if (!childIndices.isEmpty()) {
current = current.getParent();
currentIndex = childIndices.pop();
if (current != null) {
current.findNextIteration(this);
}
} else {
current = null;
}
}

protected void moveDown(Spatial node) {
if (node.getParent() != current) {
throw new IllegalArgumentException("Next node must be a child of the current node.");
}
current = node;
childIndices.push(currentIndex);
currentIndex = 0;
}

protected int advanceIndex() {
return currentIndex++;
}

protected int getCurrentIndex() {
return currentIndex;
}

public void skipChildren() {
moveUp();
}

public int getDepth() {
return childIndices.size();
}

public int getIteration() {
return iteration;
}

}

}