Description
Describe the bug
Hello, I've faced next behavior when switched from ktx file without mipmap level to ktx with pregenerated mipmap levels. There is an error when using 2D image array with mipmaps levels loaded from ktx file. There is no issue if similar image but without mipmap levels is used. Though maybe I've misconfigured something.
To Reproduce
There is a code sample. Attempt to do the same with VSG as here https://github.com/SaschaWillems/Vulkan/blob/master/examples/terraintessellation/terraintessellation.cpp
This is what I got
#include <cassert>
#include <iostream>
#include <stdexcept>
#include <string>
#include "vsg/all.h"
#include <vsgXchange/all.h>
struct UBO
{
vsg::vec4 lightPos;
vsg::vec4 frustumPlanes[6];
float displacementFactor;
float tessellationFactor;
vsg::vec2 viewportDim;
float tessellatedEdgeSize;
};
using UBOValue = vsg::Value<UBO>;
std::string TESC{R"(
#version 450
layout(push_constant) uniform PushConstants { mat4 projection; mat4 modelView; };
layout(set = 0, binding = 0) uniform UBO
{
vec4 lightPos;
vec4 frustumPlanes[6];
float displacementFactor;
float tessellationFactor;
vec2 viewportDim;
float tessellatedEdgeSize;
} ubo;
layout(set = 0, binding = 1) uniform sampler2D samplerHeight;
layout (vertices = 4) out;
layout (location = 0) in vec3 inNormal[];
layout (location = 1) in vec2 inUV[];
layout (location = 0) out vec3 outNormal[4];
layout (location = 1) out vec2 outUV[4];
// Calculate the tessellation factor based on screen space
// dimensions of the edge
float screenSpaceTessFactor(vec4 p0, vec4 p1)
{
// Calculate edge mid point
vec4 midPoint = 0.5 * (p0 + p1);
// Sphere radius as distance between the control points
float radius = distance(p0, p1) / 2.0;
// View space
vec4 v0 = modelView * midPoint;
// Project into clip space
vec4 clip0 = (projection * (v0 - vec4(radius, vec3(0.0))));
vec4 clip1 = (projection * (v0 + vec4(radius, vec3(0.0))));
// Get normalized device coordinates
clip0 /= clip0.w;
clip1 /= clip1.w;
// Convert to viewport coordinates
clip0.xy *= ubo.viewportDim;
clip1.xy *= ubo.viewportDim;
// Return the tessellation factor based on the screen size
// given by the distance of the two edge control points in screen space
// and a reference (min.) tessellation size for the edge set by the application
return clamp(distance(clip0, clip1) / ubo.tessellatedEdgeSize * ubo.tessellationFactor, 1.0, 64.0);
}
// Checks the current's patch visibility against the frustum using a sphere check
// Sphere radius is given by the patch size
bool frustumCheck()
{
// Fixed radius (increase if patch size is increased in example)
const float radius = 8.0f;
vec4 pos = gl_in[gl_InvocationID].gl_Position;
pos.z += textureLod(samplerHeight, inUV[0], 0.0).r * ubo.displacementFactor;
// Check sphere against frustum planes
for (int i = 0; i < 6; i++) {
if (dot(pos, ubo.frustumPlanes[i]) + radius < 0.0)
{
return false;
}
}
return true;
}
void main()
{
if (gl_InvocationID == 0)
{
if (!frustumCheck())
{
gl_TessLevelInner[0] = 0.0;
gl_TessLevelInner[1] = 0.0;
gl_TessLevelOuter[0] = 0.0;
gl_TessLevelOuter[1] = 0.0;
gl_TessLevelOuter[2] = 0.0;
gl_TessLevelOuter[3] = 0.0;
}
else
{
if (ubo.tessellationFactor > 0.0)
{
gl_TessLevelOuter[0] = screenSpaceTessFactor(gl_in[3].gl_Position, gl_in[0].gl_Position);
gl_TessLevelOuter[1] = screenSpaceTessFactor(gl_in[0].gl_Position, gl_in[1].gl_Position);
gl_TessLevelOuter[2] = screenSpaceTessFactor(gl_in[1].gl_Position, gl_in[2].gl_Position);
gl_TessLevelOuter[3] = screenSpaceTessFactor(gl_in[2].gl_Position, gl_in[3].gl_Position);
gl_TessLevelInner[0] = mix(gl_TessLevelOuter[0], gl_TessLevelOuter[3], 0.5);
gl_TessLevelInner[1] = mix(gl_TessLevelOuter[2], gl_TessLevelOuter[1], 0.5);
}
else
{
// Tessellation factor can be set to zero by example
// to demonstrate a simple passthrough
gl_TessLevelInner[0] = 1.0;
gl_TessLevelInner[1] = 1.0;
gl_TessLevelOuter[0] = 1.0;
gl_TessLevelOuter[1] = 1.0;
gl_TessLevelOuter[2] = 1.0;
gl_TessLevelOuter[3] = 1.0;
}
}
}
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
outNormal[gl_InvocationID] = inNormal[gl_InvocationID];
outUV[gl_InvocationID] = inUV[gl_InvocationID];
}
)"};
std::string TESE{R"(
#version 450
layout(push_constant) uniform PushConstants { mat4 projection; mat4 modelView; };
layout (set = 0, binding = 0) uniform UBO
{
vec4 lightPos;
vec4 frustumPlanes[6];
float displacementFactor;
float tessellationFactor;
vec2 viewportDim;
float tessellatedEdgeSize;
} ubo;
layout (set = 0, binding = 1) uniform sampler2D displacementMap;
layout(quads, equal_spacing, cw) in;
layout (location = 0) in vec3 inNormal[];
layout (location = 1) in vec2 inUV[];
layout (location = 0) out vec3 outNormal;
layout (location = 1) out vec2 outUV;
layout (location = 2) out vec3 outViewVec;
layout (location = 3) out vec3 outLightVec;
layout (location = 4) out vec3 outEyePos;
layout (location = 5) out vec3 outWorldPos;
void main()
{
// Interpolate UV coordinates
vec2 uv1 = mix(inUV[0], inUV[1], gl_TessCoord.x);
vec2 uv2 = mix(inUV[3], inUV[2], gl_TessCoord.x);
outUV = mix(uv1, uv2, gl_TessCoord.y);
vec3 n1 = mix(inNormal[0], inNormal[1], gl_TessCoord.x);
vec3 n2 = mix(inNormal[3], inNormal[2], gl_TessCoord.x);
outNormal = mix(n1, n2, gl_TessCoord.y);
// Interpolate positions
vec4 pos1 = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_TessCoord.x);
vec4 pos2 = mix(gl_in[3].gl_Position, gl_in[2].gl_Position, gl_TessCoord.x);
vec4 pos = mix(pos1, pos2, gl_TessCoord.y);
// Displace
pos.z += textureLod(displacementMap, outUV, 0.0).r * ubo.displacementFactor;
// Perspective projection
gl_Position = projection * modelView * pos;
// Calculate vectors for lighting based on tessellated position
outViewVec = -pos.xyz;
outLightVec = normalize(ubo.lightPos.xyz + outViewVec);
outWorldPos = pos.xyz;
outEyePos = vec3(modelView * pos);
}
)"};
std::string VERT{R"(
#version 450
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inNormal;
layout (location = 2) in vec2 inUV;
layout (location = 0) out vec3 outNormal;
layout (location = 1) out vec2 outUV;
void main(void)
{
gl_Position = vec4(inPos.xyz, 1.0);
outUV = inUV;
outNormal = inNormal;
}
)"};
std::string FRAG{R"(
#version 450
layout (set = 0, binding = 1) uniform sampler2D samplerHeight;
layout (set = 0, binding = 2) uniform sampler2DArray samplerLayers;
layout (location = 0) in vec3 inNormal;
layout (location = 1) in vec2 inUV;
layout (location = 2) in vec3 inViewVec;
layout (location = 3) in vec3 inLightVec;
layout (location = 4) in vec3 inEyePos;
layout (location = 5) in vec3 inWorldPos;
layout (location = 0) out vec4 outFragColor;
vec3 sampleTerrainLayer()
{
// Define some layer ranges for sampling depending on terrain height
vec2 layers[6];
layers[0] = vec2(-10.0, 10.0);
layers[1] = vec2(5.0, 45.0);
layers[2] = vec2(45.0, 80.0);
layers[3] = vec2(75.0, 100.0);
layers[4] = vec2(95.0, 140.0);
layers[5] = vec2(140.0, 190.0);
vec3 color = vec3(0.0);
// Get height from displacement map
float height = textureLod(samplerHeight, inUV, 0.0).r * 255.0;
for (int i = 0; i < 6; i++)
{
float range = layers[i].y - layers[i].x;
float weight = (range - abs(height - layers[i].y)) / range;
weight = max(0.0, weight);
color += weight * texture(samplerLayers, vec3(inUV * 16.0, i)).rgb;
}
return color;
}
float fog(float density)
{
const float LOG2 = -1.442695;
float dist = gl_FragCoord.z / gl_FragCoord.w * 0.1;
float d = density * dist;
return 1.0 - clamp(exp2(d * d * LOG2), 0.0, 1.0);
}
void main()
{
vec3 N = normalize(inNormal);
vec3 L = normalize(inLightVec);
vec3 ambient = vec3(0.5);
vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0);
vec4 color = vec4((ambient + diffuse) * sampleTerrainLayer(), 1.0);
const vec4 fogColor = vec4(0.47, 0.5, 0.67, 0.0);
outFragColor = mix(color, fogColor, fog(0.25));
}
)"};
vsg::ref_ptr<vsg::BindGraphicsPipeline> createBindGraphicsPipeline()
{
std::vector<VkDescriptorSetLayoutBinding> descriptorSetLayoutBindings {
// Binding 0 : Shared Tessellation shader ubo
{0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT},
// Binding 1 : Height map
{1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT | VK_SHADER_STAGE_FRAGMENT_BIT},
// Binding 2 : Terrain texture array layers
{2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT},
};
/// Pipeline Layout: Descriptors (none in this case) and Push Constants
auto descriptorSetLayout = vsg::DescriptorSetLayout::create(descriptorSetLayoutBindings);
vsg::DescriptorSetLayouts descriptorSetLayouts{descriptorSetLayout};
vsg::PushConstantRanges pushConstantRanges{{VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, 0, 128}};
auto pipelineLayout = vsg::PipelineLayout::create(
descriptorSetLayouts, pushConstantRanges);
/// Shaders
auto verttexShaderHints = vsg::ShaderCompileSettings::create();
auto vertexShader = vsg::ShaderStage::create(
VK_SHADER_STAGE_VERTEX_BIT, "main", VERT, verttexShaderHints);
auto fragmentShaderHints = vsg::ShaderCompileSettings::create();
auto fragmentShader = vsg::ShaderStage::create(
VK_SHADER_STAGE_FRAGMENT_BIT, "main", FRAG, fragmentShaderHints);
auto tesselationControlShaderHints = vsg::ShaderCompileSettings::create();
auto tesselationControlShader = vsg::ShaderStage::create(
VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, "main", TESC, tesselationControlShaderHints);
auto tesselationEvaluationShaderHints = vsg::ShaderCompileSettings::create();
auto tesselationEvaluationShader = vsg::ShaderStage::create(
VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, "main", TESE, tesselationEvaluationShaderHints);
auto shaderStages = vsg::ShaderStages{vertexShader, tesselationControlShader, tesselationEvaluationShader, fragmentShader};
/// Vertex Attributes
auto vertexInputState = vsg::VertexInputState::create();
auto& bindings = vertexInputState->vertexBindingDescriptions;
auto& attributes = vertexInputState->vertexAttributeDescriptions;
uint32_t bindingIndex{0};
uint32_t location{0};
uint32_t offset{0};
// position
bindings.emplace_back(VkVertexInputBindingDescription{
bindingIndex, sizeof(vsg::vec3), VK_VERTEX_INPUT_RATE_VERTEX});
bindingIndex++;
// normal
bindings.emplace_back(VkVertexInputBindingDescription{
bindingIndex, sizeof(vsg::vec3), VK_VERTEX_INPUT_RATE_VERTEX});
bindingIndex++;
// uv
bindings.emplace_back(VkVertexInputBindingDescription{
bindingIndex, sizeof(vsg::vec2), VK_VERTEX_INPUT_RATE_VERTEX});
bindingIndex++;
bindingIndex = 0;
attributes.emplace_back(VkVertexInputAttributeDescription{
bindingIndex, location, VK_FORMAT_R32G32B32_SFLOAT, 0});
location++;
bindingIndex++;
offset += sizeof(vsg::vec3);
attributes.emplace_back(VkVertexInputAttributeDescription{
bindingIndex, location, VK_FORMAT_R32G32B32_SFLOAT, 0});
location++;
bindingIndex++;
offset += sizeof(vsg::vec3);
attributes.emplace_back(VkVertexInputAttributeDescription{
bindingIndex, location, VK_FORMAT_R32G32_SFLOAT, 0});
location++;
bindingIndex++;
offset += sizeof(vsg::vec2);
/// Pipeline States
auto inputAssemblyState = vsg::InputAssemblyState::create();
inputAssemblyState->topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
auto rasterizationState = vsg::RasterizationState::create();
rasterizationState->cullMode = VK_CULL_MODE_NONE;
// rasterizationState->polygonMode = VK_POLYGON_MODE_LINE;
auto depthStencilState = vsg::DepthStencilState::create();
depthStencilState->depthTestEnable = VK_TRUE;
auto graphicsPipelineStates = vsg::GraphicsPipelineStates{
vertexInputState,
inputAssemblyState,
rasterizationState,
vsg::TessellationState::create(4),
vsg::ColorBlendState::create(),
vsg::MultisampleState::create(),
depthStencilState};
/// Graphics Pipeline
auto pipeline = vsg::GraphicsPipeline::create(
pipelineLayout, shaderStages, graphicsPipelineStates);
return vsg::BindGraphicsPipeline::create(pipeline);
}
// Generate a terrain quad patch with normals based on heightmap data
vsg::ref_ptr<vsg::VertexIndexDraw> generateTerrain(vsg::ref_ptr<vsg::ushortArray2D> ktxTexture, vsg::ref_ptr<vsg::Options> options)
{
const uint32_t patchSize{64};
const float uvScale{1.0f};
uint32_t dim;
uint32_t scale;
dim = ktxTexture->width();
scale = dim / patchSize;
const uint32_t vertexCount = patchSize * patchSize;
auto position = vsg::vec3Array::create(vertexCount);
auto normals = vsg::vec3Array::create(vertexCount);
auto uv = vsg::vec2Array::create(vertexCount);
const float wx = 1.0f;
const float wy = 1.0f;
// Generate a two-dimensional vertex patch
for (auto x = 0; x < patchSize; x++)
{
for (auto y = 0; y < patchSize; y++)
{
uint32_t index = (x + y * patchSize);
position->at(index).x = x * wx + wx / 2.0f - (float)patchSize * wx / 2.0f;
position->at(index).y = y * wy + wy / 2.0f - (float)patchSize * wy / 2.0f;
position->at(index).z = 0.0f;
uv->at(index).x = (float)x / (patchSize - 1) * uvScale;
uv->at(index).y = (float)y / (patchSize - 1) * uvScale;
}
}
// Calculate normals from the height map using a sobel filter
for (auto x = 0; x < patchSize; x++)
{
for (auto y = 0; y < patchSize; y++)
{
// We get
float heights[3][3];
for (auto sx = -1; sx <= 1; sx++)
{
for (auto sy = -1; sy <= 1; sy++)
{
// Get height at sampled position from heightmap
vsg::ivec2 rpos = vsg::ivec2(x + sx, y + sy) * vsg::ivec2(scale, scale);
rpos.x = std::max(0, std::min(rpos.x, (int)dim - 1));
rpos.y = std::max(0, std::min(rpos.y, (int)dim - 1));
rpos.x /= scale;
rpos.y /= scale;
heights[sx + 1][sy + 1] = (ktxTexture->at(rpos.x, rpos.y) * scale) / 65535.0f;
}
}
vsg::vec3 normal;
// Gx sobel filter
normal.x = heights[0][0] - heights[2][0] + 2.0f * heights[0][1] - 2.0f * heights[2][1] + heights[0][2] - heights[2][2];
// Gy sobel filter
normal.y = heights[0][0] + 2.0f * heights[1][0] + heights[2][0] - heights[0][2] - 2.0f * heights[1][2] - heights[2][2];
// Calculate missing up component of the normal using the filtered x and y axis
// The first value controls the bump strength
normal.z = 0.25f * sqrt(1.0f - normal.x * normal.x - normal.z * normal.z);
normals->at(x + y * patchSize) = vsg::normalize(normal * vsg::vec3(2.0f, 1.0f, 2.0f));
}
}
// Generate indices
const uint32_t w = (patchSize - 1);
const uint32_t indexCount = w * w * 4;
auto indices = vsg::uintArray::create(indexCount);
for (auto x = 0; x < w; x++)
{
for (auto y = 0; y < w; y++)
{
uint32_t index = (x + y * w) * 4;
indices->at(index) = (x + y * patchSize);
indices->at(index + 1) = indices->at(index) + patchSize;
indices->at(index + 2) = indices->at(index + 1) + 1;
indices->at(index + 3) = indices->at(index) + 1;
}
}
vsg::DataList vertexArrays{position, normals, uv};
auto drawCommands = vsg::VertexIndexDraw::create();
drawCommands->assignArrays(vertexArrays);
drawCommands->assignIndices(indices);
drawCommands->indexCount = indices->width();
drawCommands->instanceCount = 1;
return drawCommands;
}
auto createScene(
vsg::Path layersPath,
vsg::Path heightMapPath,
vsg::ref_ptr<vsg::Options> options)
{
auto sceneGraph = vsg::Group::create();
auto ktxLayers = vsg::read(layersPath, options).cast<vsg::Data>();
/*ktxLayers->properties.imageViewType = VkImageViewType::VK_IMAGE_VIEW_TYPE_2D_ARRAY;
ktxLayers->properties.format = VK_FORMAT_R8G8B8A8_UNORM;*/
// ktxLayers->properties.imageViewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
auto o = vsg::read(heightMapPath, options);
auto ktxHeightMap = o.cast<vsg::ushortArray2D>();
ktxHeightMap->properties.format = VK_FORMAT_R16_UNORM;
/// Graphics Pipeline State Group
auto stateGroup = vsg::StateGroup::create();
auto bindGraphicsPipeline = createBindGraphicsPipeline();
stateGroup->add(bindGraphicsPipeline);
auto ubo = UBOValue::create();
ubo->value().displacementFactor = 20.0f;
ubo->value().tessellationFactor = 0.75f;
ubo->value().viewportDim = vsg::vec2{1024.0f, 768.0f};
ubo->value().tessellatedEdgeSize = 20.0f;
ubo->value().lightPos.set(0, 0, 300, 1);
ubo->properties.dataVariance = vsg::DYNAMIC_DATA;
auto buffer = vsg::DescriptorBuffer::create(ubo);
auto heightMapSampler = vsg::Sampler::create();
heightMapSampler->maxLod = ktxHeightMap->properties.maxNumMipmaps;
heightMapSampler->addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
heightMapSampler->addressModeV = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
heightMapSampler->addressModeW = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
heightMapSampler->borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
auto heightMap = vsg::DescriptorImage::create(heightMapSampler, ktxHeightMap, 1, 0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
auto layerSampler = vsg::Sampler::create();
layerSampler->maxLod = ktxLayers->properties.maxNumMipmaps;
layerSampler->borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
auto layers = vsg::DescriptorImage::create(layerSampler, ktxLayers, 2, 0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
auto descriptorSet = vsg::DescriptorSet::create(bindGraphicsPipeline->pipeline->layout->setLayouts[0], vsg::Descriptors{buffer, heightMap, layers});
auto bindDescriptorSet = vsg::BindDescriptorSet::create(VK_PIPELINE_BIND_POINT_GRAPHICS, bindGraphicsPipeline->pipeline->layout, 0, descriptorSet);
stateGroup->add(bindDescriptorSet);
auto drawCmd = generateTerrain(ktxHeightMap, options);
stateGroup->addChild(drawCmd);
sceneGraph->addChild(stateGroup);
return std::make_tuple(sceneGraph, ubo);
}
class Frustum
{
public:
enum side
{
LEFT = 0,
RIGHT = 1,
TOP = 2,
BOTTOM = 3,
BACK = 4,
FRONT = 5
};
std::array<vsg::dvec4, 6> planes;
void update(vsg::dmat4 matrix)
{
planes[LEFT].x = matrix[0].w + matrix[0].x;
planes[LEFT].y = matrix[1].w + matrix[1].x;
planes[LEFT].z = matrix[2].w + matrix[2].x;
planes[LEFT].w = matrix[3].w + matrix[3].x;
planes[RIGHT].x = matrix[0].w - matrix[0].x;
planes[RIGHT].y = matrix[1].w - matrix[1].x;
planes[RIGHT].z = matrix[2].w - matrix[2].x;
planes[RIGHT].w = matrix[3].w - matrix[3].x;
planes[TOP].x = matrix[0].w - matrix[0].y;
planes[TOP].y = matrix[1].w - matrix[1].y;
planes[TOP].z = matrix[2].w - matrix[2].y;
planes[TOP].w = matrix[3].w - matrix[3].y;
planes[BOTTOM].x = matrix[0].w + matrix[0].y;
planes[BOTTOM].y = matrix[1].w + matrix[1].y;
planes[BOTTOM].z = matrix[2].w + matrix[2].y;
planes[BOTTOM].w = matrix[3].w + matrix[3].y;
planes[BACK].x = matrix[0].w + matrix[0].z;
planes[BACK].y = matrix[1].w + matrix[1].z;
planes[BACK].z = matrix[2].w + matrix[2].z;
planes[BACK].w = matrix[3].w + matrix[3].z;
planes[FRONT].x = matrix[0].w - matrix[0].z;
planes[FRONT].y = matrix[1].w - matrix[1].z;
planes[FRONT].z = matrix[2].w - matrix[2].z;
planes[FRONT].w = matrix[3].w - matrix[3].z;
for (auto i = 0; i < planes.size(); i++)
{
float length = sqrtf(planes[i].x * planes[i].x + planes[i].y * planes[i].y + planes[i].z * planes[i].z);
planes[i] /= length;
}
}
};
class FrustumUpdater : public vsg::Inherit<vsg::Visitor, FrustumUpdater>
{
public:
FrustumUpdater(vsg::ref_ptr<UBOValue> ubo, vsg::ref_ptr<vsg::Camera> camera)
: _ubo(ubo)
, _camera(camera)
{
}
void apply(vsg::FrameEvent& frame) override {
Frustum frustum;
frustum.update(_camera->projectionMatrix->transform() * _camera->viewMatrix->transform());
for (int i = 0; i < std::size(frustum.planes); i++) {
_ubo->value().frustumPlanes[i] = vsg::vec4(frustum.planes[i]);
}
_ubo->dirty();
}
private:
vsg::ref_ptr<UBOValue> _ubo;
vsg::ref_ptr<vsg::Camera> _camera;
};
int main(int argc, char** argv)
{
try
{
vsg::CommandLine arguments(&argc, argv);
// set up defaults and read command line arguments to override them
auto options = vsg::Options::create();
options->sharedObjects = vsg::SharedObjects::create();
options->fileCache = vsg::getEnv("VSG_FILE_CACHE");
options->paths = vsg::getEnvPaths("VSG_FILE_PATH");
#ifdef vsgXchange_all
// add vsgXchange's support for reading and writing 3rd party file formats
options->add(vsgXchange::all::create());
#endif
if (int log_level = 0; arguments.read("--log-level", log_level)) vsg::Logger::instance()->level = vsg::Logger::Level(log_level);
vsg::Path layers = arguments.value(std::string("numbers.ktx"), {"--layers", "-l"});
vsg::Path heights = arguments.value(std::string("heightmap.ktx"), {"--heightmap", "-h"});
auto [sceneGraph, ubo] = createScene(layers, heights, options);
/// Window
auto windowTraits = vsg::WindowTraits::create();
windowTraits->debugLayer = arguments.read({"--debug", "-d"});
windowTraits->apiDumpLayer = arguments.read({"--api", "-a"});
windowTraits->windowTitle = "terrain";
windowTraits->samples = VK_SAMPLE_COUNT_4_BIT;
auto features = windowTraits->deviceFeatures = vsg::DeviceFeatures::create();
features->get().tessellationShader = VK_TRUE;
auto window = vsg::Window::create(windowTraits);
if (!window)
{
std::cout << "Unable to create window" << std::endl;
return 1;
}
auto gpuNumber = arguments.value(-1, "--gpu");
if (gpuNumber == -1)
{
(void)window->getOrCreatePhysicalDevice();
}
else
{
auto instance = window->getOrCreateInstance();
(void)window->getOrCreateSurface(); // fulfill contract of getOrCreatePhysicalDevice();
auto& physicalDevices = instance->getPhysicalDevices();
window->setPhysicalDevice(physicalDevices[gpuNumber]);
}
/// Camera
auto lookAt = vsg::LookAt::create(
vsg::dvec3{150, 175, 200},
vsg::dvec3{0, 0, 0},
vsg::dvec3{0, 0, 1});
auto perspective = vsg::Perspective::create();
auto viewportState = vsg::ViewportState::create(window->extent2D());
auto camera = vsg::Camera::create(perspective, lookAt, viewportState);
/// Viewer and CommandGraph
auto viewer = vsg::Viewer::create();
viewer->addWindow(window);
viewer->addEventHandler(vsg::CloseHandler::create(viewer));
viewer->addEventHandler(vsg::Trackball::create(camera));
viewer->addEventHandler(FrustumUpdater::create(ubo, camera));
auto commandGraph = vsg::CommandGraph::create(window);
viewer->assignRecordAndSubmitTaskAndPresentation({commandGraph});
/// View and RenderGraph
auto view = vsg::View::create(camera);
auto renderGraph = vsg::RenderGraph::create(window, view);
commandGraph->addChild(renderGraph);
/// Add the scene to the View and compile
view->addChild(sceneGraph);
viewer->compile();
while (viewer->advanceToNextFrame())
{
viewer->handleEvents();
viewer->update();
viewer->recordAndSubmit();
viewer->present();
}
}
catch (const vsg::Exception& ve)
{
std::cerr << ve.message << std::endl;
return 1;
}
}
Attaching three files heightmap.ktx with elevation information, numbers.ktx 7 layers with mipmaps, numbers-nomipmap.ktx same 7 layers but without mipmaps.
heightmap.zip
numbers.zip
numbers-nomipmap.zip
Sample program accepts arguments --heightmap and --layers
When mipmaps are not used all works fine:
vsgterrain.exe --layers numbers-nomipmap.ktx --heightmap heightmap.ktx --debug
If mipmaps are used program is not working and a lot of errors are printed into console
vsgterrain.exe --layers numbers.ktx --heightmap heightmap.ktx --debug
VUID-vkCmdCopyBufferToImage-pRegions-00171(ERROR / SPEC): msgNum: 1867332608 - Validation Error: [ VUID-vkCmdCopyBufferToImage-pRegions-00171 ] Object 0: handle = 0x22f57403770, type = VK_OBJECT_TYPE_COMMAND_BUFFER; Object 1: handle = 0x89e60f0000000042, type = VK_OBJECT_TYPE_BUFFER; | MessageID = 0x6f4d3c00 | vkCmdCopyBufferToImage(): pRegions[10] is trying to copy 1048576 bytes plus 34603008 offset to/from the VkBuffer (VkBuffer 0x89e60f0000000042[]) which exceeds the VkBuffer total size of 35147244 bytes.
The Vulkan spec states: srcBuffer must be large enough to contain all buffer locations that are accessed according to Buffer and Image Addressing, for each element of pRegions (https://vulkan.lunarg.com/doc/view/1.3.296.0/windows/1.3-extensions/vkspec.html#VUID-vkCmdCopyBufferToImage-pRegions-00171)
Objects: 2
[0] 0x22f57403770, type: 6, name: NULL
[1] 0x89e60f0000000042, type: 9, name: NULL
VUID-vkCmdCopyBufferToImage-pRegions-00171(ERROR / SPEC): msgNum: 1867332608 - Validation Error: [ VUID-vkCmdCopyBufferToImage-pRegions-00171 ] Object 0: handle = 0x22f57403770, type = VK_OBJECT_TYPE_COMMAND_BUFFER; Object 1: handle = 0x89e60f0000000042, type = VK_OBJECT_TYPE_BUFFER; | MessageID = 0x6f4d3c00 | vkCmdCopyBufferToImage(): pRegions[11] is trying to copy 1048576 bytes plus 35651584 offset to/from the VkBuffer (VkBuffer 0x89e60f0000000042[]) which exceeds the VkBuffer total size of 35147244 bytes.
The Vulkan spec states: srcBuffer must be large enough to contain all buffer locations that are accessed according to Buffer and Image Addressing, for each element of pRegions (https://vulkan.lunarg.com/doc/view/1.3.296.0/windows/1.3-extensions/vkspec.html#VUID-vkCmdCopyBufferToImage-pRegions-00171)
Objects: 2
[0] 0x22f57403770, type: 6, name: NULL
[1] 0x89e60f0000000042, type: 9, name: NULL
VUID-vkCmdCopyBufferToImage-pRegions-00171(ERROR / SPEC): msgNum: 1867332608 - Validation Error: [ VUID-vkCmdCopyBufferToImage-pRegions-00171 ] Object 0: handle = 0x22f57403770, type = VK_OBJECT_TYPE_COMMAND_BUFFER; Object 1: handle = 0x89e60f0000000042, type = VK_OBJECT_TYPE_BUFFER; | MessageID = 0x6f4d3c00 | vkCmdCopyBufferToImage(): pRegions[12] is trying to copy 1048576 bytes plus 36700160 offset to/from the VkBuffer (VkBuffer 0x89e60f0000000042[]) which exceeds the VkBuffer total size of 35147244 bytes.
The Vulkan spec states: srcBuffer must be large enough to contain all buffer locations that are accessed according to Buffer and Image Addressing, for each element of pRegions (https://vulkan.lunarg.com/doc/view/1.3.296.0/windows/1.3-extensions/vkspec.html#VUID-vkCmdCopyBufferToImage-pRegions-00171)
Objects: 2
[0] 0x22f57403770, type: 6, name: NULL
[1] 0x89e60f0000000042, type: 9, name: NULL
VUID-vkCmdCopyBufferToImage-pRegions-00171(ERROR / SPEC): msgNum: 1867332608 - Validation Error: [ VUID-vkCmdCopyBufferToImage-pRegions-00171 ] Object 0: handle = 0x22f57403770, type = VK_OBJECT_TYPE_COMMAND_BUFFER; Object 1: handle = 0x89e60f0000000042, type = VK_OBJECT_TYPE_BUFFER; | MessageID = 0x6f4d3c00 | vkCmdCopyBufferToImage(): pRegions[13] is trying to copy 1048576 bytes plus 37748736 offset to/from the VkBuffer (VkBuffer 0x89e60f0000000042[]) which exceeds the VkBuffer total size of 35147244 bytes.
The Vulkan spec states: srcBuffer must be large enough to contain all buffer locations that are accessed according to Buffer and Image Addressing, for each element of pRegions (https://vulkan.lunarg.com/doc/view/1.3.296.0/windows/1.3-extensions/vkspec.html#VUID-vkCmdCopyBufferToImage-pRegions-00171)
Objects: 2
[0] 0x22f57403770, type: 6, name: NULL
[1] 0x89e60f0000000042, type: 9, name: NULL
VUID-vkCmdCopyBufferToImage-pRegions-00171(ERROR / SPEC): msgNum: 1867332608 - Validation Error: [ VUID-vkCmdCopyBufferToImage-pRegions-00171 ] Object 0: handle = 0x22f57403770, type = VK_OBJECT_TYPE_COMMAND_BUFFER; Object 1: handle = 0x89e60f0000000042, type = VK_OBJECT_TYPE_BUFFER; | MessageID = 0x6f4d3c00 | vkCmdCopyBufferToImage(): pRegions[14] is trying to copy 262144 bytes plus 38797312 offset to/from the VkBuffer (VkBuffer 0x89e60f0000000042[]) which exceeds the VkBuffer total size of 35147244 bytes.
The Vulkan spec states: srcBuffer must be large enough to contain all buffer locations that are accessed according to Buffer and Image Addressing, for each element of pRegions (https://vulkan.lunarg.com/doc/view/1.3.296.0/windows/1.3-extensions/vkspec.html#VUID-vkCmdCopyBufferToImage-pRegions-00171)
Objects: 2
[0] 0x22f57403770, type: 6, name: NULL
[1] 0x89e60f0000000042, type: 9, name: NULL
VUID-vkCmdCopyBufferToImage-pRegions-00171(ERROR / SPEC): msgNum: 1867332608 - Validation Error: [ VUID-vkCmdCopyBufferToImage-pRegions-00171 ] Object 0: handle = 0x22f57403770, type = VK_OBJECT_TYPE_COMMAND_BUFFER; Object 1: handle = 0x89e60f0000000042, type = VK_OBJECT_TYPE_BUFFER; | MessageID = 0x6f4d3c00 | vkCmdCopyBufferToImage(): pRegions[15] is trying to copy 262144 bytes plus 39059456 offset to/from the VkBuffer (VkBuffer 0x89e60f0000000042[]) which exceeds the VkBuffer total size of 35147244 bytes.
The Vulkan spec states: srcBuffer must be large enough to contain all buffer locations that are accessed according to Buffer and Image Addressing, for each element of pRegions (https://vulkan.lunarg.com/doc/view/1.3.296.0/windows/1.3-extensions/vkspec.html#VUID-vkCmdCopyBufferToImage-pRegions-00171)
Objects: 2
[0] 0x22f57403770, type: 6, name: NULL
[1] 0x89e60f0000000042, type: 9, name: NULL
VUID-vkCmdCopyBufferToImage-pRegions-00171(ERROR / SPEC): msgNum: 1867332608 - Validation Error: [ VUID-vkCmdCopyBufferToImage-pRegions-00171 ] Object 0: handle = 0x22f57403770, type = VK_OBJECT_TYPE_COMMAND_BUFFER; Object 1: handle = 0x89e60f0000000042, type = VK_OBJECT_TYPE_BUFFER; | MessageID = 0x6f4d3c00 | vkCmdCopyBufferToImage(): pRegions[16] is trying to copy 262144 bytes plus 39321600 offset to/from the VkBuffer (VkBuffer 0x89e60f0000000042[]) which exceeds the VkBuffer total size of 35147244 bytes.
The Vulkan spec states: srcBuffer must be large enough to contain all buffer locations that are accessed according to Buffer and Image Addressing, for each element of pRegions (https://vulkan.lunarg.com/doc/view/1.3.296.0/windows/1.3-extensions/vkspec.html#VUID-vkCmdCopyBufferToImage-pRegions-00171)
Objects: 2
[0] 0x22f57403770, type: 6, name: NULL
[1] 0x89e60f0000000042, type: 9, name: NULL
Expected behavior
Usage of 2D image arrays with mipmaps do not generate errors and program do not hang
Desktop (please complete the following information):
- Windows 11
Vulkan Instance Version: 1.3.261
GPU0:
VkPhysicalDeviceProperties:
apiVersion = 1.3.280 (4206872)
driverVersion = 2.0.302 (8388910)
vendorID = 0x1002
deviceID = 0x73a5
deviceType = PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
deviceName = AMD Radeon RX 6950 XT
pipelineCacheUUID = 706636c6-6310-5b8c-bf3e-147a585b1e04
Appreciate any help