Files
2026-06-14 19:09:18 +01:00

872 lines
30 KiB
C++

/* -*- 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;
}