Skip to content

Commit

Permalink
Change internals of VkbFence, and write unit tests for FenceSubmission
Browse files Browse the repository at this point in the history
  • Loading branch information
knokko committed Aug 20, 2024
1 parent 1803419 commit 43eb483
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,20 @@
public class FenceSubmission implements AwaitableSubmission {

private final VkbFence fence;
private final long submissionTime;
private final long referenceTime;

public FenceSubmission(VkbFence fence) {
// TODO Unit test this
this.fence = fence;
this.submissionTime = fence.getSubmissionTime();
this.referenceTime = fence.getCurrentTime();
}

@Override
public boolean hasCompleted() {
return fence.hasBeenSignaled(submissionTime);
return fence.hasBeenSignaled(referenceTime);
}

@Override
public void awaitCompletion() {
fence.awaitSubmission(submissionTime);
fence.awaitSubmission(referenceTime);
}
}
46 changes: 25 additions & 21 deletions src/main/java/com/github/knokko/boiler/sync/VkbFence.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ public class VkbFence implements Comparable<VkbFence> {

private final BoilerInstance instance;
private final long vkFence;
private long submissionTime;
private boolean signaled;

private long currentTime = 1L;
private long lastCompletedSubmission;
private boolean isPending;

public VkbFence(BoilerInstance instance, long vkFence, boolean startSignaled) {
this.instance = instance;
this.vkFence = vkFence;
this.signaled = startSignaled;
if (startSignaled) lastCompletedSubmission = 1L;
}

@Override
Expand All @@ -35,14 +37,15 @@ public void setName(String name, MemoryStack stack) {
}

public synchronized boolean isPending() {
if (submissionTime == 0) return false;
if (!isPending) return false;

var status = vkGetFenceStatus(instance.vkDevice(), vkFence);
if (status == VK_NOT_READY) return true;
assertVkSuccess(status, "GetFenceStatus", "BoilerFence.isPending");

submissionTime = 0;
signaled = true;
currentTime += 1;
lastCompletedSubmission = currentTime;
isPending = false;
return false;
}

Expand All @@ -51,51 +54,52 @@ public synchronized boolean isPending() {
* You should use this when you submit the fence manually.
*/
public synchronized long getVkFenceAndSubmit() {
if (submissionTime != 0) throw new IllegalStateException("This fence is already pending");
if (signaled) throw new IllegalStateException("This fence is still signaled");
submissionTime = System.nanoTime();
if (isPending) throw new IllegalStateException("This fence is already pending");
if (lastCompletedSubmission >= currentTime) throw new IllegalStateException("This fence is still signaled");
isPending = true;
return vkFence;
}

public long getSubmissionTime() {
return submissionTime;
synchronized long getCurrentTime() {
return currentTime;
}

public synchronized void reset() {
if (isPending()) throw new IllegalStateException("Fence is still pending");

signaled = false;
try (var stack = stackPush()) {
assertVkSuccess(vkResetFences(
instance.vkDevice(), stack.longs(vkFence)
), "ResetFences", "VkbFence.reset");
}

if (lastCompletedSubmission == currentTime) currentTime += 1;
}

public synchronized void signal() {
if (isPending()) throw new IllegalStateException("Fence is still pending");
signaled = true;
lastCompletedSubmission = currentTime;
}

public void awaitSignal() {
awaitSignal(instance.defaultTimeout);
}

public synchronized void awaitSignal(long timeout) {
if (signaled) return;
if (submissionTime == 0) throw new IllegalStateException("Fence is not signaled, nor pending");
if (lastCompletedSubmission == currentTime) return;
if (!isPending) throw new IllegalStateException("Fence is not signaled, nor pending");

try (var stack = stackPush()) {
assertVkSuccess(vkWaitForFences(
instance.vkDevice(), stack.longs(vkFence), true, timeout
), "WaitForFences", "VkbFence.awaitSignal");
}
signaled = true;
submissionTime = 0;
lastCompletedSubmission = currentTime;
isPending = false;
}

public synchronized void waitIfSubmitted(long timeout) {
if (submissionTime == 0) return;
if (!isPending) return;
awaitSignal(timeout);
}

Expand All @@ -113,17 +117,17 @@ public synchronized void waitAndReset(long timeout) {
}

public synchronized void awaitSubmission(long referenceSubmissionTime) {
if (submissionTime > referenceSubmissionTime) return;
if (lastCompletedSubmission >= referenceSubmissionTime) return;
awaitSignal();
}

public synchronized boolean isSignaled() {
return hasBeenSignaled(submissionTime);
return hasBeenSignaled(currentTime);
}

public synchronized boolean hasBeenSignaled(long referenceSubmissionTime) {
isPending();
return signaled || submissionTime > referenceSubmissionTime;
return lastCompletedSubmission >= referenceSubmissionTime;
}

void destroy() {
Expand Down
132 changes: 132 additions & 0 deletions src/test/java/com/github/knokko/boiler/sync/TestFenceSubmission.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package com.github.knokko.boiler.sync;

import com.github.knokko.boiler.builder.BoilerBuilder;
import com.github.knokko.boiler.instance.BoilerInstance;
import org.junit.jupiter.api.Test;
import org.lwjgl.vulkan.VkSubmitInfo;

import static com.github.knokko.boiler.exceptions.VulkanFailureException.assertVkSuccess;
import static java.lang.Thread.sleep;
import static org.junit.jupiter.api.Assertions.*;
import static org.lwjgl.system.MemoryStack.stackPush;
import static org.lwjgl.vulkan.VK10.VK_API_VERSION_1_0;
import static org.lwjgl.vulkan.VK10.vkQueueSubmit;
import static org.lwjgl.vulkan.VK12.VK_API_VERSION_1_2;

public class TestFenceSubmission {

@Test
public void testHasCompleted() throws InterruptedException {
var boiler = new BoilerBuilder(
VK_API_VERSION_1_0, "TestFenceSubmission", 1
).validation().forbidValidationErrors().build();

var fence0 = boiler.sync.fenceBank.borrowFence(false, "StartZero");
var fence1 = boiler.sync.fenceBank.borrowFence(true, "StartOne");

var submission0a = new FenceSubmission(fence0);
var submission1a = new FenceSubmission(fence1);

assertFalse(submission0a.hasCompleted());
assertTrue(submission1a.hasCompleted());

fence1.reset();
assertTrue(submission1a.hasCompleted());
var submission1b = new FenceSubmission(fence1);
assertFalse(submission1b.hasCompleted());

fence0.signal();
assertTrue(submission0a.hasCompleted());
fence0.reset();
var submission0b = new FenceSubmission(fence0);
assertTrue(submission0a.hasCompleted());
assertFalse(submission0b.hasCompleted());

fence0.signal();
assertTrue(submission0a.hasCompleted());
assertTrue(submission0b.hasCompleted());
assertTrue(new FenceSubmission(fence0).hasCompleted());
fence0.reset();
assertTrue(submission0a.hasCompleted());
assertTrue(submission0b.hasCompleted());
var submission0c = new FenceSubmission(fence0);
assertFalse(submission0c.hasCompleted());

emptySubmission(boiler, fence0);
sleep(100);
assertTrue(submission0a.hasCompleted());
assertTrue(submission0c.hasCompleted());
fence0.waitAndReset();
var submission0d = new FenceSubmission(fence0);
assertTrue(submission0a.hasCompleted());
assertTrue(submission0c.hasCompleted());
assertFalse(submission0d.hasCompleted());

emptySubmission(boiler, fence0);
sleep(100);
assertTrue(submission0a.hasCompleted());
assertTrue(submission0b.hasCompleted());
assertTrue(submission0c.hasCompleted());
assertTrue(submission0d.hasCompleted());

boiler.sync.fenceBank.returnFences(fence0, fence1);
boiler.destroyInitialObjects();
}

@Test
public void testAwaitCompletion() {
var boiler = new BoilerBuilder(
VK_API_VERSION_1_2, "TestAwaitCompletion", 1
).validation().forbidValidationErrors().build();

var fence0 = boiler.sync.fenceBank.borrowFence(false, "Start0");
var fence1 = boiler.sync.fenceBank.borrowFence(true, "Start1");

var submission0a = new FenceSubmission(fence0);
var submission1a = new FenceSubmission(fence1);

assertCannotAwait(submission0a);
assertInstantAwait(submission1a);

fence0.signal();
fence1.reset();

var submission0b = new FenceSubmission(fence0);
var submission1b = new FenceSubmission(fence1);

assertInstantAwait(submission0a);
assertInstantAwait(submission1a);
assertInstantAwait(submission0b);
assertCannotAwait(submission1b);

emptySubmission(boiler, fence1);
assertInstantAwait(submission1b);

boiler.sync.fenceBank.returnFences(fence0, fence1);
boiler.destroyInitialObjects();
}

private void assertInstantAwait(FenceSubmission submission) {
long startTime = System.nanoTime();
submission.awaitCompletion();
long passedTime = System.nanoTime() - startTime;
assertTrue(passedTime <= 100_000_000, "Expected passed time (" + passedTime + ") to be at most 100ms");
}

private void assertCannotAwait(FenceSubmission submission) {
assertEquals(assertThrows(
IllegalStateException.class, submission::awaitCompletion
).getMessage(), "Fence is not signaled, nor pending");
}

private void emptySubmission(BoilerInstance boiler, VkbFence fence) {
try (var stack = stackPush()) {
var emptySubmitInfo = VkSubmitInfo.calloc(stack);
emptySubmitInfo.sType$Default();

assertVkSuccess(vkQueueSubmit(
boiler.queueFamilies().compute().queues().get(0).vkQueue(), emptySubmitInfo, fence.getVkFenceAndSubmit()
), "QueueSubmit", "test");
}
}
}

0 comments on commit 43eb483

Please sign in to comment.