Added cube map array sample

This commit is contained in:
Sascha Willems 2020-07-19 07:07:54 +02:00
parent f49c3d9ede
commit dceb349849
23 changed files with 1065 additions and 11 deletions

View file

@ -97,35 +97,39 @@ Uses SPIR-V specialization constants to create multiple pipelines with different
Loads a 2D texture from disk (including all mip levels), uses staging to upload it into video memory and samples from it using combined image samplers. Loads a 2D texture from disk (including all mip levels), uses staging to upload it into video memory and samples from it using combined image samplers.
#### [08 - Cube map textures](examples/texturecubemap/) #### [08 - Texture arrays](examples/texturearray/)
Loads a cube map texture from disk containing six different faces. All faces and mip levels are uploaded into video memory and the cubemap is sampled once as a skybox (for the background) and as a source for reflections (for a 3D model).
#### [09 - Texture arrays](examples/texturearray/)
Loads a 2D texture array containing multiple 2D texture slices (each with its own mip chain) and renders multiple meshes each sampling from a different layer of the texture. 2D texture arrays don't do any interpolation between the slices. Loads a 2D texture array containing multiple 2D texture slices (each with its own mip chain) and renders multiple meshes each sampling from a different layer of the texture. 2D texture arrays don't do any interpolation between the slices.
#### [10 - 3D textures](examples/texture3d/) #### [09 - Cube map textures](examples/texturecubemap/)
Loads a cube map texture from disk containing six different faces. All faces and mip levels are uploaded into video memory, and the cubemap is displayed on a skybox as a backdrop and on a 3D model as a reflection.
#### [10 - Cube map arrays](examples/texturecubemaparray/)
Loads an array of cube map textures from a single file. All cube maps are uploaded into video memory with their faces and mip levels, and the selected cubemap is displayed on a skybox as a backdrop and on a 3D model as a reflection.
#### [11 - 3D textures](examples/texture3d/)
Generates a 3D texture on the cpu (using perlin noise), uploads it to the device and samples it to render an animation. 3D textures store volumetric data and interpolate in all three dimensions. Generates a 3D texture on the cpu (using perlin noise), uploads it to the device and samples it to render an animation. 3D textures store volumetric data and interpolate in all three dimensions.
#### [11 - Input attachments](examples/inputattachments) #### [12 - Input attachments](examples/inputattachments)
Uses input attachments to read framebuffer contents from a previous sub pass at the same pixel position within a single render pass. This can be used for basic post processing or image composition ([blog entry](https://www.saschawillems.de/tutorials/vulkan/input_attachments_subpasses)). Uses input attachments to read framebuffer contents from a previous sub pass at the same pixel position within a single render pass. This can be used for basic post processing or image composition ([blog entry](https://www.saschawillems.de/tutorials/vulkan/input_attachments_subpasses)).
#### [12 - Sub passes](examples/subpasses/) #### [13 - Sub passes](examples/subpasses/)
Advanced example that uses sub passes and input attachments to write and read back data from framebuffer attachments (same location only) in single render pass. This is used to implement deferred render composition with added forward transparency in a single pass. Advanced example that uses sub passes and input attachments to write and read back data from framebuffer attachments (same location only) in single render pass. This is used to implement deferred render composition with added forward transparency in a single pass.
#### [13 - Offscreen rendering](examples/offscreen/) #### [14 - Offscreen rendering](examples/offscreen/)
Basic offscreen rendering in two passes. First pass renders the mirrored scene to a separate framebuffer with color and depth attachments, second pass samples from that color attachment for rendering a mirror surface. Basic offscreen rendering in two passes. First pass renders the mirrored scene to a separate framebuffer with color and depth attachments, second pass samples from that color attachment for rendering a mirror surface.
#### [14 - CPU particle system](examples/particlefire/) #### [15 - CPU particle system](examples/particlefire/)
Implements a simple CPU based particle system. Particle data is stored in host memory, updated on the CPU per-frame and synchronized with the device before it's rendered using pre-multiplied alpha. Implements a simple CPU based particle system. Particle data is stored in host memory, updated on the CPU per-frame and synchronized with the device before it's rendered using pre-multiplied alpha.
#### [15 - Stencil buffer](examples/stencilbuffer/) #### [16 - Stencil buffer](examples/stencilbuffer/)
Uses the stencil buffer and its compare functionality for rendering a 3D model with dynamic outlines. Uses the stencil buffer and its compare functionality for rendering a 3D model with dynamic outlines.

View file

@ -0,0 +1,36 @@
cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
set(NAME texturecubemaparray)
set(SRC_DIR ../../../examples/${NAME})
set(BASE_DIR ../../../base)
set(EXTERNAL_DIR ../../../external)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
add_library(native-lib SHARED ${EXAMPLE_SRC})
add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
include_directories(${BASE_DIR})
include_directories(${EXTERNAL_DIR})
include_directories(${EXTERNAL_DIR}/glm)
include_directories(${EXTERNAL_DIR}/gli)
include_directories(${EXTERNAL_DIR}/imgui)
include_directories(${EXTERNAL_DIR}/assimp)
include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
target_link_libraries(
native-lib
native-app-glue
libbase
android
log
z
)

View file

@ -0,0 +1,96 @@
apply plugin: 'com.android.application'
apply from: '../gradle/outputfilename.gradle'
android {
compileSdkVersion 26
defaultConfig {
applicationId "de.saschawillems.vulkanTexturecubemapArray"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
ndk {
abiFilters "armeabi-v7a"
}
externalNativeBuild {
cmake {
cppFlags "-std=c++14"
arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
}
}
}
sourceSets {
main.assets.srcDirs = ['assets']
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
task copyTask {
copy {
from '../../common/res/drawable'
into "src/main/res/drawable"
include 'icon.png'
}
copy {
from '../../../data/shaders/glsl/base'
into 'assets/shaders/glsl/base'
include '*.spv'
}
copy {
from '../../../data/shaders/glsl/texturecubemaparray'
into 'assets/shaders/glsl/texturecubemaparray'
include '*.*'
}
copy {
from '../../../data/models'
into 'assets/models'
include 'sphere.obj'
}
copy {
from '../../../data/models'
into 'assets/models'
include 'teapot.dae'
}
copy {
from '../../../data/models'
into 'assets/models'
include 'torusknot.obj'
}
copy {
from '../../../data/models'
into 'assets/models'
include 'cube.obj'
}
copy {
from '../../../data/models'
into 'assets/models'
include 'venus.fbx'
}
copy {
from '../../../data/textures'
into 'assets/textures'
include 'cubemap_array.ktx'
}
}
preBuild.dependsOn copyTask

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.saschawillems.vulkanTexturecubemapArray">
<application
android:label="Vulkan cubemap arrays"
android:icon="@drawable/icon"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
<activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden">
<meta-data android:name="android.app.lib_name"
android:value="native-lib" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
</manifest>

View file

@ -0,0 +1,58 @@
/*
* Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
*
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
*/
package de.saschawillems.vulkanSample;
import android.app.AlertDialog;
import android.app.NativeActivity;
import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import java.util.concurrent.Semaphore;
public class VulkanActivity extends NativeActivity {
static {
// Load native library
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
// Use a semaphore to create a modal dialog
private final Semaphore semaphore = new Semaphore(0, true);
public void showAlert(final String message)
{
final VulkanActivity activity = this;
ApplicationInfo applicationInfo = activity.getApplicationInfo();
final String applicationName = applicationInfo.nonLocalizedLabel.toString();
this.runOnUiThread(new Runnable() {
public void run() {
AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
builder.setTitle(applicationName);
builder.setMessage(message);
builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
semaphore.release();
}
});
builder.setCancelable(false);
AlertDialog dialog = builder.create();
dialog.show();
}
});
try {
semaphore.acquire();
}
catch (InterruptedException e) { }
}
}

View file

@ -0,0 +1,39 @@
#version 450
layout (binding = 1) uniform samplerCubeArray samplerCubeMapArray;
layout (binding = 0) uniform UBO
{
mat4 projection;
mat4 model;
mat4 invModel;
float lodBias;
int cubeMapIndex;
} ubo;
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inNormal;
layout (location = 2) in vec3 inViewVec;
layout (location = 3) in vec3 inLightVec;
layout (location = 0) out vec4 outFragColor;
void main()
{
vec3 cI = normalize (inPos);
vec3 cR = reflect (cI, normalize(inNormal));
cR = vec3(ubo.invModel * vec4(cR, 0.0));
cR.y *= -1.0;
vec4 color = texture(samplerCubeMapArray, vec4(cR, ubo.cubeMapIndex), ubo.lodBias);
vec3 N = normalize(inNormal);
vec3 L = normalize(inLightVec);
vec3 V = normalize(inViewVec);
vec3 R = reflect(-L, N);
vec3 ambient = vec3(0.5) * color.rgb;
vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0);
vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.5);
outFragColor = vec4(ambient + diffuse * color.rgb + specular, 1.0);
}

