Skip to content

Commit

Permalink
wip: continue xr improvements + docs
Browse files Browse the repository at this point in the history
  • Loading branch information
knokko committed Sep 21, 2024
1 parent c455f33 commit c07998b
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 69 deletions.
2 changes: 2 additions & 0 deletions docs/initialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ to use virtual reality (OpenXR), where the OpenXR runtime can set
`XrGraphicsRequirementsVulkanKHR.maxApiVersionSupported` to
`VK_API_VERSION_1_0` during `xrGetVulkanGraphicsRequirementsKHR`,
which will pin you on Vulkan 1.0, even if the device supports 1.3.
The OpenXR runtime for PCVR for Oculus Quest 2 did this last time
I checked (21-09-2024).
- As far as I know, targeting Vulkan 1.1 is useless on desktop.

## Instance creation properties
Expand Down
88 changes: 87 additions & 1 deletion docs/methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -607,4 +607,90 @@ You can call `session.attach(stack, actionSet)` to attach a
single `XrActionSet` to the `XrSession` (using
`xrAttachSessionActionSets`). If you wish to attach multiple
action sets at the same time, you will have to call
`xrAttachSessionActionSets` yourself.
`xrAttachSessionActionSets` yourself.

### The session loop
Once all action sets and Vulkan resources are set up, it's time
to start the session loop. Managing a session loop is a lot of
work (code) that shouldn't depend much on the application.
The `SessionLoop` class was made to lighten this work for
applications. It is an abstract class that handles most of the
work, but you need to implement several methods:

#### Creating the projection matrix
You need to implement the method
`Matrix4f createProjectionMatrix(XrFovf fov)`.
You could easily do this by returning
`xr.createProjectionMatrix(fov, nearPlane, farPlane)`,
but you can also create a more complicated projection matrix.

#### Choosing the active action set
You need to implement the method
`XrActionSet[] chooseActiveActionSets()`. If your application
only has 1 action set, you could attach it before starting
the session loop, and always return it.

If your application actually switches action sets, it becomes
a bit more complicated. When you want to change the active
action set, you will need to *attach* the new action set during
this method, and then return it. If you want to keep using the
same action set as the last call, just return that same
action set(s).

#### The update method
You need to implement the method `void update()`, which will
be called during every iteration of the session loop, always
after polling and handling events. You can do whatever you
want in this method, and are allowed to leave the method body
empty.

#### Handling events
You need to implement the method
`void handleEvent(XrEventDataBuffer event)`. This method will
be called for each event that is polled with `xrPollEvent`.
Just like in the update method, you can do whatever you want
in this method, including leaving the method body empty.

Note that the `SessionLoop` class will automatically handle
`XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED` events before calling
`handleEvent`. You may listen to session state changes, but you
don't **need** to.

#### Waiting for render resources
You need to implement the method
`void waitForRenderResources(MemoryStack stack)`. You should use
this method to wait for render resources (like command buffers
and fences) that you will need during the *next* frame.

This method will only be called during session loop iterations
where the session state is either `SYNCHRONIZED`, `VISIBLE`,
`FOCUSSED`, or `READY`. Note that this does *not* necessarily
mean that a frame will be rendered during this iteration.

#### Recording render commands
You need to implement the method
```java
void recordRenderCommands(
MemoryStack stack, XrFrameState frameState, int swapchainImageIndex, Matrix4f[] cameraMatrices
)
```
During this method, you should record all the command buffers
that you intend to submit for this frame. This method will be
called right after `xrAcquireSwapchainImage` and
`xrSyncActions`, which means that the next swapchain image is
known, but that it's not yet ready.

Therefor, you may (and should) record command buffers that use
the swapchain image, but you may not submit them yet. You may
submit any command buffers that do **not** need the swapchain
image.

Since this method is called right after `xrSyncActions`, it is
also the best moment to query actions.

