diff --git a/build.gradle b/build.gradle index 6842319..f73e325 100644 --- a/build.gradle +++ b/build.gradle @@ -81,6 +81,7 @@ project(":samples") { implementation "org.lwjgl:lwjgl" implementation "org.lwjgl:lwjgl-vma" implementation "org.lwjgl:lwjgl-vulkan" + implementation "org.lwjgl:lwjgl-ktx" implementation "org.lwjgl:lwjgl-glfw" implementation "org.lwjgl:lwjgl-openxr" implementation "org.joml:joml:${jomlVersion}" @@ -89,6 +90,7 @@ project(":samples") { runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives" runtimeOnly "org.lwjgl:lwjgl-vma::$lwjglNatives" runtimeOnly "org.lwjgl:lwjgl-openxr::$lwjglNatives" + runtimeOnly "org.lwjgl:lwjgl-ktx::$lwjglNatives" if (lwjglNatives == "natives-macos") runtimeOnly "org.lwjgl:lwjgl-vulkan::$lwjglNatives" } shadowJar { diff --git a/samples/src/main/java/com/github/knokko/boiler/samples/HelloKtx.java b/samples/src/main/java/com/github/knokko/boiler/samples/HelloKtx.java new file mode 100644 index 0000000..98af4cf --- /dev/null +++ b/samples/src/main/java/com/github/knokko/boiler/samples/HelloKtx.java @@ -0,0 +1,224 @@ +package com.github.knokko.boiler.samples; + +import com.github.knokko.boiler.BoilerInstance; +import com.github.knokko.boiler.builders.BoilerBuilder; +import com.github.knokko.boiler.builders.WindowBuilder; +import com.github.knokko.boiler.commands.CommandRecorder; +import com.github.knokko.boiler.descriptors.HomogeneousDescriptorPool; +import com.github.knokko.boiler.descriptors.VkbDescriptorSetLayout; +import com.github.knokko.boiler.images.VkbImage; +import com.github.knokko.boiler.pipelines.GraphicsPipelineBuilder; +import com.github.knokko.boiler.synchronization.ResourceUsage; +import com.github.knokko.boiler.utilities.ReflectionHelper; +import com.github.knokko.boiler.window.AcquiredImage; +import com.github.knokko.boiler.window.SimpleWindowRenderLoop; +import com.github.knokko.boiler.window.VkbWindow; +import com.github.knokko.boiler.window.WindowEventLoop; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.util.ktx.*; +import org.lwjgl.vulkan.*; + +import javax.imageio.ImageIO; + +import java.io.IOException; +import java.util.Objects; + +import static com.github.knokko.boiler.exceptions.VulkanFailureException.assertVkSuccess; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.util.ktx.KTXVulkan.ktxVulkanDeviceInfo_Construct; +import static org.lwjgl.vulkan.KHRSurface.VK_PRESENT_MODE_FIFO_KHR; +import static org.lwjgl.vulkan.VK10.*; +import static org.lwjgl.vulkan.VK12.VK_API_VERSION_1_2; + +public class HelloKtx extends SimpleWindowRenderLoop { + + private static void assertKtxSuccess(int result) { + if (result != KTX.KTX_SUCCESS) throw new RuntimeException("returned " + result + KTX.ktxErrorString(result)); + } + + @SuppressWarnings("resource") + public static void main(String[] args) { + var boiler = new BoilerBuilder( + VK_API_VERSION_1_2, "HelloKtx", 1 + ) + .validation() + .enableDynamicRendering() + .addWindow(new WindowBuilder(800, 500, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) + .requiredFeatures10(VkPhysicalDeviceFeatures::textureCompressionBC) + .featurePicker10(((stack, supportedFeatures, toEnable) -> toEnable.textureCompressionBC(true))) + .build(); + +// try (var stack = stackPush()) { +// var pTexture = stack.callocPointer(1); +// assertKtxSuccess(KTX.ktxTexture_CreateFromNamedFile(stack.ASCII("test2-bc1.ktx2"), 1, pTexture)); +// var ktxTexture = ktxTexture2.create(pTexture.get(0)); +// +// // TODO Using AMD compressonator CLI: .\compressonatorcli.exe -fd bc7 test1.png test1-bc7.ktx2 +// int format = KTXVulkan.ktxTexture2_GetVkFormat(ktxTexture); +// System.out.println("format is " + format + ": " + ReflectionHelper.getIntConstantName(VK13.class, format, "VK_FORMAT_", "", "unknown")); +// +// var uploadCommandPool = boiler.commands.createPool(0, boiler.queueFamilies().graphics().index(), "KtxPool"); +// +// var deviceInfo = ktxVulkanDeviceInfo.calloc(stack); +// assertKtxSuccess(ktxVulkanDeviceInfo_Construct( +// deviceInfo, boiler.vkPhysicalDevice(), boiler.vkDevice(), +// boiler.queueFamilies().graphics().first().vkQueue(), +// uploadCommandPool, null +// )); +// +// var ktxVkTexture = ktxVulkanTexture.calloc(stack); +// +// assertKtxSuccess(KTXVulkan.ktxTexture2_VkUpload(ktxTexture, deviceInfo, ktxVkTexture)); +// +// System.out.println("size is " + ktxVkTexture.width() + " x " + ktxVkTexture.height()); +// KTXVulkan.ktxVulkanTexture_Destruct(ktxVkTexture, boiler.vkDevice(), null); +// KTXVulkan.ktxVulkanDeviceInfo_Destruct(deviceInfo); +// +// vkDestroyCommandPool(boiler.vkDevice(), uploadCommandPool, null); +// } + + var eventLoop = new WindowEventLoop(); + eventLoop.addWindow(new HelloKtx(boiler.window())); + eventLoop.runMain(); + + boiler.destroyInitialObjects(); + } + + public HelloKtx(VkbWindow window) { + super( + window, 1, true, VK_PRESENT_MODE_FIFO_KHR, + ResourceUsage.COLOR_ATTACHMENT_WRITE, ResourceUsage.COLOR_ATTACHMENT_WRITE + ); + } + + private VkbImage simpleImage; + + private long sampler; + private VkbDescriptorSetLayout descriptorSetLayout; + private HomogeneousDescriptorPool descriptorPool; + private long descriptorSet; + private long pipelineLayout, graphicsPipeline; + + @Override + @SuppressWarnings("resource") + protected void setup(BoilerInstance boiler, MemoryStack stack) { + super.setup(boiler, stack); + + this.simpleImage = boiler.images.createSimple( + 16, 16, VK_FORMAT_R8G8B8A8_SRGB, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, "SimpleImage" + ); + + this.sampler = boiler.images.createSimpleSampler( + VK_FILTER_NEAREST, VK_SAMPLER_MIPMAP_MODE_NEAREST, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, "TheSampler" + ); + + var descriptorBindings = VkDescriptorSetLayoutBinding.calloc(2, stack); + boiler.descriptors.binding(descriptorBindings, 0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_SHADER_STAGE_FRAGMENT_BIT); + descriptorBindings.get(0).descriptorCount(4); + boiler.descriptors.binding(descriptorBindings, 1, VK_DESCRIPTOR_TYPE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT); + + this.descriptorSetLayout = boiler.descriptors.createLayout(stack, descriptorBindings, "ImagesLayout"); + this.descriptorPool = descriptorSetLayout.createPool(1, 0, "ImagesPool"); + this.descriptorSet = descriptorPool.allocate(1)[0]; + + var writeImages = VkDescriptorImageInfo.calloc(4, stack); + for (int index = 0; index < 4; index++) { // TODO Use compressed textures for the last 3 indices + var simpleImageWrite = writeImages.get(index); + simpleImageWrite.imageView(this.simpleImage.vkImageView()); + simpleImageWrite.imageLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + } + + var writeSamplers = VkDescriptorImageInfo.calloc(1, stack); + writeSamplers.sampler(sampler); + + var descriptorWrites = VkWriteDescriptorSet.calloc(2, stack); + boiler.descriptors.writeImage(descriptorWrites, descriptorSet, 0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, writeImages); + boiler.descriptors.writeImage(descriptorWrites, descriptorSet, 1, VK_DESCRIPTOR_TYPE_SAMPLER, writeSamplers); + + vkUpdateDescriptorSets(boiler.vkDevice(), descriptorWrites, null); + this.pipelineLayout = boiler.pipelines.createLayout(null, "CompressedLayout", descriptorSetLayout.vkDescriptorSetLayout); + + var builder = new GraphicsPipelineBuilder(boiler, stack); + builder.simpleShaderStages( + "CompressedPipeline", + "com/github/knokko/boiler/samples/graphics/ktx.vert.spv", + "com/github/knokko/boiler/samples/graphics/ktx.frag.spv" + ); + builder.noVertexInput(); + builder.simpleInputAssembly(); + builder.dynamicViewports(1); + builder.simpleRasterization(VK_CULL_MODE_NONE); + builder.noMultisampling(); + builder.noDepthStencil(); + builder.noColorBlending(1); + builder.dynamicStates(VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_VIEWPORT); + builder.ciPipeline.layout(pipelineLayout); + builder.dynamicRendering(0, VK_FORMAT_UNDEFINED, VK_FORMAT_UNDEFINED, window.surfaceFormat); + this.graphicsPipeline = builder.build("CompressedPipeline"); + + var stagingBuffer = boiler.buffers.createMapped(4 * 16 * 16, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, "StagingBuffer"); + try { + var bufferedImage = ImageIO.read(Objects.requireNonNull(HelloKtx.class.getClassLoader().getResourceAsStream( + "com/github/knokko/boiler/samples/images/test1.png" + ))); + boiler.buffers.encodeBufferedImageRGBA(stagingBuffer, bufferedImage, 0); + } catch (IOException e) { + throw new RuntimeException(e); + } + + var stagingCommandPool = boiler.commands.createPool(0, boiler.queueFamilies().graphics().index(), "StagingCommandPool"); + var stagingCommandBuffer = boiler.commands.createPrimaryBuffers(stagingCommandPool, 1, "StagingCommandBuffer")[0]; + var recorder = CommandRecorder.begin(stagingCommandBuffer, boiler, stack, "Staging"); + recorder.transitionLayout(this.simpleImage, null, ResourceUsage.TRANSFER_DEST); + recorder.copyBufferToImage(this.simpleImage, stagingBuffer.vkBuffer());// TODO Use buffer range instead? + recorder.transitionLayout(this.simpleImage, ResourceUsage.TRANSFER_DEST, ResourceUsage.shaderRead(VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT)); + recorder.end(); + + var stagingFence = boiler.sync.fenceBank.borrowFence(false, "StagingFence"); + boiler.queueFamilies().graphics().first().submit(stagingCommandBuffer, "Staging", null, stagingFence); + stagingFence.awaitSignal(); + boiler.sync.fenceBank.returnFence(stagingFence); + + vkDestroyCommandPool(boiler.vkDevice(), stagingCommandPool, null); + stagingBuffer.destroy(boiler); + } + + @Override + protected void recordFrame( + MemoryStack stack, int frameIndex, CommandRecorder recorder, + AcquiredImage acquiredImage, BoilerInstance instance + ) { + var colorAttachments = VkRenderingAttachmentInfo.calloc(1, stack); + recorder.simpleColorRenderingAttachment( + colorAttachments.get(0), acquiredImage.image().vkImageView(), + VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE, + 1f, 0f, 0f, 1f + ); + recorder.beginSimpleDynamicRendering( + acquiredImage.width(), acquiredImage.height(), + colorAttachments, null, null + ); + vkCmdBindPipeline(recorder.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + recorder.dynamicViewportAndScissor(acquiredImage.width(), acquiredImage.height()); + vkCmdBindDescriptorSets( + recorder.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, + 0, stack.longs(descriptorSet), null + ); + vkCmdDraw(recorder.commandBuffer, 6, 1, 0, 0); + recorder.endDynamicRendering(); + } + + @Override + protected void cleanUp(BoilerInstance boiler) { + super.cleanUp(boiler); + descriptorPool.destroy(); + descriptorSetLayout.destroy(); + simpleImage.destroy(boiler); + vkDestroySampler(boiler.vkDevice(), sampler, null); + vkDestroyPipeline(boiler.vkDevice(), graphicsPipeline, null); + vkDestroyPipelineLayout(boiler.vkDevice(), pipelineLayout, null); + } +} diff --git a/samples/src/main/resources/com/github/knokko/boiler/samples/graphics/ktx.frag b/samples/src/main/resources/com/github/knokko/boiler/samples/graphics/ktx.frag new file mode 100644 index 0000000..e6155f6 --- /dev/null +++ b/samples/src/main/resources/com/github/knokko/boiler/samples/graphics/ktx.frag @@ -0,0 +1,11 @@ +#version 450 + +layout(set = 0, binding = 0) uniform texture2D images[4]; +layout(set = 0, binding = 1) uniform sampler imageSampler; + +layout(location = 0) out vec4 outColor; + +void main() { + //outColor = vec4(0.0, 1.0, 0.5, 1.0); + outColor = texture(sampler2D(images[0], imageSampler), vec2(0.5, 0.5)); +} diff --git a/samples/src/main/resources/com/github/knokko/boiler/samples/graphics/ktx.frag.spv b/samples/src/main/resources/com/github/knokko/boiler/samples/graphics/ktx.frag.spv new file mode 100644 index 0000000..4f29b6d Binary files /dev/null and b/samples/src/main/resources/com/github/knokko/boiler/samples/graphics/ktx.frag.spv differ diff --git a/samples/src/main/resources/com/github/knokko/boiler/samples/graphics/ktx.vert b/samples/src/main/resources/com/github/knokko/boiler/samples/graphics/ktx.vert new file mode 100644 index 0000000..412f1aa --- /dev/null +++ b/samples/src/main/resources/com/github/knokko/boiler/samples/graphics/ktx.vert @@ -0,0 +1,9 @@ +#version 450 + +vec2 positions[] = { + vec2(-0.8, -0.8), vec2(-0.3, -0.8), vec2(-0.3, -0.3), vec2(-0.3, -0.3), vec2(-0.8, -0.3), vec2(-0.8, -0.8) +}; + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); +} diff --git a/samples/src/main/resources/com/github/knokko/boiler/samples/graphics/ktx.vert.spv b/samples/src/main/resources/com/github/knokko/boiler/samples/graphics/ktx.vert.spv new file mode 100644 index 0000000..771d390 Binary files /dev/null and b/samples/src/main/resources/com/github/knokko/boiler/samples/graphics/ktx.vert.spv differ diff --git a/samples/src/main/resources/com/github/knokko/boiler/samples/images/test1.png b/samples/src/main/resources/com/github/knokko/boiler/samples/images/test1.png new file mode 100644 index 0000000..3a3deb1 Binary files /dev/null and b/samples/src/main/resources/com/github/knokko/boiler/samples/images/test1.png differ diff --git a/src/main/java/com/github/knokko/boiler/descriptors/BoilerDescriptors.java b/src/main/java/com/github/knokko/boiler/descriptors/BoilerDescriptors.java index 3314cc1..0cbb73c 100644 --- a/src/main/java/com/github/knokko/boiler/descriptors/BoilerDescriptors.java +++ b/src/main/java/com/github/knokko/boiler/descriptors/BoilerDescriptors.java @@ -132,7 +132,7 @@ public void writeImage( write.dstSet(descriptorSet); write.dstBinding(binding); write.dstArrayElement(0); - write.descriptorCount(1); + write.descriptorCount(image.remaining()); write.descriptorType(type); write.pImageInfo(image); } diff --git a/test1-bc1.ktx2 b/test1-bc1.ktx2 new file mode 100644 index 0000000..877cc55 Binary files /dev/null and b/test1-bc1.ktx2 differ diff --git a/test1-bc3.ktx2 b/test1-bc3.ktx2 new file mode 100644 index 0000000..f027bc6 Binary files /dev/null and b/test1-bc3.ktx2 differ diff --git a/test1-bc7.ktx2 b/test1-bc7.ktx2 new file mode 100644 index 0000000..39f8d48 Binary files /dev/null and b/test1-bc7.ktx2 differ diff --git a/test2-1.png b/test2-1.png new file mode 100644 index 0000000..11268e9 Binary files /dev/null and b/test2-1.png differ diff --git a/test2-7.png b/test2-7.png new file mode 100644 index 0000000..947b914 Binary files /dev/null and b/test2-7.png differ diff --git a/test2-bc1.ktx2 b/test2-bc1.ktx2 new file mode 100644 index 0000000..9efc9bd Binary files /dev/null and b/test2-bc1.ktx2 differ diff --git a/test2-bc3.ktx2 b/test2-bc3.ktx2 new file mode 100644 index 0000000..d1e6ae1 Binary files /dev/null and b/test2-bc3.ktx2 differ diff --git a/test2-bc7.ktx2 b/test2-bc7.ktx2 new file mode 100644 index 0000000..1356e15 Binary files /dev/null and b/test2-bc7.ktx2 differ diff --git a/test2.png b/test2.png new file mode 100644 index 0000000..5310cfb Binary files /dev/null and b/test2.png differ