Binary file not shown.

View file

@ -0,0 +1,30 @@
#version 450
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inNormal;
layout (binding = 0) uniform UBO
{
mat4 projection;
mat4 model;
mat4 invModel;
float lodBias;
int cubeMapIndex;
} ubo;
layout (location = 0) out vec3 outPos;
layout (location = 1) out vec3 outNormal;
layout (location = 2) out vec3 outViewVec;
layout (location = 3) out vec3 outLightVec;
void main()
{
gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
outPos = vec3(ubo.model * vec4(inPos, 1.0));
outNormal = mat3(ubo.model) * inNormal;
vec3 lightPos = vec3(0.0f, -5.0f, 5.0f);
outLightVec = lightPos.xyz - outPos.xyz;
outViewVec = -outPos.xyz;
}

Binary file not shown.

View file

@ -0,0 +1,21 @@
#version 450
layout (binding = 1) uniform samplerCubeArray samplerCubeMapArray;
layout (binding = 0) uniform UBO
{
mat4 projection;
mat4 model;
mat4 invModel;
float lodBias;
int cubeMapIndex;
} ubo;
layout (location = 0) in vec3 inUVW;
layout (location = 0) out vec4 outFragColor;
void main()
{
outFragColor = texture(samplerCubeMapArray, vec4(inUVW, ubo.cubeMapIndex), ubo.lodBias);
}

