From 43eb4831e0967985f53c02c2991316424c56bb4c Mon Sep 17 00:00:00 2001 From: knokko Date: Tue, 20 Aug 2024 13:33:02 +0200 Subject: [PATCH] Change internals of VkbFence, and write unit tests for FenceSubmission --- .../knokko/boiler/sync/FenceSubmission.java | 9 +- .../github/knokko/boiler/sync/VkbFence.java | 46 +++--- .../boiler/sync/TestFenceSubmission.java | 132 ++++++++++++++++++ 3 files changed, 161 insertions(+), 26 deletions(-) create mode 100644 src/test/java/com/github/knokko/boiler/sync/TestFenceSubmission.java diff --git a/src/main/java/com/github/knokko/boiler/sync/FenceSubmission.java b/src/main/java/com/github/knokko/boiler/sync/FenceSubmission.java index a5228d9..ed8e0b5 100644 --- a/src/main/java/com/github/knokko/boiler/sync/FenceSubmission.java +++ b/src/main/java/com/github/knokko/boiler/sync/FenceSubmission.java @@ -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); } } diff --git a/src/main/java/com/github/knokko/boiler/sync/VkbFence.java b/src/main/java/com/github/knokko/boiler/sync/VkbFence.java index b062c09..cae3868 100644 --- a/src/main/java/com/github/knokko/boiler/sync/VkbFence.java +++ b/src/main/java/com/github/knokko/boiler/sync/VkbFence.java @@ -11,13 +11,15 @@ public class VkbFence implements Comparable { 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 @@ -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; } @@ -51,30 +54,31 @@ 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() { @@ -82,20 +86,20 @@ public void awaitSignal() { } 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); } @@ -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() { diff --git a/src/test/java/com/github/knokko/boiler/sync/TestFenceSubmission.java b/src/test/java/com/github/knokko/boiler/sync/TestFenceSubmission.java new file mode 100644 index 0000000..972aeb1 --- /dev/null +++ b/src/test/java/com/github/knokko/boiler/sync/TestFenceSubmission.java @@ -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"); + } + } +}