#### Submitting the render commands
You need to implement the method `void submitRenderCommands()`.
During this method, you should submit the command buffers that
you recorded during `recordRenderCommands`. This method will be
called right after `xrWaitSwapchainImage` and right before
`xrReleaseSwapchainImage`.
109 changes: 60 additions & 49 deletions samples/src/main/java/com/github/knokko/boiler/samples/HelloXR.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.knokko.boiler.samples;

import com.github.knokko.boiler.buffers.MappedVkbBuffer;
import com.github.knokko.boiler.builders.BoilerBuilder;
import com.github.knokko.boiler.builders.xr.BoilerXrBuilder;
import com.github.knokko.boiler.commands.CommandRecorder;
Expand Down Expand Up @@ -28,6 +29,8 @@

public class HelloXR {

private static final int NUM_FRAMES_IN_FLIGHT = 2;

public static void main(String[] args) throws InterruptedException {
var boiler = new BoilerBuilder(
VK_API_VERSION_1_0, "HelloXR", 1
Expand Down Expand Up @@ -94,11 +97,12 @@ public static void main(String[] args) throws InterruptedException {
VK_IMAGE_ASPECT_DEPTH_BIT, VK_SAMPLE_COUNT_1_BIT, 1, 2, true, "DepthImage"
);

var commandPool = boiler.commands.createPool(
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, boiler.queueFamilies().graphics().index(), "Drawing"
var commandPools = boiler.commands.createPools(
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, boiler.queueFamilies().graphics().index(),
NUM_FRAMES_IN_FLIGHT, "Drawing"
);
var commandBuffer = boiler.commands.createPrimaryBuffers(commandPool, 1, "Drawing")[0];
var fence = boiler.sync.fenceBank.borrowFence(false, "Drawing");
var commandBuffers = boiler.commands.createPrimaryBufferPerPool("Drawing", commandPools);
var fences = boiler.sync.fenceBank.borrowFences(NUM_FRAMES_IN_FLIGHT, false, "Drawing");

int vertexSize = (3 + 3) * 4;
var vertexBuffer = boiler.buffers.createMapped(
Expand All @@ -124,13 +128,16 @@ public static void main(String[] args) throws InterruptedException {
hostIndexBuffer.put(1).put(2).put(3); // right of the hand triangle
hostIndexBuffer.put(2).put(0).put(3); // left of the hand triangle

var matrixBuffer = boiler.buffers.createMapped(
5 * 64, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, "MatrixBuffer"
);
var matrixBuffers = new MappedVkbBuffer[NUM_FRAMES_IN_FLIGHT];
for (int index = 0; index < NUM_FRAMES_IN_FLIGHT; index++) {
matrixBuffers[index] = boiler.buffers.createMapped(
5 * 64, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, "MatrixBuffer"
);
}

VkbDescriptorSetLayout descriptorSetLayout;
HomogeneousDescriptorPool descriptorPool;
long descriptorSet;
long[] descriptorSets;
long pipelineLayout;
long graphicsPipeline;
try (var stack = stackPush()) {
Expand All @@ -139,15 +146,17 @@ public static void main(String[] args) throws InterruptedException {
boiler.descriptors.binding(layoutBindings, 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT);

descriptorSetLayout = boiler.descriptors.createLayout(stack, layoutBindings, "MatricesLayout");
descriptorPool = descriptorSetLayout.createPool(1, 0, "MatricesPool");
descriptorSet = descriptorPool.allocate(1)[0];
descriptorPool = descriptorSetLayout.createPool(NUM_FRAMES_IN_FLIGHT, 0, "MatricesPool");
descriptorSets = descriptorPool.allocate(NUM_FRAMES_IN_FLIGHT);

var descriptorWrites = VkWriteDescriptorSet.calloc(1, stack);
boiler.descriptors.writeBuffer(
stack, descriptorWrites, descriptorSet,
0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, matrixBuffer.fullRange()
);
vkUpdateDescriptorSets(boiler.vkDevice(), descriptorWrites, null);
for (int index = 0; index < NUM_FRAMES_IN_FLIGHT; index++) {
boiler.descriptors.writeBuffer(
stack, descriptorWrites, descriptorSets[index],
0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, matrixBuffers[index].fullRange()
);
vkUpdateDescriptorSets(boiler.vkDevice(), descriptorWrites, null);
}

var pushConstants = VkPushConstantRange.calloc(1, stack);
pushConstants.stageFlags(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
Expand Down Expand Up @@ -243,7 +252,7 @@ public static void main(String[] args) throws InterruptedException {

class HelloSessionLoop extends SessionLoop {

private Matrix4f leftHandMatrix, rightHandMatrix;
private int frameIndex;

public HelloSessionLoop(
VkbSession session, XrSpace renderSpace,
Expand Down Expand Up @@ -273,22 +282,29 @@ protected void handleEvent(XrEventDataBuffer event) {
}

@Override
protected void prepareRender(MemoryStack stack, XrFrameState frameState) {
leftHandMatrix = boiler.xr().locateSpace(
protected void waitForRenderResources(MemoryStack stack) {
frameIndex = (frameIndex + 1) % NUM_FRAMES_IN_FLIGHT;
fences[frameIndex].waitIfSubmitted();
fences[frameIndex].reset();
assertVkSuccess(vkResetCommandPool(
xr.boilerInstance.vkDevice(), commandPools[frameIndex], 0
), "ResetCommandPool", "Drawing");
}

@Override
protected void recordRenderCommands(
MemoryStack stack, XrFrameState frameState, int swapchainImageIndex, Matrix4f[] cameraMatrices
) {
Matrix4f leftHandMatrix = boiler.xr().locateSpace(
stack, leftHandSpace, renderSpace, frameState.predictedDisplayTime(), "left hand"
).createMatrix();
if (leftHandMatrix != null) leftHandMatrix.scale(0.1f);
rightHandMatrix = boiler.xr().locateSpace(
Matrix4f rightHandMatrix = boiler.xr().locateSpace(
stack, rightHandSpace, renderSpace, frameState.predictedDisplayTime(), "right hand"
).createMatrix();
if (rightHandMatrix != null) rightHandMatrix.scale(0.1f);
}

@Override
protected void recordRenderCommands(
MemoryStack stack, int swapchainImageIndex, Matrix4f[] cameraMatrices
) {
var commands = CommandRecorder.begin(commandBuffer, xr.boilerInstance, stack, "Drawing");
var commands = CommandRecorder.begin(commandBuffers[frameIndex], xr.boilerInstance, stack, "Drawing");

var colorAttachments = VkRenderingAttachmentInfoKHR.calloc(1, stack);
commands.simpleColorRenderingAttachment(
Expand All @@ -311,19 +327,19 @@ protected void recordRenderCommands(
dynamicRenderingInfo.pColorAttachments(colorAttachments);
dynamicRenderingInfo.pDepthAttachment(depthAttachment);

vkCmdBeginRenderingKHR(commandBuffer, dynamicRenderingInfo);
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
vkCmdBeginRenderingKHR(commandBuffers[frameIndex], dynamicRenderingInfo);
vkCmdBindPipeline(commandBuffers[frameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
vkCmdBindDescriptorSets(
commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout, 0, stack.longs(descriptorSet), null
commandBuffers[frameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout, 0, stack.longs(descriptorSets[frameIndex]), null
);
vkCmdBindVertexBuffers(
commandBuffer, 0,
commandBuffers[frameIndex], 0,
stack.longs(vertexBuffer.vkBuffer()), stack.longs(0)
);
vkCmdBindIndexBuffer(commandBuffer, indexBuffer.vkBuffer(), 0, VK_INDEX_TYPE_UINT32);
vkCmdBindIndexBuffer(commandBuffers[frameIndex], indexBuffer.vkBuffer(), 0, VK_INDEX_TYPE_UINT32);

var hostMatrixBuffer = memFloatBuffer(matrixBuffer.hostAddress(), 5 * 16);
var hostMatrixBuffer = memFloatBuffer(matrixBuffers[frameIndex].hostAddress(), 5 * 16);
cameraMatrices[0].get(0, hostMatrixBuffer);
cameraMatrices[1].get(16, hostMatrixBuffer);

Expand All @@ -342,55 +358,50 @@ protected void recordRenderCommands(
pushConstants.put(0, 0);
pushConstants.put(1, 0);
vkCmdPushConstants(
commandBuffer, pipelineLayout,
commandBuffers[frameIndex], pipelineLayout,
VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT, 0, pushConstants
);
vkCmdDrawIndexed(commandBuffer, 3, 1, 0, 0, 0);
vkCmdDrawIndexed(commandBuffers[frameIndex], 3, 1, 0, 0, 0);

if (leftHandMatrix != null) {
var giLeftClick = session.prepareSubactionState(stack, handClickAction, pathLeftHand);
var holdsLeft = session.getBooleanAction(stack, giLeftClick, "left click").currentState();
pushConstants.put(0, holdsLeft ? 0 : 1);
pushConstants.put(1, 1);
vkCmdPushConstants(
commandBuffer, pipelineLayout,
commandBuffers[frameIndex], pipelineLayout,
VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT, 0, pushConstants
);
vkCmdDrawIndexed(commandBuffer, 12, 1, 3, 0, 0);
vkCmdDrawIndexed(commandBuffers[frameIndex], 12, 1, 3, 0, 0);
}
if (rightHandMatrix != null) {
var giRightClick = session.prepareSubactionState(stack, handClickAction, pathRightHand);
var holdsRight = session.getBooleanAction(stack, giRightClick, "right click").currentState();
pushConstants.put(0, holdsRight ? 0 : 1);
pushConstants.put(1, 2);
vkCmdPushConstants(
commandBuffer, pipelineLayout,
commandBuffers[frameIndex], pipelineLayout,
VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT, 0, pushConstants
);
vkCmdDrawIndexed(commandBuffer, 12, 1, 3, 0, 0);
vkCmdDrawIndexed(commandBuffers[frameIndex], 12, 1, 3, 0, 0);
}

vkCmdEndRenderingKHR(commandBuffer);
vkCmdEndRenderingKHR(commandBuffers[frameIndex]);
commands.end();
}

@Override
protected void submitAndWaitRender() {
protected void submitRenderCommands() {
xr.boilerInstance.queueFamilies().graphics().first().submit(
commandBuffer, "Drawing", null, fence
commandBuffers[frameIndex], "Drawing", null, fences[frameIndex]
);

fence.waitAndReset();
assertVkSuccess(vkResetCommandPool(
xr.boilerInstance.vkDevice(), commandPool, 0
), "ResetCommandPool", "Drawing");
}
}

new HelloSessionLoop(session, renderSpace, swapchain, width, height).run();

boiler.sync.fenceBank.returnFence(fence);
vkDestroyCommandPool(boiler.vkDevice(), commandPool, null);
boiler.sync.fenceBank.returnFences(fences);
for (var commandPool : commandPools) vkDestroyCommandPool(boiler.vkDevice(), commandPool, null);
vkDestroyPipeline(boiler.vkDevice(), graphicsPipeline, null);
vkDestroyPipelineLayout(boiler.vkDevice(), pipelineLayout, null);
descriptorPool.destroy();
Expand All @@ -401,7 +412,7 @@ protected void submitAndWaitRender() {

vertexBuffer.destroy(boiler);
indexBuffer.destroy(boiler);
matrixBuffer.destroy(boiler);
for (var matrixBuffer : matrixBuffers) matrixBuffer.destroy(boiler);
vkDestroyImageView(boiler.vkDevice(), depthImage.vkImageView(), null);
vmaDestroyImage(boiler.vmaAllocator(), depthImage.vkImage(), depthImage.vmaAllocation());

Expand Down
Loading

0 comments on commit c07998b

Please sign in to comment.