Binary file not shown.

View file

@ -0,0 +1,21 @@
#version 450
layout (location = 0) in vec3 inPos;
layout (binding = 0) uniform UBO
{
mat4 projection;
mat4 model;
mat4 invModel;
float lodBias;
int cubeMapIndex;
} ubo;
layout (location = 0) out vec3 outUVW;
void main()
{
outUVW = inPos;
outUVW.y *= -1.0;
gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
}

Binary file not shown.

View file

@ -0,0 +1,44 @@
// Copyright 2020 Google LLC
TextureCubeArray textureCubeMapArray : register(t1);
SamplerState samplerCubeMapArray : register(s1);
struct UBO
{
float4x4 projection;
float4x4 model;
float4x4 invModel;
float lodBias;
int cubeMapIndex;
};
cbuffer ubo : register(b0) { UBO ubo; }
struct VSOutput
{
[[vk::location(0)]] float3 Pos : POSITION0;
[[vk::location(1)]] float3 Normal : NORMAL0;
[[vk::location(2)]] float LodBias : TEXCOORD3;
[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
[[vk::location(4)]] float3 LightVec : TEXCOORD2;
};
float4 main(VSOutput input) : SV_TARGET
{
float3 cI = normalize (input.ViewVec);
float3 cR = reflect (cI, normalize(input.Normal));
cR = mul(ubo.invModel, float4(cR, 0.0)).xyz;
cR *= float3(-1.0, 1.0, -1.0);
float4 color = textureCubeMapArray.SampleLevel(samplerCubeMapArray, float4(cR, ubo.cubeMapIndex), input.LodBias);
float3 N = normalize(input.Normal);
float3 L = normalize(input.LightVec);
float3 V = normalize(input.ViewVec);
float3 R = reflect(-L, N);
float3 ambient = float3(0.5, 0.5, 0.5) * color.rgb;
float3 diffuse = max(dot(N, L), 0.0) * float3(1.0, 1.0, 1.0);
float3 specular = pow(max(dot(R, V), 0.0), 16.0) * float3(0.5, 0.5, 0.5);
return float4(ambient + diffuse * color.rgb + specular, 1.0);
}

Binary file not shown.

View file

@ -0,0 +1,42 @@
// Copyright 2020 Google LLC
struct VSInput
{
[[vk::location(0)]] float3 Pos : POSITION0;
[[vk::location(1)]] float3 Normal : NORMAL0;
};
struct UBO
{
float4x4 projection;
float4x4 model;
float4x4 invModel;
float lodBias;
};
cbuffer ubo : register(b0) { UBO ubo; }
struct VSOutput
{
float4 Pos : SV_POSITION;
[[vk::location(0)]] float3 WorldPos : POSITION0;
[[vk::location(1)]] float3 Normal : NORMAL0;
[[vk::location(2)]] float LodBias : TEXCOORD3;
[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
[[vk::location(4)]] float3 LightVec : TEXCOORD2;
};
VSOutput main(VSInput input)
{
VSOutput output = (VSOutput)0;
output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
output.WorldPos = mul(ubo.model, float4(input.Pos, 1.0)).xyz;
output.Normal = mul((float3x3)ubo.model, input.Normal);
output.LodBias = ubo.lodBias;
float3 lightPos = float3(0.0f, -5.0f, 5.0f);
output.LightVec = lightPos.xyz - output.WorldPos.xyz;
output.ViewVec = -output.WorldPos;
return output;
}

Binary file not shown.

View file

@ -0,0 +1,20 @@
// Copyright 2020 Google LLC
TextureCubeArray textureCubeMapArray : register(t1);
SamplerState samplerCubeMapArray : register(s1);
struct UBO
{
float4x4 projection;
float4x4 model;
float4x4 invModel;
float lodBias;
int cubeMapIndex;
};
cbuffer ubo : register(b0) { UBO ubo; }
float4 main([[vk::location(0)]] float3 inUVW : TEXCOORD0) : SV_TARGET
{
return textureCubeMapArray.SampleLevel(samplerCubeMapArray, float4(inUVW, ubo.cubeMapIndex), ubo.lodBias);
}

Binary file not shown.

View file

@ -0,0 +1,27 @@
// Copyright 2020 Google LLC
struct UBO
{
float4x4 projection;
float4x4 model;
float4x4 invModel;
float lodBias;
int cubeMapIndex;
};
cbuffer ubo : register(b0) { UBO ubo; }
struct VSOutput
{
float4 Pos : SV_POSITION;
[[vk::location(0)]] float3 UVW : TEXCOORD0;
};
VSOutput main([[vk::location(0)]] float3 Pos : POSITION0)
{
VSOutput output = (VSOutput)0;
output.UVW = Pos;
output.UVW.y *= -1.0;
output.Pos = mul(ubo.projection, mul(ubo.model, float4(Pos.xyz, 1.0)));
return output;
}

Binary file not shown.

View file

@ -117,6 +117,7 @@ set(EXAMPLES
texture3d texture3d
texturearray texturearray
texturecubemap texturecubemap
texturecubemaparray
texturemipmapgen texturemipmapgen
texturesparseresidency texturesparseresidency
triangle triangle

View file

@ -0,0 +1,591 @@
/*
* Vulkan Example - Cube map array texture loading and displaying
*
* Copyright (C) 2020 by Sascha Willems - www.saschawillems.de
*
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <vector>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <vulkan/vulkan.h>
#include "vulkanexamplebase.h"
#include "VulkanBuffer.hpp"
#include "VulkanTexture.hpp"
#include "VulkanModel.hpp"
#include <ktx.h>
#include <ktxvulkan.h>
#define ENABLE_VALIDATION false
class VulkanExample : public VulkanExampleBase
{
public:
bool displaySkybox = true;
vks::Texture cubeMapArray;
// Vertex layout for the models
vks::VertexLayout vertexLayout = vks::VertexLayout({
vks::VERTEX_COMPONENT_POSITION,
vks::VERTEX_COMPONENT_NORMAL,
vks::VERTEX_COMPONENT_UV,
});
struct Meshes {
vks::Model skybox;
std::vector<vks::Model> objects;
int32_t objectIndex = 0;
} models;
struct {
vks::Buffer object;
vks::Buffer skybox;
} uniformBuffers;
struct ShaderData {
glm::mat4 projection;
glm::mat4 modelView;
glm::mat4 inverseModelview;
float lodBias = 0.0f;
int cubeMapIndex = 1;
} shaderData;
struct {
VkPipeline skybox;
VkPipeline reflect;
} pipelines;
struct {
VkDescriptorSet object;
VkDescriptorSet skybox;
} descriptorSets;
VkPipelineLayout pipelineLayout;
VkDescriptorSetLayout descriptorSetLayout;
std::vector<std::string> objectNames;
VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
{
title = "Cube map textures";
camera.type = Camera::CameraType::lookat;
camera.setPosition(glm::vec3(0.0f, 0.0f, -4.0f));
camera.setRotation(glm::vec3(-7.25f, -120.0f, 0.0f));
camera.setRotationSpeed(0.25f);
camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
settings.overlay = true;
}
~VulkanExample()
{
// Clean up texture resources
vkDestroyImageView(device, cubeMapArray.view, nullptr);
vkDestroyImage(device, cubeMapArray.image, nullptr);
vkDestroySampler(device, cubeMapArray.sampler, nullptr);
vkFreeMemory(device, cubeMapArray.deviceMemory, nullptr);
vkDestroyPipeline(device, pipelines.skybox, nullptr);
vkDestroyPipeline(device, pipelines.reflect, nullptr);
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
for (auto& model : models.objects) {
model.destroy();
}
models.skybox.destroy();
uniformBuffers.object.destroy();
uniformBuffers.skybox.destroy();
}
// Enable physical device features required for this example
virtual void getEnabledFeatures()
{
if (deviceFeatures.imageCubeArray) {
enabledFeatures.imageCubeArray = VK_TRUE;
} else {
vks::tools::exitFatal("Selected GPU does not support cube map arrays!", VK_ERROR_FEATURE_NOT_PRESENT);
}
enabledFeatures.imageCubeArray = VK_TRUE;
if (deviceFeatures.samplerAnisotropy) {
enabledFeatures.samplerAnisotropy = VK_TRUE;
}
};
void loadCubemapArray(std::string filename, VkFormat format, bool forceLinearTiling)
{
ktxResult result;
ktxTexture* ktxTexture;
#if defined(__ANDROID__)
// Textures are stored inside the apk on Android (compressed)
// So they need to be loaded via the asset manager
AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING);
if (!asset) {
vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
}
size_t size = AAsset_getLength(asset);
assert(size > 0);
ktx_uint8_t *textureData = new ktx_uint8_t[size];
AAsset_read(asset, textureData, size);
AAsset_close(asset);
result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
delete[] textureData;
#else
if (!vks::tools::fileExists(filename)) {
vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
}
result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
#endif
assert(result == KTX_SUCCESS);
// Get properties required for using and upload texture data from the ktx texture object
cubeMapArray.width = ktxTexture->baseWidth;
cubeMapArray.height = ktxTexture->baseHeight;
cubeMapArray.mipLevels = ktxTexture->numLevels;
cubeMapArray.layerCount = ktxTexture->numLayers;
ktx_uint8_t *ktxTextureData = ktxTexture_GetData(ktxTexture);
ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture);
VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
VkMemoryRequirements memReqs;
// Create a host-visible staging buffer that contains the raw image data
VkBuffer stagingBuffer;
VkDeviceMemory stagingMemory;
VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo();
bufferCreateInfo.size = ktxTextureSize;
// This buffer is used as a transfer source for the buffer copy
bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &stagingBuffer));
// Get memory requirements for the staging buffer (alignment, memory type bits)
vkGetBufferMemoryRequirements(device, stagingBuffer, &memReqs);
memAllocInfo.allocationSize = memReqs.size;
// Get memory type index for a host visible buffer
memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &stagingMemory));
VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffer, stagingMemory, 0));
// Copy texture data into staging buffer
uint8_t *data;
VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, memReqs.size, 0, (void **)&data));
memcpy(data, ktxTextureData, ktxTextureSize);
vkUnmapMemory(device, stagingMemory);
// Create optimal tiled target image
VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
imageCreateInfo.format = format;
imageCreateInfo.mipLevels = cubeMapArray.mipLevels;
imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageCreateInfo.extent = { cubeMapArray.width, cubeMapArray.height, 1 };
imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
// Cube faces count as array layers in Vulkan
imageCreateInfo.arrayLayers = 6 * cubeMapArray.layerCount;
// This flag is required for cube map images
imageCreateInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &cubeMapArray.image));
vkGetImageMemoryRequirements(device, cubeMapArray.image, &memReqs);
memAllocInfo.allocationSize = memReqs.size;
memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &cubeMapArray.deviceMemory));
VK_CHECK_RESULT(vkBindImageMemory(device, cubeMapArray.image, cubeMapArray.deviceMemory, 0));
VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
// Setup buffer copy regions for each face including all of its miplevels
std::vector<VkBufferImageCopy> bufferCopyRegions;
uint32_t offset = 0;
// Setup buffer copy regions to copy the data from the ktx file to our image
for (uint32_t layer = 0; layer < ktxTexture->numLayers; layer++) {
for (uint32_t face = 0; face < 6; face++) {
for (uint32_t level = 0; level < ktxTexture->numLevels; level++) {
ktx_size_t offset;
KTX_error_code ret = ktxTexture_GetImageOffset(ktxTexture, level, layer, face, &offset);
assert(ret == KTX_SUCCESS);
VkBufferImageCopy bufferCopyRegion = {};
bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
bufferCopyRegion.imageSubresource.mipLevel = level;
bufferCopyRegion.imageSubresource.baseArrayLayer = layer * 6 + face;
bufferCopyRegion.imageSubresource.layerCount = 1;
bufferCopyRegion.imageExtent.width = ktxTexture->baseWidth >> level;
bufferCopyRegion.imageExtent.height = ktxTexture->baseHeight >> level;
bufferCopyRegion.imageExtent.depth = 1;
bufferCopyRegion.bufferOffset = offset;
bufferCopyRegions.push_back(bufferCopyRegion);
}
}
}
// Image barrier for optimal image (target)
// Set initial layout for all array layers (faces) of the optimal (target) tiled texture
VkImageSubresourceRange subresourceRange = {};
subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
subresourceRange.baseMipLevel = 0;
subresourceRange.levelCount = cubeMapArray.mipLevels;
subresourceRange.layerCount = 6 * cubeMapArray.layerCount;
vks::tools::setImageLayout(
copyCmd,
cubeMapArray.image,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
subresourceRange);
// Copy the cube map faces from the staging buffer to the optimal tiled image
vkCmdCopyBufferToImage(
copyCmd,
stagingBuffer,
cubeMapArray.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
static_cast<uint32_t>(bufferCopyRegions.size()),
bufferCopyRegions.data()
);
// Change texture image layout to shader read after all faces have been copied
cubeMapArray.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
vks::tools::setImageLayout(
copyCmd,
cubeMapArray.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
cubeMapArray.imageLayout,
subresourceRange);
vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
// Create sampler
VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
sampler.magFilter = VK_FILTER_LINEAR;
sampler.minFilter = VK_FILTER_LINEAR;
sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler.addressModeV = sampler.addressModeU;
sampler.addressModeW = sampler.addressModeU;
sampler.mipLodBias = 0.0f;
sampler.compareOp = VK_COMPARE_OP_NEVER;
sampler.minLod = 0.0f;
sampler.maxLod = cubeMapArray.mipLevels;
sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
sampler.maxAnisotropy = 1.0f;
if (vulkanDevice->features.samplerAnisotropy)
{
sampler.maxAnisotropy = vulkanDevice->properties.limits.maxSamplerAnisotropy;
sampler.anisotropyEnable = VK_TRUE;
}
VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &cubeMapArray.sampler));
// Create the image view for a cube map array
VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo();
view.viewType = VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
view.format = format;
view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
view.subresourceRange.layerCount = 6 * cubeMapArray.layerCount;
view.subresourceRange.levelCount = cubeMapArray.mipLevels;
view.image = cubeMapArray.image;
VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &cubeMapArray.view));
// Clean up staging resources
vkFreeMemory(device, stagingMemory, nullptr);
vkDestroyBuffer(device, stagingBuffer, nullptr);
ktxTexture_Destroy(ktxTexture);
}
void buildCommandBuffers()
{
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
VkClearValue clearValues[2];
clearValues[0].color = defaultClearColor;
clearValues[1].depthStencil = { 1.0f, 0 };
VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
renderPassBeginInfo.renderPass = renderPass;
renderPassBeginInfo.renderArea.offset.x = 0;
renderPassBeginInfo.renderArea.offset.y = 0;
renderPassBeginInfo.renderArea.extent.width = width;
renderPassBeginInfo.renderArea.extent.height = height;
renderPassBeginInfo.clearValueCount = 2;
renderPassBeginInfo.pClearValues = clearValues;
for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
{
// Set target frame buffer
renderPassBeginInfo.framebuffer = frameBuffers[i];
VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
VkDeviceSize offsets[1] = { 0 };
// Skybox
if (displaySkybox)
{
vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.skybox, 0, NULL);
vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &models.skybox.vertices.buffer, offsets);
vkCmdBindIndexBuffer(drawCmdBuffers[i], models.skybox.indices.buffer, 0, VK_INDEX_TYPE_UINT32);
vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skybox);
vkCmdDrawIndexed(drawCmdBuffers[i], models.skybox.indexCount, 1, 0, 0, 0);
}
// 3D object
vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.object, 0, NULL);
vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &models.objects[models.objectIndex].vertices.buffer, offsets);
vkCmdBindIndexBuffer(drawCmdBuffers[i], models.objects[models.objectIndex].indices.buffer, 0, VK_INDEX_TYPE_UINT32);
vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.reflect);
vkCmdDrawIndexed(drawCmdBuffers[i], models.objects[models.objectIndex].indexCount, 1, 0, 0, 0);
drawUI(drawCmdBuffers[i]);
vkCmdEndRenderPass(drawCmdBuffers[i]);
VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
}
}
void loadAssets()
{
// Skybox
models.skybox.loadFromFile(getAssetPath() + "models/cube.obj", vertexLayout, 0.05f, vulkanDevice, queue);
// Objects
std::vector<std::string> filenames = { "sphere.obj", "teapot.dae", "torusknot.obj", "venus.fbx" };
objectNames = { "Sphere", "Teapot", "Torusknot", "Venus" };
for (auto file : filenames) {
vks::Model model;
model.loadFromFile(getAssetPath() + "models/" + file, vertexLayout, 0.05f * (file == "venus.fbx" ? 3.0f : 1.0f), vulkanDevice, queue);
models.objects.push_back(model);
}
// Load the cube map array from a ktx texture file
loadCubemapArray(getAssetPath() + "textures/cubemap_array.ktx", VK_FORMAT_R8G8B8A8_UNORM, false);
}
void setupDescriptorPool()
{
const std::vector<VkDescriptorPoolSize> poolSizes = {
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2)
};
const VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
}
void setupDescriptorSetLayout()
{
const std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
// Binding 0 : Uniform buffer
vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0),
// Binding 1 : Fragment shader image sampler
vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1)
};
const VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
const VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout));
}
void setupDescriptorSets()
{
// Image descriptor for the cube map texture
VkDescriptorImageInfo textureDescriptor = vks::initializers::descriptorImageInfo(cubeMapArray.sampler, cubeMapArray.view, cubeMapArray.imageLayout);
VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
// 3D object descriptor set
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.object));
std::vector<VkWriteDescriptorSet> writeDescriptorSets =
{
// Binding 0 : Vertex shader uniform buffer
vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.object.descriptor),
// Binding 1 : Fragment shader cubemap sampler
vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureDescriptor)
};
vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
// Sky box descriptor set
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.skybox));
writeDescriptorSets =
{
// Binding 0 : Vertex shader uniform buffer
vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.skybox.descriptor),
// Binding 1 : Fragment shader cubemap sampler
vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureDescriptor)
};
vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
}
void preparePipelines()
{
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
std::vector<VkDynamicState> dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
// Vertex bindings and attributes
VkVertexInputBindingDescription vertexInputBinding =
vks::initializers::vertexInputBindingDescription(0, vertexLayout.stride(), VK_VERTEX_INPUT_RATE_VERTEX);
std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Location 0: Position
vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3), // Location 1: Normal
};
VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
vertexInputState.vertexBindingDescriptionCount = 1;
vertexInputState.pVertexBindingDescriptions = &vertexInputBinding;
vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data();
VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
pipelineCreateInfo.pRasterizationState = &rasterizationState;
pipelineCreateInfo.pColorBlendState = &colorBlendState;
pipelineCreateInfo.pMultisampleState = &multisampleState;
pipelineCreateInfo.pViewportState = &viewportState;
pipelineCreateInfo.pDepthStencilState = &depthStencilState;
pipelineCreateInfo.pDynamicState = &dynamicState;
pipelineCreateInfo.stageCount = shaderStages.size();
pipelineCreateInfo.pStages = shaderStages.data();
pipelineCreateInfo.pVertexInputState = &vertexInputState;
// Skybox pipeline (background cube)
shaderStages[0] = loadShader(getShadersPath() + "texturecubemaparray/skybox.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
shaderStages[1] = loadShader(getShadersPath() + "texturecubemaparray/skybox.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.skybox));
// Cube map reflect pipeline
shaderStages[0] = loadShader(getShadersPath() + "texturecubemaparray/reflect.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
shaderStages[1] = loadShader(getShadersPath() + "texturecubemaparray/reflect.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
// Enable depth test and write
depthStencilState.depthWriteEnable = VK_TRUE;
depthStencilState.depthTestEnable = VK_TRUE;
// Flip cull mode
rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT;
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.reflect));
}
void prepareUniformBuffers()
{
// Object vertex shader uniform buffer
VK_CHECK_RESULT(vulkanDevice->createBuffer(
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&uniformBuffers.object,
sizeof(ShaderData)));
// Skybox vertex shader uniform buffer
VK_CHECK_RESULT(vulkanDevice->createBuffer(
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&uniformBuffers.skybox,
sizeof(ShaderData)));
// Map persistent
VK_CHECK_RESULT(uniformBuffers.object.map());
VK_CHECK_RESULT(uniformBuffers.skybox.map());
updateUniformBuffers();
}
void updateUniformBuffers()
{
// 3D object
shaderData.projection = camera.matrices.perspective;
shaderData.modelView = camera.matrices.view;
shaderData.inverseModelview = glm::inverse(camera.matrices.view);
memcpy(uniformBuffers.object.mapped, &shaderData, sizeof(ShaderData));
// Skybox
shaderData.modelView = camera.matrices.view;
// Cancel out translation
shaderData.modelView[3] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
memcpy(uniformBuffers.skybox.mapped, &shaderData, sizeof(ShaderData));
}
void draw()
{
VulkanExampleBase::prepareFrame();
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
VulkanExampleBase::submitFrame();
}
void prepare()
{
VulkanExampleBase::prepare();
loadAssets();
prepareUniformBuffers();
setupDescriptorSetLayout();
preparePipelines();
setupDescriptorPool();
setupDescriptorSets();
buildCommandBuffers();
prepared = true;
}
virtual void render()
{
if (!prepared)
return;
draw();
if (camera.updated) {
updateUniformBuffers();
}
}
virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
{
if (overlay->header("Settings")) {
if (overlay->sliderInt("Cube map", &shaderData.cubeMapIndex, 0, cubeMapArray.layerCount - 1)) {
updateUniformBuffers();
}
if (overlay->sliderFloat("LOD bias", &shaderData.lodBias, 0.0f, (float)cubeMapArray.mipLevels)) {
updateUniformBuffers();
}
if (overlay->comboBox("Object type", &models.objectIndex, objectNames)) {
buildCommandBuffers();
}
if (overlay->checkBox("Skybox", &displaySkybox)) {
buildCommandBuffers();
}
}
}
};
VULKAN_EXAMPLE_MAIN()