-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add FixedDescriptorBank and GrowingDescriptorBank
- Loading branch information
Showing
4 changed files
with
378 additions
and
0 deletions.
There are no files selected for viewing
96 changes: 96 additions & 0 deletions
96
src/main/java/com/github/knokko/boiler/descriptors/FixedDescriptorBank.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package com.github.knokko.boiler.descriptors; | ||
|
||
import com.github.knokko.boiler.instance.BoilerInstance; | ||
import org.lwjgl.system.MemoryStack; | ||
import org.lwjgl.vulkan.VkDescriptorPoolCreateInfo; | ||
import org.lwjgl.vulkan.VkDescriptorSetAllocateInfo; | ||
|
||
import java.util.concurrent.ConcurrentSkipListSet; | ||
import java.util.function.BiConsumer; | ||
|
||
import static com.github.knokko.boiler.exceptions.VulkanFailureException.assertVkSuccess; | ||
import static org.lwjgl.system.MemoryStack.stackPush; | ||
import static org.lwjgl.vulkan.VK10.*; | ||
|
||
/** | ||
* <p> | ||
* A <i>FixedDescriptorBank</i> is a descriptor pool from which descriptor sets of one specific descriptor set layout | ||
* can be 'borrowed'. All descriptor sets are allocated right after creation of the descriptor pool, and are never | ||
* freed or reset. Instead, descriptor sets can be 'borrowed' and 'returned', after which they can be 'borrowed' again. | ||
* </p> | ||
* | ||
* <p> | ||
* <i>FixedDescriptorBank</i>s are 'fixed' in the sense that they have a bounded capacity specified in the | ||
* constructor, and can't grow any bigger. You can use <i>GrowingDescriptorBank</i> instead if you don't like this | ||
* limitation. | ||
* </p> | ||
* | ||
* Borrowing and returning descriptor sets is thread-safe, but destroying the bank is not: the bank can only be | ||
* destroyed when all borrows and returns have been completed. | ||
*/ | ||
public class FixedDescriptorBank { | ||
|
||
private final BoilerInstance instance; | ||
private final long descriptorPool; | ||
|
||
private final ConcurrentSkipListSet<Long> borrowedDescriptorSets; | ||
private final ConcurrentSkipListSet<Long> unusedDescriptorSets; | ||
|
||
public FixedDescriptorBank( | ||
BoilerInstance instance, long descriptorSetLayout, String context, | ||
BiConsumer<MemoryStack, VkDescriptorPoolCreateInfo> configureDescriptorPool | ||
) { | ||
this.instance = instance; | ||
try (var stack = stackPush()) { | ||
var ciPool = VkDescriptorPoolCreateInfo.calloc(stack); | ||
ciPool.sType$Default(); | ||
configureDescriptorPool.accept(stack, ciPool); | ||
int capacity = ciPool.maxSets(); | ||
|
||
var pPool = stack.callocLong(1); | ||
assertVkSuccess(vkCreateDescriptorPool( | ||
instance.vkDevice(), ciPool, null, pPool | ||
), "CreateDescriptorPool", "DescriptorBank" + context); | ||
this.descriptorPool = pPool.get(0); | ||
|
||
var aiSets = VkDescriptorSetAllocateInfo.calloc(stack); | ||
aiSets.sType$Default(); | ||
aiSets.descriptorPool(descriptorPool); | ||
aiSets.pSetLayouts(stack.longs(descriptorSetLayout)); | ||
|
||
var pSets = stack.callocLong(capacity); | ||
assertVkSuccess(vkAllocateDescriptorSets( | ||
instance.vkDevice(), aiSets, pSets | ||
), "AllocateDescriptorSets", "DescriptorBank" + context); | ||
|
||
this.unusedDescriptorSets = new ConcurrentSkipListSet<>(); | ||
for (int index = 0; index < capacity; index++) { | ||
this.unusedDescriptorSets.add(pSets.get(index)); | ||
} | ||
this.borrowedDescriptorSets = new ConcurrentSkipListSet<>(); | ||
} | ||
} | ||
|
||
/** | ||
* Note: this method returns null when all descriptor sets are currently borrowed. | ||
*/ | ||
public Long borrowDescriptorSet() { | ||
Long result = unusedDescriptorSets.pollFirst(); | ||
if (result != null) borrowedDescriptorSets.add(result); | ||
return result; | ||
} | ||
|
||
public void returnDescriptorSet(long descriptorSet) { | ||
if (!borrowedDescriptorSets.remove(descriptorSet)) { | ||
throw new IllegalArgumentException(descriptorSet + " wasn't borrowed"); | ||
} | ||
unusedDescriptorSets.add(descriptorSet); | ||
} | ||
|
||
public void destroy(boolean checkEmpty) { | ||
if (checkEmpty && !borrowedDescriptorSets.isEmpty()) { | ||
throw new IllegalStateException("Not all descriptor sets have been returned"); | ||
} | ||
vkDestroyDescriptorPool(instance.vkDevice(), descriptorPool, null); | ||
} | ||
} |
136 changes: 136 additions & 0 deletions
136
src/main/java/com/github/knokko/boiler/descriptors/GrowingDescriptorBank.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
package com.github.knokko.boiler.descriptors; | ||
|
||
import com.github.knokko.boiler.instance.BoilerInstance; | ||
import org.lwjgl.system.MemoryStack; | ||
import org.lwjgl.vulkan.VkDescriptorPoolCreateInfo; | ||
import org.lwjgl.vulkan.VkDescriptorSetAllocateInfo; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.concurrent.ConcurrentSkipListSet; | ||
import java.util.function.BiConsumer; | ||
|
||
import static com.github.knokko.boiler.exceptions.VulkanFailureException.assertVkSuccess; | ||
import static org.lwjgl.system.MemoryStack.stackPush; | ||
import static org.lwjgl.vulkan.VK10.*; | ||
|
||
/** | ||
* <p> | ||
* A <i>GrowingDescriptorBank</i> is a 'bank' of descriptor sets in the sense that users can borrow descriptor sets | ||
* from it, and return those once they are no longer needed. Once allocated, descriptor sets from the bank will | ||
* never be freed or reset: when they are returned, they can simply be borrowed again. When the bank is destroyed, | ||
* all descriptor pools will be destroyed. | ||
* </p> | ||
* | ||
* <p> | ||
* Unlike <i>FixedDescriptorBank</i>s, <i>GrowingDescriptorBank</i>s have unlimited capacity: when all descriptor | ||
* sets have been borrowed, it will create an additional (bigger) descriptor pool. But, just like fixed banks, | ||
* growing banks also support only descriptor sets from 1 descriptor set layout. | ||
* </p> | ||
* | ||
* Borrowing and returning descriptor sets is thread-safe, but destroying the bank is not: the bank can only be | ||
* destroyed when all borrows and returns have been completed. | ||
*/ | ||
public class GrowingDescriptorBank { | ||
|
||
private final BoilerInstance instance; | ||
private final long descriptorSetLayout; | ||
private final String name; | ||
private final BiConsumer<MemoryStack, VkDescriptorPoolCreateInfo> configureDescriptorPool; | ||
|
||
private final List<Long> descriptorPools = new ArrayList<>(); | ||
private int nextCapacity = 2; | ||
|
||
|
||
private final ConcurrentSkipListSet<Long> unusedDescriptorSets, borrowedDescriptorSets; | ||
|
||
/** | ||
* @param name Debugging purposes only | ||
* @param configureDescriptorPool Populate the given <i>VkDescriptorPoolCreateInfo</i>. You should use this to | ||
* set <i>pPoolSizes</i> for a descriptor pool that can hold exactly <b>1</b> | ||
* descriptor set. (The <i>descriptorCount</i>s will automatically be multiplied | ||
* with the capacity of the descriptor pool.) You should ignore <i>maxSets</i> and | ||
* you can optionally set the <i>flags</i>. | ||
*/ | ||
public GrowingDescriptorBank( | ||
BoilerInstance instance, long descriptorSetLayout, String name, | ||
BiConsumer<MemoryStack, VkDescriptorPoolCreateInfo> configureDescriptorPool | ||
) { | ||
this.instance = instance; | ||
this.descriptorSetLayout = descriptorSetLayout; | ||
this.name = name; | ||
this.configureDescriptorPool = configureDescriptorPool; | ||
this.unusedDescriptorSets = new ConcurrentSkipListSet<>(); | ||
this.borrowedDescriptorSets = new ConcurrentSkipListSet<>(); | ||
} | ||
|
||
public long borrowDescriptorSet() { | ||
Long maybeResult = unusedDescriptorSets.pollFirst(); | ||
if (maybeResult == null) { | ||
synchronized (this) { | ||
|
||
// Ensure that it's not possible to create 2 new pools at the same time | ||
maybeResult = unusedDescriptorSets.pollFirst(); | ||
if (maybeResult == null) { | ||
|
||
try (var stack = stackPush()) { | ||
var ciPool = VkDescriptorPoolCreateInfo.calloc(stack); | ||
ciPool.sType$Default(); | ||
configureDescriptorPool.accept(stack, ciPool); | ||
ciPool.maxSets(nextCapacity); | ||
for (var poolSize : Objects.requireNonNull(ciPool.pPoolSizes())) { | ||
poolSize.descriptorCount(nextCapacity * poolSize.descriptorCount()); | ||
} | ||
|
||
var pPool = stack.callocLong(1); | ||
assertVkSuccess(vkCreateDescriptorPool( | ||
instance.vkDevice(), ciPool, null, pPool | ||
), "CreateDescriptorPool", "GrowingDescriptorBank-" + name + "-" + nextCapacity); | ||
long newDescriptorPool = pPool.get(0); | ||
|
||
descriptorPools.add(newDescriptorPool); | ||
|
||
var pSetLayouts = stack.callocLong(nextCapacity); | ||
for (int index = 0; index < nextCapacity; index++) { | ||
pSetLayouts.put(index, descriptorSetLayout); | ||
} | ||
var aiSets = VkDescriptorSetAllocateInfo.calloc(stack); | ||
aiSets.sType$Default(); | ||
aiSets.descriptorPool(newDescriptorPool); | ||
aiSets.pSetLayouts(pSetLayouts); | ||
|
||
var pSets = stack.callocLong(nextCapacity); | ||
assertVkSuccess(vkAllocateDescriptorSets( | ||
instance.vkDevice(), aiSets, pSets | ||
), "AllocateDescriptorSets", "GrowingDescriptorBank-" + name + "-" + nextCapacity); | ||
|
||
maybeResult = pSets.get(0); | ||
for (int index = 1; index < nextCapacity; index++) { | ||
unusedDescriptorSets.add(pSets.get(index)); | ||
} | ||
nextCapacity *= 2; | ||
} | ||
} | ||
} | ||
} | ||
borrowedDescriptorSets.add(maybeResult); | ||
return maybeResult; | ||
} | ||
|
||
public void returnDescriptorSet(long descriptorSet) { | ||
if (!borrowedDescriptorSets.remove(descriptorSet)) { | ||
throw new IllegalArgumentException("Descriptor set " + descriptorSet + " wasn't borrowed"); | ||
} | ||
unusedDescriptorSets.add(descriptorSet); | ||
} | ||
|
||
public void destroy(boolean checkBorrows) { | ||
if (checkBorrows && !borrowedDescriptorSets.isEmpty()) { | ||
throw new IllegalStateException("Not all borrowed descriptor sets have been returned"); | ||
} | ||
for (long descriptorPool : descriptorPools) { | ||
vkDestroyDescriptorPool(instance.vkDevice(), descriptorPool, null); | ||
} | ||
} | ||
} |
69 changes: 69 additions & 0 deletions
69
src/test/java/com/github/knokko/boiler/descriptors/TestFixedDescriptorBank.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package com.github.knokko.boiler.descriptors; | ||
|
||
import com.github.knokko.boiler.builder.BoilerBuilder; | ||
import com.github.knokko.boiler.builder.instance.ValidationFeatures; | ||
import org.junit.jupiter.api.Test; | ||
import org.lwjgl.vulkan.VkDescriptorPoolSize; | ||
import org.lwjgl.vulkan.VkDescriptorSetLayoutBinding; | ||
|
||
import static org.junit.jupiter.api.Assertions.*; | ||
import static org.lwjgl.system.MemoryStack.stackPush; | ||
import static org.lwjgl.vulkan.VK10.*; | ||
|
||
public class TestFixedDescriptorBank { | ||
|
||
@Test | ||
public void testFixedDescriptorBank() { | ||
var boiler = new BoilerBuilder(VK_API_VERSION_1_0, "TestFixedDescriptorBank", 1) | ||
.validation(new ValidationFeatures(false, false, false, false, false)) | ||
.build(); | ||
|
||
long descriptorSetLayout; | ||
try (var stack = stackPush()) { | ||
var bindings = VkDescriptorSetLayoutBinding.calloc(1, stack); | ||
bindings.binding(0); | ||
bindings.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); | ||
bindings.descriptorCount(5); | ||
bindings.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); | ||
|
||
descriptorSetLayout = boiler.descriptors.createLayout(stack, bindings, "Test"); | ||
} | ||
var bank = new FixedDescriptorBank(boiler, descriptorSetLayout, "Test", (stack, ciPool) -> { | ||
var poolSizes = VkDescriptorPoolSize.calloc(1, stack); | ||
poolSizes.type(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); | ||
poolSizes.descriptorCount(5); | ||
|
||
ciPool.flags(0); | ||
ciPool.maxSets(2); | ||
ciPool.pPoolSizes(poolSizes); | ||
}); | ||
|
||
long descriptorSet1 = bank.borrowDescriptorSet(); | ||
long descriptorSet2 = bank.borrowDescriptorSet(); | ||
assertNotEquals(descriptorSet1, descriptorSet2); | ||
assertNull(bank.borrowDescriptorSet()); | ||
|
||
bank.returnDescriptorSet(descriptorSet1); | ||
assertEquals(descriptorSet1, bank.borrowDescriptorSet()); | ||
|
||
bank.returnDescriptorSet(descriptorSet2); | ||
assertEquals(descriptorSet2, bank.borrowDescriptorSet()); | ||
|
||
assertNull(bank.borrowDescriptorSet()); | ||
|
||
bank.returnDescriptorSet(descriptorSet1); | ||
bank.returnDescriptorSet(descriptorSet2); | ||
|
||
long descriptorSet12 = bank.borrowDescriptorSet(); | ||
long descriptorSet21 = bank.borrowDescriptorSet(); | ||
assertNull(bank.borrowDescriptorSet()); | ||
assertNotEquals(descriptorSet12, descriptorSet21); | ||
assertTrue(descriptorSet12 == descriptorSet1 || descriptorSet12 == descriptorSet2); | ||
assertTrue(descriptorSet21 == descriptorSet1 || descriptorSet21 == descriptorSet2); | ||
|
||
bank.destroy(false); | ||
|
||
vkDestroyDescriptorSetLayout(boiler.vkDevice(), descriptorSetLayout, null); | ||
boiler.destroyInitialObjects(); | ||
} | ||
} |
77 changes: 77 additions & 0 deletions
77
src/test/java/com/github/knokko/boiler/descriptors/TestGrowingDescriptorBank.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package com.github.knokko.boiler.descriptors; | ||
|
||
import com.github.knokko.boiler.builder.BoilerBuilder; | ||
import com.github.knokko.boiler.builder.instance.ValidationFeatures; | ||
import org.junit.jupiter.api.Test; | ||
import org.lwjgl.vulkan.VkDescriptorPoolSize; | ||
import org.lwjgl.vulkan.VkDescriptorSetLayoutBinding; | ||
|
||
import java.util.HashSet; | ||
import java.util.Set; | ||
|
||
import static org.junit.jupiter.api.Assertions.*; | ||
import static org.lwjgl.system.MemoryStack.stackPush; | ||
import static org.lwjgl.vulkan.VK10.*; | ||
|
||
public class TestGrowingDescriptorBank { | ||
|
||
@Test | ||
public void testGrowingDescriptorBank() { | ||
var boiler = new BoilerBuilder(VK_API_VERSION_1_0, "TestFixedDescriptorBank", 1) | ||
.validation(new ValidationFeatures(false, false, false, false, false)) | ||
.build(); | ||
|
||
long descriptorSetLayout; | ||
try (var stack = stackPush()) { | ||
var bindings = VkDescriptorSetLayoutBinding.calloc(1, stack); | ||
bindings.binding(0); | ||
bindings.descriptorType(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE); | ||
bindings.descriptorCount(3); | ||
bindings.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); | ||
|
||
descriptorSetLayout = boiler.descriptors.createLayout(stack, bindings, "Test"); | ||
} | ||
|
||
var bank = new GrowingDescriptorBank(boiler, descriptorSetLayout, "Test", (stack, ciPool) -> { | ||
var poolSizes = VkDescriptorPoolSize.calloc(1, stack); | ||
poolSizes.type(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE); | ||
poolSizes.descriptorCount(3); | ||
|
||
ciPool.pPoolSizes(poolSizes); | ||
}); | ||
|
||
var initialSets = new long[15]; | ||
for (int index = 0; index < initialSets.length; index++) { | ||
initialSets[index] = bank.borrowDescriptorSet(); | ||
} | ||
assertUnique(initialSets); | ||
|
||
for (int index = 5; index < 12; index++) { | ||
bank.returnDescriptorSet(initialSets[index]); | ||
} | ||
|
||
var finalSets = new HashSet<Long>(); | ||
for (int index = 0; index < 200; index++) { | ||
finalSets.add(bank.borrowDescriptorSet()); | ||
} | ||
assertEquals(200, finalSets.size()); | ||
|
||
for (int index = 0; index < initialSets.length; index++) { | ||
if (index >= 5 && index < 12) { | ||
assertTrue(finalSets.contains(initialSets[index])); | ||
} else { | ||
assertFalse(finalSets.contains(initialSets[index])); | ||
} | ||
} | ||
|
||
bank.destroy(false); | ||
vkDestroyDescriptorSetLayout(boiler.vkDevice(), descriptorSetLayout, null); | ||
boiler.destroyInitialObjects(); | ||
} | ||
|
||
private void assertUnique(long[] array) { | ||
Set<Long> set = new HashSet<>(array.length); | ||
for (long element : array) set.add(element); | ||
assertEquals(set.size(), array.length); | ||
} | ||
} |