Add ktx
This commit is contained in:
@@ -0,0 +1,871 @@
|
||||
/* -*- tab-width: 4; -*- */
|
||||
/* vi: set sw=2 ts=4 expandtab: */
|
||||
|
||||
/*
|
||||
* Copyright 2017-2020 Mark Callow.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @class TextureCubemap
|
||||
* @~English
|
||||
*
|
||||
* @brief Test loading of cubemap textures.
|
||||
*
|
||||
* @author Mark Callow, github.com/MarkCallow.
|
||||
*
|
||||
* @par Acknowledgement
|
||||
* Thanks to Sascha Willems' - www.saschawillems.de - for the concept,
|
||||
* the VulkanTextOverlay VulkanMeshLoader classes and the shaders used
|
||||
* by this test.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <vector>
|
||||
|
||||
#include "TextureCubemap.h"
|
||||
#include "VulkanTextureTranscoder.hpp"
|
||||
#include "SwipeDetector.h"
|
||||
#include "argparser.h"
|
||||
#include "ltexceptions.h"
|
||||
|
||||
#define VERTEX_BUFFER_BIND_ID 0
|
||||
#define ENABLE_VALIDATION false
|
||||
#define USE_GL_RH_NDC 0
|
||||
#define NORMALIZE_AXES 0
|
||||
|
||||
// Vertex layout for this example
|
||||
std::vector<vkMeshLoader::VertexLayout> vertexLayout =
|
||||
{
|
||||
vkMeshLoader::VERTEX_LAYOUT_POSITION,
|
||||
vkMeshLoader::VERTEX_LAYOUT_NORMAL,
|
||||
vkMeshLoader::VERTEX_LAYOUT_UV
|
||||
};
|
||||
|
||||
VulkanLoadTestSample*
|
||||
TextureCubemap::create(VulkanContext& vkctx,
|
||||
uint32_t width, uint32_t height,
|
||||
const char* const szArgs, const std::string sBasePath)
|
||||
{
|
||||
return new TextureCubemap(vkctx, width, height, szArgs, sBasePath,
|
||||
USE_GL_RH_NDC ? 1 : -1);
|
||||
}
|
||||
|
||||
TextureCubemap::TextureCubemap(VulkanContext& vkctx,
|
||||
uint32_t width, uint32_t height,
|
||||
const char* const szArgs, const std::string sBasePath,
|
||||
int32_t yflip)
|
||||
: VulkanLoadTestSample(vkctx, width, height, sBasePath, yflip)
|
||||
{
|
||||
zoom = -4.0f;
|
||||
rotationSpeed = 0.25f;
|
||||
rotation = { -7.25f, 120.0f, 0.0f };
|
||||
transcoded = false;
|
||||
|
||||
ktxVulkanDeviceInfo vdi;
|
||||
ktxVulkanDeviceInfo_Construct(&vdi, vkctx.gpu, vkctx.device,
|
||||
vkctx.queue, vkctx.commandPool, nullptr);
|
||||
|
||||
processArgs(szArgs);
|
||||
|
||||
KTX_error_code ktxresult;
|
||||
ktxTexture* kTexture;
|
||||
std::string ktxfilepath = externalFile ? ktxfilename
|
||||
: getAssetPath() + ktxfilename;
|
||||
ktxresult = ktxTexture_CreateFromNamedFile(ktxfilepath.c_str(),
|
||||
preloadImages ? KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT
|
||||
: KTX_TEXTURE_CREATE_NO_FLAGS,
|
||||
&kTexture);
|
||||
if (KTX_SUCCESS != ktxresult) {
|
||||
std::stringstream message;
|
||||
|
||||
message << "Creation of ktxTexture from \"" << ktxfilepath
|
||||
<< "\" failed: " << ktxErrorString(ktxresult);
|
||||
throw std::runtime_error(message.str());
|
||||
}
|
||||
|
||||
if (ktxTexture_NeedsTranscoding(kTexture)) {
|
||||
TextureTranscoder tc(vkctx);
|
||||
tc.transcode((ktxTexture2*)kTexture);
|
||||
transcoded = true;
|
||||
}
|
||||
|
||||
vk::Format
|
||||
vkFormat = static_cast<vk::Format>(ktxTexture_GetVkFormat(kTexture));
|
||||
transcodedFormat = vkFormat;
|
||||
vk::FormatProperties properties;
|
||||
vkctx.gpu.getFormatProperties(vkFormat, &properties);
|
||||
vk::FormatFeatureFlags wantedFeatures =
|
||||
vk::FormatFeatureFlagBits::eSampledImage
|
||||
| vk::FormatFeatureFlagBits::eSampledImageFilterLinear;
|
||||
if (!(properties.optimalTilingFeatures & wantedFeatures)) {
|
||||
ktxTexture_Destroy(kTexture);
|
||||
throw unsupported_ttype();
|
||||
}
|
||||
|
||||
ktxresult = ktxTexture_VkUpload(kTexture, &vdi, &cubeMap);
|
||||
|
||||
if (KTX_SUCCESS != ktxresult) {
|
||||
std::stringstream message;
|
||||
|
||||
message << "ktxTexture_VkUpload failed: " << ktxErrorString(ktxresult);
|
||||
throw std::runtime_error(message.str());
|
||||
}
|
||||
|
||||
if (kTexture->orientation.y == KTX_ORIENT_Y_DOWN) {
|
||||
// Assume a KTX-compliant cubemap. That means the faces are in a
|
||||
// LH coord system with +y up. Multiply the cube's y and z by -1 to
|
||||
// put the +z face in front of the view and keep +y up. Alternatively
|
||||
// we could multiply the y and x coords by -1 to keep the +y up while
|
||||
// placing the +z face in the +z direction.
|
||||
#if !USE_GL_RH_NDC
|
||||
ubo.uvwTransform = glm::scale(glm::mat4(1.0f), glm::vec3(1, -1, -1));
|
||||
#else
|
||||
// Scale the skybox cube's z by -1 to convert it to LH coords
|
||||
// with the +z face in front of the view.
|
||||
ubo.uvwTransform = glm::scale(glm::mat4(1.0f), glm::vec3(1, 1, -1));
|
||||
#endif
|
||||
} else {
|
||||
std::stringstream message;
|
||||
|
||||
message << "Cubemap faces have unsupported KTXorientation value.";
|
||||
throw std::runtime_error(message.str());
|
||||
}
|
||||
|
||||
ktxTexture_Destroy(kTexture);
|
||||
ktxVulkanDeviceInfo_Destruct(&vdi);
|
||||
|
||||
try {
|
||||
prepare();
|
||||
} catch (std::exception& e) {
|
||||
(void)e; // To quiet unused variable warnings from some compilers.
|
||||
cleanup();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
TextureCubemap::~TextureCubemap()
|
||||
{
|
||||
cleanup();
|
||||
}
|
||||
|
||||
int
|
||||
TextureCubemap::doEvent(SDL_Event* event)
|
||||
{
|
||||
int result = 0;
|
||||
switch(event->type) {
|
||||
case SDL_EVENT_USER:
|
||||
if (event->user.code == SwipeDetector::swipeGesture) {
|
||||
SwipeDetector::Direction direction
|
||||
= SwipeDetector::pointerToDirection(event->user.data1);
|
||||
switch (direction) {
|
||||
case SwipeDetector::Direction::up:
|
||||
toggleObject(+1);
|
||||
break;
|
||||
case SwipeDetector::Direction::down:
|
||||
toggleObject(-1);
|
||||
break;
|
||||
default:
|
||||
result = 1;
|
||||
}
|
||||
} else {
|
||||
result = 1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result = 1;
|
||||
}
|
||||
if (result == 1)
|
||||
result = VulkanLoadTestSample::doEvent(event);
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::resize(uint32_t width, uint32_t height)
|
||||
{
|
||||
this->w_width = width;
|
||||
this->w_height = height;
|
||||
rebuildCommandBuffers();
|
||||
updateUniformBuffers();
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::run(uint32_t /*msTicks*/)
|
||||
{
|
||||
// Nothing to do since the scene is not animated.
|
||||
// VulkanLoadTests base class redraws from the command buffer we built.
|
||||
}
|
||||
|
||||
//===================================================================
|
||||
|
||||
void
|
||||
TextureCubemap::processArgs(std::string sArgs)
|
||||
{
|
||||
// Options descriptor
|
||||
struct argparser::option longopts[] = {
|
||||
{"external", argparser::option::no_argument, &externalFile, 1},
|
||||
{"preload", argparser::option::no_argument, &preloadImages, 1},
|
||||
{NULL, argparser::option::no_argument, NULL, 0}
|
||||
};
|
||||
|
||||
argvector argv(sArgs);
|
||||
argparser ap(argv);
|
||||
|
||||
int ch;
|
||||
while ((ch = ap.getopt(nullptr, longopts, nullptr)) != -1) {
|
||||
switch (ch) {
|
||||
case 0: break;
|
||||
default: assert(false); // Error in args in sample table.
|
||||
}
|
||||
}
|
||||
assert(ap.optind < argv.size());
|
||||
ktxfilename = argv[ap.optind];
|
||||
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
void
|
||||
TextureCubemap::cleanup()
|
||||
{
|
||||
// Clean up used Vulkan resources
|
||||
|
||||
// Clean up texture resources
|
||||
if (sampler)
|
||||
vkctx.device.destroySampler(sampler);
|
||||
if (imageView)
|
||||
vkctx.device.destroyImageView(imageView);
|
||||
ktxVulkanTexture_Destruct(&cubeMap, vkctx.device, nullptr);
|
||||
|
||||
if (pipelines.reflect)
|
||||
vkctx.device.destroyPipeline(pipelines.reflect);
|
||||
if (pipelines.skybox)
|
||||
vkctx.device.destroyPipeline(pipelines.skybox);
|
||||
if (pipelineLayout)
|
||||
vkctx.device.destroyPipelineLayout(pipelineLayout);
|
||||
if (descriptorSetLayout)
|
||||
vkctx.device.destroyDescriptorSetLayout(descriptorSetLayout);
|
||||
|
||||
vkctx.destroyDrawCommandBuffers();
|
||||
|
||||
for (size_t i = 0; i < meshes.objects.size(); i++)
|
||||
{
|
||||
vkMeshLoader::freeMeshBufferResources(vkctx.device, &meshes.objects[i]);
|
||||
}
|
||||
vkMeshLoader::freeMeshBufferResources(vkctx.device, &meshes.skybox);
|
||||
|
||||
uniformData.object.freeResources(vkctx.device);
|
||||
uniformData.skybox.freeResources(vkctx.device);
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::rebuildCommandBuffers()
|
||||
{
|
||||
if (!vkctx.checkDrawCommandBuffers())
|
||||
{
|
||||
vkctx.destroyDrawCommandBuffers();
|
||||
vkctx.createDrawCommandBuffers();
|
||||
}
|
||||
buildCommandBuffers();
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::buildCommandBuffers()
|
||||
{
|
||||
vk::CommandBufferBeginInfo cmdBufInfo({}, nullptr);
|
||||
|
||||
vk::ClearValue clearValues[2];
|
||||
clearValues[0].color = defaultClearColor;
|
||||
clearValues[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
|
||||
vk::RenderPassBeginInfo renderPassBeginInfo(vkctx.renderPass,
|
||||
nullptr,
|
||||
{{0, 0}, {w_width, w_height}},
|
||||
2,
|
||||
clearValues);
|
||||
|
||||
for (uint32_t i = 0; i < vkctx.drawCmdBuffers.size(); ++i)
|
||||
{
|
||||
// Set target frame buffer
|
||||
renderPassBeginInfo.framebuffer = vkctx.framebuffers[i];
|
||||
|
||||
VK_CHECK_RESULT(vkBeginCommandBuffer(vkctx.drawCmdBuffers[i],
|
||||
&static_cast<const VkCommandBufferBeginInfo&>(cmdBufInfo)));
|
||||
|
||||
vkCmdBeginRenderPass(vkctx.drawCmdBuffers[i],
|
||||
&static_cast<const VkRenderPassBeginInfo&>(renderPassBeginInfo),
|
||||
VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
#if !USE_GL_RH_NDC
|
||||
vk::Viewport viewport(0, 0,
|
||||
(float)w_width, (float)w_height,
|
||||
0.0f, 1.0f);
|
||||
#else
|
||||
// Make OpenGL style viewport
|
||||
vk::Viewport viewport(0, (float)w_height,
|
||||
(float)w_width, -(float)w_height,
|
||||
0.0f, 1.0f);
|
||||
#endif
|
||||
vkCmdSetViewport(vkctx.drawCmdBuffers[i], 0, 1,
|
||||
&static_cast<const VkViewport&>(viewport));
|
||||
|
||||
vk::Rect2D scissor({0, 0}, {w_width, w_height});
|
||||
vkCmdSetScissor(vkctx.drawCmdBuffers[i], 0, 1,
|
||||
&static_cast<const VkRect2D&>(scissor));
|
||||
|
||||
VkDeviceSize offsets[1] = { 0 };
|
||||
|
||||
// Skybox
|
||||
if (displaySkybox)
|
||||
{
|
||||
vkCmdBindDescriptorSets(vkctx.drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipelineLayout, 0, 1,
|
||||
&static_cast<const VkDescriptorSet&>(descriptorSets.skybox), 0, NULL);
|
||||
vkCmdBindVertexBuffers(vkctx.drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &meshes.skybox.vertices.buf, offsets);
|
||||
vkCmdBindIndexBuffer(vkctx.drawCmdBuffers[i], meshes.skybox.indices.buf, 0, VK_INDEX_TYPE_UINT32);
|
||||
vkCmdBindPipeline(vkctx.drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skybox);
|
||||
vkCmdDrawIndexed(vkctx.drawCmdBuffers[i], meshes.skybox.indexCount, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
// 3D object
|
||||
vkCmdBindDescriptorSets(vkctx.drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipelineLayout, 0, 1,
|
||||
&static_cast<const VkDescriptorSet&>(descriptorSets.object), 0, NULL);
|
||||
vkCmdBindVertexBuffers(vkctx.drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &meshes.objects[meshes.objectIndex].vertices.buf, offsets);
|
||||
vkCmdBindIndexBuffer(vkctx.drawCmdBuffers[i], meshes.objects[meshes.objectIndex].indices.buf, 0, VK_INDEX_TYPE_UINT32);
|
||||
vkCmdBindPipeline(vkctx.drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.reflect);
|
||||
vkCmdDrawIndexed(vkctx.drawCmdBuffers[i], meshes.objects[meshes.objectIndex].indexCount, 1, 0, 0, 0);
|
||||
|
||||
vkCmdEndRenderPass(vkctx.drawCmdBuffers[i]);
|
||||
|
||||
VK_CHECK_RESULT(vkEndCommandBuffer(vkctx.drawCmdBuffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::loadMeshes()
|
||||
{
|
||||
std::string filepath = getAssetPath();
|
||||
|
||||
// Skybox
|
||||
loadMesh(filepath + "cube.obj", &meshes.skybox, vertexLayout, 0.05f);
|
||||
// Objects
|
||||
meshes.objects.resize(3);
|
||||
loadMesh(filepath + "sphere.obj", &meshes.objects[0], vertexLayout, 0.05f);
|
||||
loadMesh(filepath + "teapot.dae", &meshes.objects[1], vertexLayout, 0.05f);
|
||||
loadMesh(filepath + "torusknot.obj", &meshes.objects[2], vertexLayout, 0.05f);
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::setupVertexDescriptions()
|
||||
{
|
||||
// Binding description
|
||||
vertices.bindingDescriptions.resize(1);
|
||||
vertices.bindingDescriptions[0] =
|
||||
vk::VertexInputBindingDescription(
|
||||
VERTEX_BUFFER_BIND_ID,
|
||||
vkMeshLoader::vertexSize(vertexLayout),
|
||||
vk::VertexInputRate::eVertex);
|
||||
|
||||
// Attribute descriptions
|
||||
// Describes memory layout and shader positions
|
||||
vertices.attributeDescriptions.resize(3);
|
||||
// Location 0 : Position
|
||||
vertices.attributeDescriptions[0] =
|
||||
vk::VertexInputAttributeDescription(
|
||||
0,
|
||||
VERTEX_BUFFER_BIND_ID,
|
||||
vk::Format::eR32G32B32Sfloat,
|
||||
0);
|
||||
// Location 1 : Vertex normal
|
||||
vertices.attributeDescriptions[1] =
|
||||
vk::VertexInputAttributeDescription(
|
||||
1,
|
||||
VERTEX_BUFFER_BIND_ID,
|
||||
vk::Format::eR32G32B32Sfloat,
|
||||
sizeof(float) * 3);
|
||||
// Location 2 : Texture coordinates
|
||||
vertices.attributeDescriptions[2] =
|
||||
vk::VertexInputAttributeDescription(
|
||||
2,
|
||||
VERTEX_BUFFER_BIND_ID,
|
||||
vk::Format::eR32G32Sfloat,
|
||||
sizeof(float) * 6);
|
||||
|
||||
vertices.inputState = vk::PipelineVertexInputStateCreateInfo();
|
||||
vertices.inputState.vertexBindingDescriptionCount =
|
||||
static_cast<uint32_t>(vertices.bindingDescriptions.size());
|
||||
vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data();
|
||||
vertices.inputState.vertexAttributeDescriptionCount =
|
||||
static_cast<uint32_t>(vertices.attributeDescriptions.size());
|
||||
vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data();
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::setupDescriptorPool()
|
||||
{
|
||||
// Example uses one ubo and one image sampler
|
||||
std::vector<vk::DescriptorPoolSize> poolSizes =
|
||||
{
|
||||
{vk::DescriptorType::eUniformBuffer, 2},
|
||||
{vk::DescriptorType::eCombinedImageSampler, 2}
|
||||
};
|
||||
|
||||
vk::DescriptorPoolCreateInfo descriptorPoolInfo(
|
||||
{},
|
||||
2,
|
||||
static_cast<uint32_t>(poolSizes.size()),
|
||||
poolSizes.data());
|
||||
vk::Result res = vkctx.device.createDescriptorPool(&descriptorPoolInfo,
|
||||
nullptr,
|
||||
&descriptorPool);
|
||||
if (res != vk::Result::eSuccess) {
|
||||
throw bad_vulkan_alloc((int)res, "createDescriptorPool");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::setupDescriptorSetLayout()
|
||||
{
|
||||
std::vector<vk::DescriptorSetLayoutBinding> setLayoutBindings =
|
||||
{
|
||||
// Binding 0 : Vertex shader uniform buffer
|
||||
{
|
||||
0,
|
||||
vk::DescriptorType::eUniformBuffer,
|
||||
1,
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
|
||||
},
|
||||
// Binding 1 : Fragment shader image sampler
|
||||
{
|
||||
1,
|
||||
vk::DescriptorType::eCombinedImageSampler,
|
||||
1,
|
||||
vk::ShaderStageFlagBits::eFragment
|
||||
},
|
||||
};
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo descriptorLayout(
|
||||
{},
|
||||
static_cast<uint32_t>(setLayoutBindings.size()),
|
||||
setLayoutBindings.data());
|
||||
|
||||
vk::Result res
|
||||
= vkctx.device.createDescriptorSetLayout(&descriptorLayout,
|
||||
nullptr,
|
||||
&descriptorSetLayout);
|
||||
if (res != vk::Result::eSuccess) {
|
||||
throw bad_vulkan_alloc((int)res, "createDescriptorSetLayout");
|
||||
}
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo(
|
||||
{},
|
||||
1,
|
||||
&descriptorSetLayout);
|
||||
|
||||
res = vkctx.device.createPipelineLayout(&pipelineLayoutCreateInfo,
|
||||
nullptr,
|
||||
&pipelineLayout);
|
||||
if (res != vk::Result::eSuccess) {
|
||||
throw bad_vulkan_alloc((int)res, "createPipelineLayout");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::setupDescriptorSets()
|
||||
{
|
||||
vk::DescriptorSetAllocateInfo allocInfo(
|
||||
descriptorPool,
|
||||
1,
|
||||
&descriptorSetLayout);
|
||||
|
||||
vk::Result res
|
||||
= vkctx.device.allocateDescriptorSets(&allocInfo,
|
||||
&descriptorSets.object);
|
||||
if (res != vk::Result::eSuccess) {
|
||||
throw bad_vulkan_alloc((int)res, "allocateDescriptorSets");
|
||||
}
|
||||
|
||||
// Image descriptor for the cubemap texture
|
||||
vk::DescriptorImageInfo cubeMapDescriptor(
|
||||
sampler,
|
||||
imageView,
|
||||
vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||
|
||||
std::vector<vk::WriteDescriptorSet> writeDescriptorSets =
|
||||
{
|
||||
// Binding 0 : Vertex shader uniform buffer
|
||||
vk::WriteDescriptorSet(
|
||||
descriptorSets.object,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
vk::DescriptorType::eUniformBuffer,
|
||||
nullptr,
|
||||
&uniformData.object.descriptor),
|
||||
// Binding 1 : Fragment shader cubemap sampler
|
||||
vk::WriteDescriptorSet(
|
||||
descriptorSets.object,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
vk::DescriptorType::eCombinedImageSampler,
|
||||
&cubeMapDescriptor)
|
||||
};
|
||||
|
||||
vkctx.device.updateDescriptorSets(
|
||||
static_cast<uint32_t>(writeDescriptorSets.size()),
|
||||
writeDescriptorSets.data(),
|
||||
0,
|
||||
nullptr);
|
||||
|
||||
// Sky box descriptor set
|
||||
res = vkctx.device.allocateDescriptorSets(&allocInfo,
|
||||
&descriptorSets.skybox);
|
||||
if (res != vk::Result::eSuccess) {
|
||||
throw bad_vulkan_alloc((int)res, "allocateDescriptorSets");
|
||||
}
|
||||
|
||||
writeDescriptorSets =
|
||||
{
|
||||
// Binding 0 : Vertex shader uniform buffer
|
||||
vk::WriteDescriptorSet(
|
||||
descriptorSets.skybox,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
vk::DescriptorType::eUniformBuffer,
|
||||
nullptr,
|
||||
&uniformData.skybox.descriptor),
|
||||
// Binding 1 : Fragment shader texture sampler
|
||||
vk::WriteDescriptorSet(
|
||||
descriptorSets.skybox,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
vk::DescriptorType::eCombinedImageSampler,
|
||||
&cubeMapDescriptor)
|
||||
};
|
||||
|
||||
vkctx.device.updateDescriptorSets(
|
||||
static_cast<uint32_t>(writeDescriptorSets.size()),
|
||||
writeDescriptorSets.data(),
|
||||
0,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::preparePipelines()
|
||||
{
|
||||
vk::PipelineInputAssemblyStateCreateInfo inputAssemblyState(
|
||||
{},
|
||||
vk::PrimitiveTopology::eTriangleList);
|
||||
|
||||
vk::PipelineRasterizationStateCreateInfo rasterizationState;
|
||||
// Must be false because we haven't enabled the depthClamp device feature.
|
||||
rasterizationState.depthClampEnable = false;
|
||||
rasterizationState.rasterizerDiscardEnable = VK_FALSE;
|
||||
rasterizationState.polygonMode = vk::PolygonMode::eFill;
|
||||
rasterizationState.cullMode = vk::CullModeFlagBits::eBack;
|
||||
// Make the faces on the inside of the cube the front faces. The mesh
|
||||
// was designed with the exterior faces as the front faces for OpenGL's
|
||||
// default of GL_CCW.
|
||||
#if !USE_GL_RH_NDC
|
||||
rasterizationState.frontFace = vk::FrontFace::eCounterClockwise;
|
||||
#else
|
||||
rasterizationState.frontFace = vk::FrontFace::eClockwise;
|
||||
#endif
|
||||
rasterizationState.lineWidth = 1.0f;
|
||||
|
||||
vk::PipelineColorBlendAttachmentState blendAttachmentState;
|
||||
blendAttachmentState.blendEnable = VK_FALSE;
|
||||
//blendAttachmentState.colorWriteMask = 0xf;
|
||||
blendAttachmentState.colorWriteMask = vk::ColorComponentFlagBits::eR
|
||||
| vk::ColorComponentFlagBits::eG
|
||||
| vk::ColorComponentFlagBits::eB
|
||||
| vk::ColorComponentFlagBits::eA;
|
||||
|
||||
vk::PipelineColorBlendStateCreateInfo colorBlendState;
|
||||
colorBlendState.attachmentCount = 1;
|
||||
colorBlendState.pAttachments = &blendAttachmentState;
|
||||
|
||||
vk::PipelineDepthStencilStateCreateInfo depthStencilState;
|
||||
depthStencilState.depthTestEnable = VK_FALSE;
|
||||
depthStencilState.depthWriteEnable = VK_FALSE;
|
||||
depthStencilState.depthCompareOp = vk::CompareOp::eLessOrEqual;
|
||||
|
||||
vk::PipelineViewportStateCreateInfo viewportState;
|
||||
viewportState.viewportCount = 1;
|
||||
viewportState.scissorCount = 1;
|
||||
|
||||
vk::PipelineMultisampleStateCreateInfo multisampleState;
|
||||
multisampleState.rasterizationSamples = vk::SampleCountFlagBits::e1;
|
||||
|
||||
std::vector<vk::DynamicState> dynamicStateEnables = {
|
||||
vk::DynamicState::eViewport,
|
||||
vk::DynamicState::eScissor
|
||||
};
|
||||
vk::PipelineDynamicStateCreateInfo dynamicState(
|
||||
{},
|
||||
static_cast<uint32_t>(dynamicStateEnables.size()),
|
||||
dynamicStateEnables.data());
|
||||
|
||||
// Load shaders
|
||||
std::array<vk::PipelineShaderStageCreateInfo,2> shaderStages;
|
||||
std::string filepath = getAssetPath();
|
||||
shaderStages[0] = loadShader(filepath + "skybox.vert.spv",
|
||||
vk::ShaderStageFlagBits::eVertex);
|
||||
shaderStages[1] = loadShader(filepath + "skybox.frag.spv",
|
||||
vk::ShaderStageFlagBits::eFragment);
|
||||
|
||||
vk::GraphicsPipelineCreateInfo pipelineCreateInfo;
|
||||
pipelineCreateInfo.layout = pipelineLayout;
|
||||
pipelineCreateInfo.renderPass = vkctx.renderPass;
|
||||
pipelineCreateInfo.pVertexInputState = &vertices.inputState;
|
||||
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
|
||||
pipelineCreateInfo.pRasterizationState = &rasterizationState;
|
||||
pipelineCreateInfo.pColorBlendState = &colorBlendState;
|
||||
pipelineCreateInfo.pMultisampleState = &multisampleState;
|
||||
pipelineCreateInfo.pViewportState = &viewportState;
|
||||
pipelineCreateInfo.pDepthStencilState = &depthStencilState;
|
||||
pipelineCreateInfo.pDynamicState = &dynamicState;
|
||||
pipelineCreateInfo.stageCount = (uint32_t)shaderStages.size();
|
||||
pipelineCreateInfo.pStages = shaderStages.data();
|
||||
|
||||
vk::Result res
|
||||
= vkctx.device.createGraphicsPipelines(vkctx.pipelineCache, 1,
|
||||
&pipelineCreateInfo, nullptr,
|
||||
&pipelines.skybox);
|
||||
if (res != vk::Result::eSuccess) {
|
||||
throw bad_vulkan_alloc((int)res, "createGraphicsPipelines");
|
||||
}
|
||||
|
||||
// Cube map reflect pipeline
|
||||
shaderStages[0] = loadShader(filepath + "reflect.vert.spv",
|
||||
vk::ShaderStageFlagBits::eVertex);
|
||||
shaderStages[1] = loadShader(filepath + "reflect.frag.spv",
|
||||
vk::ShaderStageFlagBits::eFragment);
|
||||
|
||||
// Enable depth test and write
|
||||
depthStencilState.depthWriteEnable = VK_TRUE;
|
||||
depthStencilState.depthTestEnable = VK_TRUE;
|
||||
// Flip cull mode
|
||||
rasterizationState.cullMode = vk::CullModeFlagBits::eFront;
|
||||
|
||||
res = vkctx.device.createGraphicsPipelines(vkctx.pipelineCache, 1,
|
||||
&pipelineCreateInfo, nullptr,
|
||||
&pipelines.reflect);
|
||||
if (res != vk::Result::eSuccess) {
|
||||
throw bad_vulkan_alloc((int)res, "createGraphicsPipelines");
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms
|
||||
void
|
||||
TextureCubemap::prepareUniformBuffers()
|
||||
{
|
||||
// 3D objact
|
||||
vkctx.createBuffer(
|
||||
vk::BufferUsageFlagBits::eUniformBuffer,
|
||||
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent,
|
||||
sizeof(ubo),
|
||||
nullptr,
|
||||
&uniformData.object.buffer,
|
||||
&uniformData.object.memory,
|
||||
&uniformData.object.descriptor);
|
||||
|
||||
// Skybox
|
||||
vkctx.createBuffer(
|
||||
vk::BufferUsageFlagBits::eUniformBuffer,
|
||||
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent,
|
||||
sizeof(ubo),
|
||||
nullptr,
|
||||
&uniformData.skybox.buffer,
|
||||
&uniformData.skybox.memory,
|
||||
&uniformData.skybox.descriptor);
|
||||
|
||||
updateUniformBuffers();
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::updateUniformBuffers()
|
||||
{
|
||||
// 3D object
|
||||
glm::mat4 viewMatrix = glm::mat4();
|
||||
ubo.projection = glm::perspective(glm::radians(60.0f),
|
||||
(float)w_width / (float)w_height,
|
||||
0.001f, 256.0f);
|
||||
viewMatrix = glm::translate(viewMatrix, glm::vec3(0.0f, 0.0f, zoom));
|
||||
|
||||
// Transform the z axis to what the viewer is perceiving as the z axis to make
|
||||
// the behaviour of 2-finger rotation (the value in rotation.z) understandable.
|
||||
glm::mat4 zAxisRotMatrix;
|
||||
zAxisRotMatrix = glm::rotate(zAxisRotMatrix, glm::radians(-rotation.x),
|
||||
glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
zAxisRotMatrix = glm::rotate(zAxisRotMatrix, glm::radians(-rotation.y),
|
||||
glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
zRotationAxis = normalize(zAxisRotMatrix * glm::vec4(0.0f, 0.0f, 1.0f, 1.0f));
|
||||
|
||||
ubo.modelView = glm::mat4();
|
||||
ubo.modelView = viewMatrix * glm::translate(ubo.modelView, cameraPos);
|
||||
ubo.modelView = glm::rotate(ubo.modelView, glm::radians(rotation.x),
|
||||
glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
ubo.modelView = glm::rotate(ubo.modelView, glm::radians(rotation.y),
|
||||
glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
ubo.modelView = glm::rotate(ubo.modelView, glm::radians(rotation.z),
|
||||
zRotationAxis);
|
||||
|
||||
// Do the inverse here because doing it in every fragment is a bit much.
|
||||
// Also MetalSL does not have inverse() and does not support passing
|
||||
// transforms between stages.
|
||||
ubo.invModelView = glm::inverse(ubo.modelView);
|
||||
|
||||
uint8_t *pData;
|
||||
VK_CHECK_RESULT(vkMapMemory(vkctx.device, uniformData.object.memory, 0, sizeof(ubo), 0, (void **)&pData));
|
||||
memcpy(pData, &ubo, sizeof(ubo));
|
||||
vkUnmapMemory(vkctx.device, uniformData.object.memory);
|
||||
|
||||
// Skybox
|
||||
// Remove translation from modelView so the skybox doesn't move.
|
||||
ubo.modelView = glm::mat4(glm::mat3(ubo.modelView));
|
||||
// Inverse not needed by skybox.
|
||||
|
||||
VK_CHECK_RESULT(vkMapMemory(vkctx.device, uniformData.skybox.memory, 0, sizeof(ubo), 0, (void **)&pData));
|
||||
memcpy(pData, &ubo, sizeof(ubo));
|
||||
vkUnmapMemory(vkctx.device, uniformData.skybox.memory);
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::prepareSamplerAndView()
|
||||
{
|
||||
// Create sampler.
|
||||
vk::SamplerCreateInfo samplerInfo;
|
||||
// Set the non-default values
|
||||
samplerInfo.magFilter = vk::Filter::eLinear;
|
||||
samplerInfo.minFilter = vk::Filter::eLinear;
|
||||
samplerInfo.mipmapMode = vk::SamplerMipmapMode::eLinear;
|
||||
samplerInfo.maxLod = (float)cubeMap.levelCount;
|
||||
if (vkctx.gpuFeatures.samplerAnisotropy == VK_TRUE) {
|
||||
samplerInfo.anisotropyEnable = VK_TRUE;
|
||||
samplerInfo.maxAnisotropy = 8;
|
||||
} else {
|
||||
// vulkan.hpp needs fixing
|
||||
samplerInfo.maxAnisotropy = 1.0;
|
||||
}
|
||||
samplerInfo.borderColor = vk::BorderColor::eFloatOpaqueWhite;
|
||||
sampler = vkctx.device.createSampler(samplerInfo);
|
||||
|
||||
// Create image view.
|
||||
// Textures are not directly accessed by the shaders and are abstracted
|
||||
// by image views containing additional information and sub resource
|
||||
// ranges.
|
||||
vk::ImageViewCreateInfo viewInfo;
|
||||
// Set the non-default values.
|
||||
viewInfo.image = cubeMap.image;
|
||||
viewInfo.format = static_cast<vk::Format>(cubeMap.imageFormat);
|
||||
viewInfo.viewType = static_cast<vk::ImageViewType>(cubeMap.viewType);
|
||||
viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
viewInfo.subresourceRange.layerCount = cubeMap.layerCount;
|
||||
viewInfo.subresourceRange.levelCount = cubeMap.levelCount;
|
||||
imageView = vkctx.device.createImageView(viewInfo);
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::prepare()
|
||||
{
|
||||
prepareSamplerAndView();
|
||||
loadMeshes();
|
||||
setupVertexDescriptions();
|
||||
prepareUniformBuffers();
|
||||
setupDescriptorSetLayout();
|
||||
preparePipelines();
|
||||
setupDescriptorPool();
|
||||
setupDescriptorSets();
|
||||
vkctx.createDrawCommandBuffers();
|
||||
buildCommandBuffers();
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::toggleSkyBox()
|
||||
{
|
||||
displaySkybox = !displaySkybox;
|
||||
rebuildCommandBuffers();
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::toggleObject(int direction)
|
||||
{
|
||||
meshes.objectIndex += direction;
|
||||
if (meshes.objectIndex >= static_cast<int32_t>(meshes.objects.size())) {
|
||||
meshes.objectIndex = 0;
|
||||
} else if (meshes.objectIndex < 0) {
|
||||
meshes.objectIndex = static_cast<int32_t>(meshes.objects.size()) - 1;
|
||||
}
|
||||
rebuildCommandBuffers();
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::changeLodBias(float delta)
|
||||
{
|
||||
ubo.lodBias += delta;
|
||||
if (ubo.lodBias < 0.0f)
|
||||
{
|
||||
ubo.lodBias = 0.0f;
|
||||
}
|
||||
if (ubo.lodBias > cubeMap.levelCount)
|
||||
{
|
||||
ubo.lodBias = (float)cubeMap.levelCount;
|
||||
}
|
||||
updateUniformBuffers();
|
||||
//updateTextOverlay();
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::keyPressed(uint32_t keyCode)
|
||||
{
|
||||
switch (keyCode)
|
||||
{
|
||||
case 's':
|
||||
toggleSkyBox();
|
||||
break;
|
||||
case ' ':
|
||||
toggleObject(+1);
|
||||
break;
|
||||
case SDLK_KP_PLUS:
|
||||
changeLodBias(0.1f);
|
||||
break;
|
||||
case SDLK_KP_MINUS:
|
||||
changeLodBias(-0.1f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TextureCubemap::getOverlayText(VulkanTextOverlay *textOverlay, float yOffset)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << std::setprecision(2) << std::fixed << ubo.lodBias;
|
||||
textOverlay->addText("Press \"s\" to toggle skybox", 5.0f,
|
||||
yOffset, VulkanTextOverlay::alignLeft);
|
||||
textOverlay->addText("Press \"space\" or 2-finger swipe up or down to change object", 5.0f,
|
||||
yOffset+20.0f, VulkanTextOverlay::alignLeft);
|
||||
textOverlay->addText("LOD bias: " + ss.str() + " (numpad +/- to change)",
|
||||
5.0f, yOffset+40.0f, VulkanTextOverlay::alignLeft);
|
||||
}
|
||||
|
||||
const char*
|
||||
TextureCubemap::customizeTitle(const char* const baseTitle)
|
||||
{
|
||||
if (transcoded) {
|
||||
this->title = baseTitle;
|
||||
this->title += " Transcoded to ";
|
||||
this->title += vkFormatString((VkFormat)transcodedFormat);
|
||||
return this->title.c_str();
|
||||
}
|
||||
return baseTitle;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user