diff --git a/README.md b/README.md index 0805482e..7b47202a 100644 --- a/README.md +++ b/README.md @@ -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. -#### [08 - 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 sampled once as a skybox (for the background) and as a source for reflections (for a 3D model). - -#### [09 - Texture arrays](examples/texturearray/) +#### [08 - 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. -#### [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. -#### [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)). -#### [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. -#### [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. -#### [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. -#### [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. diff --git a/android/examples/texturecubemaparray/CMakeLists.txt b/android/examples/texturecubemaparray/CMakeLists.txt new file mode 100644 index 00000000..83756c4d --- /dev/null +++ b/android/examples/texturecubemaparray/CMakeLists.txt @@ -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 +) diff --git a/android/examples/texturecubemaparray/build.gradle b/android/examples/texturecubemaparray/build.gradle new file mode 100644 index 00000000..7a1d6985 --- /dev/null +++ b/android/examples/texturecubemaparray/build.gradle @@ -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 \ No newline at end of file diff --git a/android/examples/texturecubemaparray/src/main/AndroidManifest.xml b/android/examples/texturecubemaparray/src/main/AndroidManifest.xml new file mode 100644 index 00000000..f194d0d0 --- /dev/null +++ b/android/examples/texturecubemaparray/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + diff --git a/android/examples/texturecubemaparray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/texturecubemaparray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java new file mode 100644 index 00000000..12e14fc6 --- /dev/null +++ b/android/examples/texturecubemaparray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java @@ -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) { } + } +} diff --git a/data/shaders/glsl/texturecubemaparray/reflect.frag b/data/shaders/glsl/texturecubemaparray/reflect.frag new file mode 100644 index 00000000..9047dff4 --- /dev/null +++ b/data/shaders/glsl/texturecubemaparray/reflect.frag @@ -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); +} \ No newline at end of file diff --git a/data/shaders/glsl/texturecubemaparray/reflect.frag.spv b/data/shaders/glsl/texturecubemaparray/reflect.frag.spv new file mode 100644 index 00000000..d05cb7e3 Binary files /dev/null and b/data/shaders/glsl/texturecubemaparray/reflect.frag.spv differ diff --git a/data/shaders/glsl/texturecubemaparray/reflect.vert b/data/shaders/glsl/texturecubemaparray/reflect.vert new file mode 100644 index 00000000..135ed298 --- /dev/null +++ b/data/shaders/glsl/texturecubemaparray/reflect.vert @@ -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; +} diff --git a/data/shaders/glsl/texturecubemaparray/reflect.vert.spv b/data/shaders/glsl/texturecubemaparray/reflect.vert.spv new file mode 100644 index 00000000..e9996713 Binary files /dev/null and b/data/shaders/glsl/texturecubemaparray/reflect.vert.spv differ diff --git a/data/shaders/glsl/texturecubemaparray/skybox.frag b/data/shaders/glsl/texturecubemaparray/skybox.frag new file mode 100644 index 00000000..c22e5dff --- /dev/null +++ b/data/shaders/glsl/texturecubemaparray/skybox.frag @@ -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); +} \ No newline at end of file diff --git a/data/shaders/glsl/texturecubemaparray/skybox.frag.spv b/data/shaders/glsl/texturecubemaparray/skybox.frag.spv new file mode 100644 index 00000000..b39c92e2 Binary files /dev/null and b/data/shaders/glsl/texturecubemaparray/skybox.frag.spv differ diff --git a/data/shaders/glsl/texturecubemaparray/skybox.vert b/data/shaders/glsl/texturecubemaparray/skybox.vert new file mode 100644 index 00000000..28daad87 --- /dev/null +++ b/data/shaders/glsl/texturecubemaparray/skybox.vert @@ -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); +} diff --git a/data/shaders/glsl/texturecubemaparray/skybox.vert.spv b/data/shaders/glsl/texturecubemaparray/skybox.vert.spv new file mode 100644 index 00000000..9d6e0d96 Binary files /dev/null and b/data/shaders/glsl/texturecubemaparray/skybox.vert.spv differ diff --git a/data/shaders/hlsl/texturecubemaparray/reflect.frag b/data/shaders/hlsl/texturecubemaparray/reflect.frag new file mode 100644 index 00000000..363c8a48 --- /dev/null +++ b/data/shaders/hlsl/texturecubemaparray/reflect.frag @@ -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); +} \ No newline at end of file diff --git a/data/shaders/hlsl/texturecubemaparray/reflect.frag.spv b/data/shaders/hlsl/texturecubemaparray/reflect.frag.spv new file mode 100644 index 00000000..5aea02ba Binary files /dev/null and b/data/shaders/hlsl/texturecubemaparray/reflect.frag.spv differ diff --git a/data/shaders/hlsl/texturecubemaparray/reflect.vert b/data/shaders/hlsl/texturecubemaparray/reflect.vert new file mode 100644 index 00000000..9323ee4c --- /dev/null +++ b/data/shaders/hlsl/texturecubemaparray/reflect.vert @@ -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; +} diff --git a/data/shaders/hlsl/texturecubemaparray/reflect.vert.spv b/data/shaders/hlsl/texturecubemaparray/reflect.vert.spv new file mode 100644 index 00000000..85b14c35 Binary files /dev/null and b/data/shaders/hlsl/texturecubemaparray/reflect.vert.spv differ diff --git a/data/shaders/hlsl/texturecubemaparray/skybox.frag b/data/shaders/hlsl/texturecubemaparray/skybox.frag new file mode 100644 index 00000000..10807de6 --- /dev/null +++ b/data/shaders/hlsl/texturecubemaparray/skybox.frag @@ -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); +} \ No newline at end of file diff --git a/data/shaders/hlsl/texturecubemaparray/skybox.frag.spv b/data/shaders/hlsl/texturecubemaparray/skybox.frag.spv new file mode 100644 index 00000000..111db475 Binary files /dev/null and b/data/shaders/hlsl/texturecubemaparray/skybox.frag.spv differ diff --git a/data/shaders/hlsl/texturecubemaparray/skybox.vert b/data/shaders/hlsl/texturecubemaparray/skybox.vert new file mode 100644 index 00000000..cb818f23 --- /dev/null +++ b/data/shaders/hlsl/texturecubemaparray/skybox.vert @@ -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; +} diff --git a/data/shaders/hlsl/texturecubemaparray/skybox.vert.spv b/data/shaders/hlsl/texturecubemaparray/skybox.vert.spv new file mode 100644 index 00000000..cf46f682 Binary files /dev/null and b/data/shaders/hlsl/texturecubemaparray/skybox.vert.spv differ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8d40c6b2..ee47c851 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -117,6 +117,7 @@ set(EXAMPLES texture3d texturearray texturecubemap + texturecubemaparray texturemipmapgen texturesparseresidency triangle diff --git a/examples/texturecubemaparray/texturecubemaparray.cpp b/examples/texturecubemaparray/texturecubemaparray.cpp new file mode 100644 index 00000000..9295ad2d --- /dev/null +++ b/examples/texturecubemaparray/texturecubemaparray.cpp @@ -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 +#include +#include +#include +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include + +#include +#include "vulkanexamplebase.h" +#include "VulkanBuffer.hpp" +#include "VulkanTexture.hpp" +#include "VulkanModel.hpp" +#include +#include + +#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 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 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 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(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 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 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 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 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 dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; + VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); + std::array shaderStages; + + // Vertex bindings and attributes + VkVertexInputBindingDescription vertexInputBinding = + vks::initializers::vertexInputBindingDescription(0, vertexLayout.stride(), VK_VERTEX_INPUT_RATE_VERTEX); + std::vector 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(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() \ No newline at end of file