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

819 lines
29 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 Texture
* @~English
*
* @brief Test loading of 2D textures.
*
* @author Mark Callow, github.com/MarkCallow.
*
* @par Acknowledgement
* Thanks to Sascha Willems' - www.saschawillems.de - for the concept,
* the VulkanTextOverlay class and the shaders used by this test.
*/
#if defined(_WIN32)
#define _CRT_SECURE_NO_WARNINGS // For sscanf
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <exception>
#include <vector>
#include "argparser.h"
#include "Texture.h"
#include "VulkanTextureTranscoder.hpp"
#include "ltexceptions.h"
#define VERTEX_BUFFER_BIND_ID 0
#define ENABLE_VALIDATION false
// Vertex layout for this example
struct Vertex {
std::array<float, 3> pos;
std::array<float, 2> uv;
std::array<float, 3> normal;
std::array<float, 3> color;
};
VulkanLoadTestSample*
Texture::create(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
{
return new Texture(vkctx, width, height, szArgs, sBasePath);
}
Texture::Texture(VulkanContext& vkctx,
uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: VulkanLoadTestSample(vkctx, width, height, sBasePath)
{
zoom = -2.5f;
rotation = { 0.0f, 15.0f, 0.0f };
tiling = vk::ImageTiling::eOptimal;
useSubAlloc = UseSuballocator::No;
rgbcolor upperLeftColor{ 0.7f, 0.1f, 0.2f };
rgbcolor lowerLeftColor{ 0.8f, 0.9f, 0.3f };
rgbcolor upperRightColor{ 0.4f, 1.0f, 0.5f };
rgbcolor lowerRightColor{ 0.0f, 0.6f, 0.1f };
transcoded = false;
quadColor = { upperLeftColor, lowerLeftColor,
upperRightColor, lowerRightColor };
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(),
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& features = tiling == vk::ImageTiling::eLinear ?
properties.linearTilingFeatures :
properties.optimalTilingFeatures;
vk::FormatFeatureFlags wantedFeatures =
vk::FormatFeatureFlagBits::eSampledImage
| vk::FormatFeatureFlagBits::eSampledImageFilterLinear;
if (!(features & wantedFeatures)) {
ktxTexture_Destroy(kTexture);
throw unsupported_ttype();
}
if (useSubAlloc == UseSuballocator::Yes)
{
VkInstance vkInst = vkctx.instance;
VMA_CALLBACKS::InitVMA(vdi.physicalDevice, vdi.device, vkInst, vdi.deviceMemoryProperties);
ktxresult = ktxTexture_VkUploadEx_WithSuballocator(kTexture, &vdi, &texture,
static_cast<VkImageTiling>(tiling),
VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, &subAllocatorCallbacks);
}
else // Keep separate call so ktxTexture_VkUploadEx is also tested.
ktxresult = ktxTexture_VkUploadEx(kTexture, &vdi, &texture,
static_cast<VkImageTiling>(tiling),
VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "ktxTexture_VkUpload failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
if (kTexture->orientation.x == KTX_ORIENT_X_LEFT)
sign_s = -1;
if (kTexture->orientation.y == KTX_ORIENT_Y_UP)
sign_t = -1;
ktx_uint32_t swizzleLen;
char* swizzleStr;
ktxresult = ktxHashList_FindValue(&kTexture->kvDataHead, KTX_SWIZZLE_KEY,
&swizzleLen, (void**)&swizzleStr);
if (ktxresult == KTX_SUCCESS && swizzleLen == 5) {
if (gpuSupportsSwizzle()) {
swizzle.r = swizzleStr[0] == 'r' ? vk::ComponentSwizzle::eR
: swizzleStr[0] == 'g' ? vk::ComponentSwizzle::eG
: swizzleStr[0] == 'b' ? vk::ComponentSwizzle::eB
: swizzleStr[0] == 'a' ? vk::ComponentSwizzle::eA
: swizzleStr[0] == '0' ? vk::ComponentSwizzle::eZero
: vk::ComponentSwizzle::eOne;
swizzle.g = swizzleStr[1] == 'r' ? vk::ComponentSwizzle::eR
: swizzleStr[1] == 'g' ? vk::ComponentSwizzle::eG
: swizzleStr[1] == 'b' ? vk::ComponentSwizzle::eB
: swizzleStr[1] == 'a' ? vk::ComponentSwizzle::eA
: swizzleStr[1] == '0' ? vk::ComponentSwizzle::eZero
: vk::ComponentSwizzle::eOne;
swizzle.b = swizzleStr[2] == 'r' ? vk::ComponentSwizzle::eR
: swizzleStr[2] == 'g' ? vk::ComponentSwizzle::eG
: swizzleStr[2] == 'b' ? vk::ComponentSwizzle::eB
: swizzleStr[2] == 'a' ? vk::ComponentSwizzle::eA
: swizzleStr[2] == '0' ? vk::ComponentSwizzle::eZero
: vk::ComponentSwizzle::eOne;
swizzle.a = swizzleStr[3] == 'r' ? vk::ComponentSwizzle::eR
: swizzleStr[3] == 'g' ? vk::ComponentSwizzle::eG
: swizzleStr[3] == 'b' ? vk::ComponentSwizzle::eB
: swizzleStr[3] == 'a' ? vk::ComponentSwizzle::eA
: swizzleStr[3] == '0' ? vk::ComponentSwizzle::eZero
: vk::ComponentSwizzle::eOne;
} else {
std::stringstream message;
message << "Input file has swizzle metadata but "
<< "app is running on a VK_KHR_portability_subset device "
<< "that does not support swizzling.";
// I have absolutely no idea why the following line makes clang
// raise an error about no matching conversion from
// std::__1::basic_stringstream to unsupported_ttype
// so I've resorted to using a temporary variable.
//throw(unsupported_ttype(message.str());
std::string msg = message.str();
throw(unsupported_ttype(msg));
}
}
ktxTexture_Destroy(kTexture);
ktxVulkanDeviceInfo_Destruct(&vdi);
try {
prepare();
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
cleanup();
throw;
}
}
Texture::~Texture()
{
cleanup();
}
void
Texture::resize(uint32_t width, uint32_t height)
{
this->w_width = width;
this->w_height = height;
vkctx.destroyDrawCommandBuffers();
vkctx.createDrawCommandBuffers();
buildCommandBuffers();
updateUniformBuffers();
}
void
Texture::run(uint32_t /*msTicks*/)
{
// Nothing to do since the scene is not animated.
// VulkanLoadTests base class redraws from the command buffer we built.
}
//===================================================================
void
Texture::processArgs(std::string sArgs)
{
// Options descriptor
struct argparser::option longopts[] = {
{"external", argparser::option::no_argument, &externalFile, 1},
{"linear-tiling", argparser::option::no_argument, (int*)&tiling, (int)vk::ImageTiling::eLinear},
{"use-vma", argparser::option::no_argument, (int*)&useSubAlloc, (int)UseSuballocator::Yes},
{"qcolor", argparser::option::required_argument, NULL, 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;
case 1:
{
std::istringstream in(ap.optarg);
rgbcolor clr;
int i;
for (i = 0; i < 4 && !in.eof(); i++) {
in >> clr[0] >> skip(",") >> clr[1] >> skip(",") >> clr[2];
quadColor[i] = clr;
if (!in.eof())
in >> skip(",");
}
assert(!in.fail() && (i == 1 || i == 4));
if (i == 1) {
for(; i < 4; i++)
quadColor[i] = quadColor[0];
}
break;
}
default: assert(false); // Error in args in sample table.
}
}
assert(ap.optind < argv.size());
ktxfilename = argv[ap.optind];
}
/* ------------------------------------------------------------------------- */
// It is difficult to have these members clean up up their own mess, hence
// this. Some of them are vulkan.hpp objects that have no destructors and
// no record of the device. We could add destructors for our own but each
// would have to remember the device.
void
Texture::cleanup()
{
// Clean up used Vulkan resources
vkctx.destroyDrawCommandBuffers();
if (sampler)
vkctx.device.destroySampler(sampler);
if (imageView)
vkctx.device.destroyImageView(imageView);
if (useSubAlloc == UseSuballocator::Yes)
{
VkDevice vkDev = vkctx.device;
(void)ktxVulkanTexture_Destruct_WithSuballocator(&texture, vkDev, VK_NULL_HANDLE, &subAllocatorCallbacks);
VMA_CALLBACKS::DestroyVMA();
}
else // Keep separate call so ktxVulkanTexture_Destruct is also tested.
ktxVulkanTexture_Destruct(&texture, vkctx.device, nullptr);
if (pipelines.solid)
vkctx.device.destroyPipeline(pipelines.solid);
if (pipelineLayout)
vkctx.device.destroyPipelineLayout(pipelineLayout);
if (descriptorSetLayout)
vkctx.device.destroyDescriptorSetLayout(descriptorSetLayout);
quad.freeResources(vkctx.device);
uniformDataVS.freeResources(vkctx.device);
}
void
Texture::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);
vk::Viewport viewport(0, 0,
(float)w_width, (float)w_height,
0.0f, 1.0f);
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));
vkCmdBindDescriptorSets(vkctx.drawCmdBuffers[i],
VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1,
&static_cast<const VkDescriptorSet&>(descriptorSet), 0, NULL);
vkCmdBindPipeline(vkctx.drawCmdBuffers[i],
VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.solid);
VkDeviceSize offsets[1] = { 0 };
vkCmdBindVertexBuffers(vkctx.drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID,
1, &static_cast<const VkBuffer&>(quad.vertices.buf), offsets);
vkCmdBindIndexBuffer(vkctx.drawCmdBuffers[i], quad.indices.buf, 0,
VK_INDEX_TYPE_UINT32);
vkCmdDrawIndexed(vkctx.drawCmdBuffers[i], quad.indexCount, 1, 0, 0, 0);
vkCmdEndRenderPass(vkctx.drawCmdBuffers[i]);
VK_CHECK_RESULT(vkEndCommandBuffer(vkctx.drawCmdBuffers[i]));
}
}
void
Texture::generateQuad()
{
// Setup vertices for a single uv-mapped quad
#define DIM 1.0f
#define NORMAL { 0.0f, 0.0f, 1.0f }
std::vector<Vertex> vertexBuffer = {
{ { -DIM, -DIM, 0.0f }, { 0.0f, 0.0f }, NORMAL, { quadColor[0] } },
{ { -DIM, DIM, 0.0f }, { 0.0f, 1.0f }, NORMAL, { quadColor[1] } },
{ { DIM, -DIM, 0.0f }, { 1.0f, 0.0f }, NORMAL, { quadColor[2] } },
{ { DIM, DIM, 0.0f }, { 1.0f, 1.0f }, NORMAL, { quadColor[3] } }
};
#undef DIM
#undef NORMAL
if (sign_s < 0 || sign_t < 0) {
// Transform the texture coordinates to get correct image orientation.
for (uint32_t i = 0; i < vertexBuffer.size(); i++) {
if (sign_t < 1) {
vertexBuffer[i].uv[1] = vertexBuffer[i].uv[1] * -1 + 1;
}
if (sign_s < 1) {
vertexBuffer[i].uv[0] = vertexBuffer[i].uv[0] * -1 + 1;
}
}
}
vkctx.createBuffer(
vk::BufferUsageFlagBits::eVertexBuffer,
vertexBuffer.size() * sizeof(Vertex),
vertexBuffer.data(),
&quad.vertices.buf,
&quad.vertices.mem);
// Setup indices
std::vector<uint32_t> indexBuffer = { 0,1,2,3 };
quad.indexCount = static_cast<uint32_t>(indexBuffer.size());
vkctx.createBuffer(
vk::BufferUsageFlagBits::eIndexBuffer,
indexBuffer.size() * sizeof(uint32_t),
indexBuffer.data(),
&quad.indices.buf,
&quad.indices.mem);
}
void
Texture::setupVertexDescriptions()
{
// Binding description
vertices.bindingDescriptions.resize(1);
vertices.bindingDescriptions[0] =
vk::VertexInputBindingDescription(
VERTEX_BUFFER_BIND_ID,
sizeof(Vertex),
vk::VertexInputRate::eVertex);
//#define OFFSET(f) (&(((struct Vertex*)0)->f) - &(struct Vertex*)0)
// Attribute descriptions
// Describes memory layout and shader positions
vertices.attributeDescriptions.resize(4);
// Location 0 : Position
vertices.attributeDescriptions[0] =
vk::VertexInputAttributeDescription(
0,
VERTEX_BUFFER_BIND_ID,
vk::Format::eR32G32B32Sfloat,
0);
// Location 1 : Texture coordinates
vertices.attributeDescriptions[1] =
vk::VertexInputAttributeDescription(
1,
VERTEX_BUFFER_BIND_ID,
vk::Format::eR32G32Sfloat,
sizeof(float) * 3);
// Location 2 : Vertex normal
vertices.attributeDescriptions[2] =
vk::VertexInputAttributeDescription(
2,
VERTEX_BUFFER_BIND_ID,
vk::Format::eR32G32B32Sfloat,
sizeof(float) * 5);
// Location 3 : Color
vertices.attributeDescriptions[3] =
vk::VertexInputAttributeDescription(
3,
VERTEX_BUFFER_BIND_ID,
vk::Format::eR32G32B32Sfloat,
sizeof(float) * 8);
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
Texture::setupDescriptorPool()
{
// Example uses one ubo and one image sampler
std::vector<vk::DescriptorPoolSize> poolSizes =
{
{vk::DescriptorType::eUniformBuffer, 1},
{vk::DescriptorType::eCombinedImageSampler, 1}
};
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
Texture::setupDescriptorSetLayout()
{
std::vector<vk::DescriptorSetLayoutBinding> setLayoutBindings =
{
// Binding 0 : Vertex shader uniform buffer
{0,
vk::DescriptorType::eUniformBuffer,
1,
vk::ShaderStageFlagBits::eVertex},
// 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
Texture::setupDescriptorSet()
{
vk::DescriptorSetAllocateInfo allocInfo(
descriptorPool,
1,
&descriptorSetLayout);
vk::Result res
= vkctx.device.allocateDescriptorSets(&allocInfo, &descriptorSet);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "allocateDescriptorSets");
}
// Image descriptor for the color map texture
vk::DescriptorImageInfo texDescriptor(
sampler,
imageView,
vk::ImageLayout::eShaderReadOnlyOptimal);
std::vector<vk::WriteDescriptorSet> writeDescriptorSets;
// Binding 0 : Vertex shader uniform buffer
writeDescriptorSets.push_back(vk::WriteDescriptorSet(
descriptorSet,
0,
0,
1,
vk::DescriptorType::eUniformBuffer,
nullptr,
&uniformDataVS.descriptor)
);
// Binding 1 : Fragment shader texture sampler
writeDescriptorSets.push_back(vk::WriteDescriptorSet(
descriptorSet,
1,
0,
1,
vk::DescriptorType::eCombinedImageSampler,
&texDescriptor)
);
vkctx.device.updateDescriptorSets(
static_cast<uint32_t>(writeDescriptorSets.size()),
writeDescriptorSets.data(),
0,
nullptr);
}
void
Texture::preparePipelines()
{
vk::PipelineInputAssemblyStateCreateInfo inputAssemblyState(
{},
vk::PrimitiveTopology::eTriangleStrip,
// primmitiveRestartEnable not needed but disabling it results in a MoltenVK
// feature not present warning.
true);
vk::PipelineRasterizationStateCreateInfo rasterizationState;
// Must be false because we haven't enabled the depthClamp device feature.
rasterizationState.depthClampEnable = false;
rasterizationState.rasterizerDiscardEnable = false;
rasterizationState.polygonMode = vk::PolygonMode::eFill;
rasterizationState.cullMode = vk::CullModeFlagBits::eNone;
rasterizationState.frontFace = vk::FrontFace::eCounterClockwise;
rasterizationState.lineWidth = 1.0f;
vk::PipelineColorBlendAttachmentState blendAttachmentState;
blendAttachmentState.blendEnable = 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 = true;
depthStencilState.depthWriteEnable = true;
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 + "texture.vert.spv",
vk::ShaderStageFlagBits::eVertex);
std::string fragShader = filepath;
if (texture.viewType == VK_IMAGE_VIEW_TYPE_1D)
fragShader += "texture1d";
else
fragShader += "texture2d";
fragShader += ".frag.spv";
shaderStages[1] = loadShader(fragShader,
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.solid);
if (res != vk::Result::eSuccess) {
throw bad_vulkan_alloc((int)res, "createGraphicsPipelines");
}
}
// Prepare and initialize uniform buffer containing shader uniforms
void
Texture::prepareUniformBuffers()
{
// Vertex shader uniform buffer block
vkctx.createBuffer(
vk::BufferUsageFlagBits::eUniformBuffer,
sizeof(uboVS),
&uboVS,
&uniformDataVS.buffer,
&uniformDataVS.memory,
&uniformDataVS.descriptor);
updateUniformBuffers();
}
void
Texture::updateUniformBuffers()
{
if (w_width == 0 || w_height == 0)
return;
// Vertex shader
uboVS.projection = glm::perspective(glm::radians(60.0f), (float)w_width / (float)w_height, 0.001f, 256.0f);
glm::mat4 viewMatrix = glm::translate(glm::mat4(), glm::vec3(0.0f, 0.0f, zoom));
uboVS.model = viewMatrix * glm::translate(glm::mat4(), cameraPos);
uboVS.model = glm::rotate(uboVS.model, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
uboVS.model = glm::rotate(uboVS.model, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
uboVS.model = glm::rotate(uboVS.model, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
// Because MetalSL does not have a matrix inverse function...
// It looks like the glm::mat3(glm::mat4) does something different than
// GLSL. If I convert to mat3 here, only half the quad is lit. Do it in
// the shader.
uboVS.normal = inverse(transpose(uboVS.model));
uboVS.viewPos = glm::vec4(0.0f, 0.0f, -zoom, 0.0f);
uint8_t *pData;
VK_CHECK_RESULT(vkMapMemory(vkctx.device, uniformDataVS.memory, 0, sizeof(uboVS), 0, (void **)&pData));
memcpy(pData, &uboVS, sizeof(uboVS));
vkUnmapMemory(vkctx.device, uniformDataVS.memory);
}
void
Texture::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)texture.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.components = swizzle;
viewInfo.image = texture.image;
viewInfo.format = static_cast<vk::Format>(texture.imageFormat);
viewInfo.viewType = static_cast<vk::ImageViewType>(texture.viewType);
viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
viewInfo.subresourceRange.layerCount = texture.layerCount;
viewInfo.subresourceRange.levelCount = texture.levelCount;
imageView = vkctx.device.createImageView(viewInfo);
}
void
Texture::prepare()
{
prepareSamplerAndView();
generateQuad();
setupVertexDescriptions();
prepareUniformBuffers();
setupDescriptorSetLayout();
preparePipelines();
setupDescriptorPool();
setupDescriptorSet();
vkctx.createDrawCommandBuffers();
buildCommandBuffers();
}
void
Texture::changeLodBias(float delta)
{
uboVS.lodBias += delta;
if (uboVS.lodBias < 0.0f)
{
uboVS.lodBias = 0.0f;
}
if (uboVS.lodBias > texture.levelCount)
{
uboVS.lodBias = (float)texture.levelCount;
}
updateUniformBuffers();
//updateTextOverlay();
}
void
Texture::keyPressed(uint32_t keyCode)
{
switch (keyCode)
{
case SDLK_KP_PLUS:
changeLodBias(0.1f);
break;
case SDLK_KP_MINUS:
changeLodBias(-0.1f);
break;
}
}
void
Texture::getOverlayText(VulkanTextOverlay *textOverlay, float yOffset)
{
std::stringstream ss;
ss << std::setprecision(2) << std::fixed << uboVS.lodBias;
textOverlay->addText("LOD bias: " + ss.str() + " (numpad +/- to change)",
5.0f, yOffset, VulkanTextOverlay::alignLeft);
}
const char*
Texture::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;
}