diff --git a/LICENSE.md b/LICENSE.md index c6f22a086..04f0da1d5 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -100,6 +100,7 @@ The following files are licensed under the Attribution 3.0 Unported (CC BY 3.0) * all files under [`assets/3rd_party/models/cornell_box`](assets/3rd_party/models/cornell_box) * all files under [`assets/3rd_party/models/fireplace_room`](assets/3rd_party/models/fireplace_room) * all files under [`assets/3rd_party/models/sponza`](assets/3rd_party/models/sponza) + * all files under [`assets/3rd_party/textures/yokohama_at_night`](assets/3rd_party/textures/yokohama_at_night) See https://creativecommons.org/licenses/by/3.0/ for full license details. diff --git a/assets/3rd_party/textures/yokohama_at_night/cubemap_yokohama_rgba.ktx b/assets/3rd_party/textures/yokohama_at_night/cubemap_yokohama_rgba.ktx new file mode 100644 index 000000000..e85529d08 Binary files /dev/null and b/assets/3rd_party/textures/yokohama_at_night/cubemap_yokohama_rgba.ktx differ diff --git a/assets/3rd_party/textures/yokohama_at_night/license.txt b/assets/3rd_party/textures/yokohama_at_night/license.txt new file mode 100644 index 000000000..8b404c273 --- /dev/null +++ b/assets/3rd_party/textures/yokohama_at_night/license.txt @@ -0,0 +1,13 @@ +Author +====== + +This is the work of Emil Persson, aka Humus. +http://www.humus.name + + + +License +======= + +This work is licensed under a Creative Commons Attribution 3.0 Unported License. +http://creativecommons.org/licenses/by/3.0/ diff --git a/assets/3rd_party/textures/yokohama_at_night/negx.jpg b/assets/3rd_party/textures/yokohama_at_night/negx.jpg new file mode 100644 index 000000000..48b37dcc0 Binary files /dev/null and b/assets/3rd_party/textures/yokohama_at_night/negx.jpg differ diff --git a/assets/3rd_party/textures/yokohama_at_night/negy.jpg b/assets/3rd_party/textures/yokohama_at_night/negy.jpg new file mode 100644 index 000000000..08834502d Binary files /dev/null and b/assets/3rd_party/textures/yokohama_at_night/negy.jpg differ diff --git a/assets/3rd_party/textures/yokohama_at_night/negz.jpg b/assets/3rd_party/textures/yokohama_at_night/negz.jpg new file mode 100644 index 000000000..d5b53c953 Binary files /dev/null and b/assets/3rd_party/textures/yokohama_at_night/negz.jpg differ diff --git a/assets/3rd_party/textures/yokohama_at_night/posx.jpg b/assets/3rd_party/textures/yokohama_at_night/posx.jpg new file mode 100644 index 000000000..f457b83e2 Binary files /dev/null and b/assets/3rd_party/textures/yokohama_at_night/posx.jpg differ diff --git a/assets/3rd_party/textures/yokohama_at_night/posy.jpg b/assets/3rd_party/textures/yokohama_at_night/posy.jpg new file mode 100644 index 000000000..ebee2aff2 Binary files /dev/null and b/assets/3rd_party/textures/yokohama_at_night/posy.jpg differ diff --git a/assets/3rd_party/textures/yokohama_at_night/posz.jpg b/assets/3rd_party/textures/yokohama_at_night/posz.jpg new file mode 100644 index 000000000..3de23609e Binary files /dev/null and b/assets/3rd_party/textures/yokohama_at_night/posz.jpg differ diff --git a/assets/3rd_party/textures/yokohama_at_night/readme.txt b/assets/3rd_party/textures/yokohama_at_night/readme.txt new file mode 100644 index 000000000..871a4b175 --- /dev/null +++ b/assets/3rd_party/textures/yokohama_at_night/readme.txt @@ -0,0 +1,9 @@ +The JPG files were downloaded from: + +https://www.humus.name/Textures/Yokohama3.zip + +The KTX and DDS files were created for gears-vk from the JPG files using ImageViewer available at: + +https://github.com/kopaka1822/ImageViewer + +For copyright and license information see license.txt. diff --git a/assets/3rd_party/textures/yokohama_at_night/yokohama_at_night-All-Mipmaps-Srgb-RGB8-DXT1-SRGB.ktx b/assets/3rd_party/textures/yokohama_at_night/yokohama_at_night-All-Mipmaps-Srgb-RGB8-DXT1-SRGB.ktx new file mode 100644 index 000000000..44323f4d6 Binary files /dev/null and b/assets/3rd_party/textures/yokohama_at_night/yokohama_at_night-All-Mipmaps-Srgb-RGB8-DXT1-SRGB.ktx differ diff --git a/assets/3rd_party/textures/yokohama_at_night/yokohama_at_night-All-Mipmaps-Srgb-RGBA8-DXT1-SRGB.dds b/assets/3rd_party/textures/yokohama_at_night/yokohama_at_night-All-Mipmaps-Srgb-RGBA8-DXT1-SRGB.dds new file mode 100644 index 000000000..7c181601b Binary files /dev/null and b/assets/3rd_party/textures/yokohama_at_night/yokohama_at_night-All-Mipmaps-Srgb-RGBA8-DXT1-SRGB.dds differ diff --git a/auto_vk b/auto_vk index ef5585d03..4948c02ba 160000 --- a/auto_vk +++ b/auto_vk @@ -1 +1 @@ -Subproject commit ef5585d03a77c560f339b69c380ee810b46197a6 +Subproject commit 4948c02ba6d66fa8ff09c2209005b4bcc8bea5d2 diff --git a/examples/texture_cubemap/shaders/reflect.frag b/examples/texture_cubemap/shaders/reflect.frag new file mode 100644 index 000000000..7d045ecb6 --- /dev/null +++ b/examples/texture_cubemap/shaders/reflect.frag @@ -0,0 +1,40 @@ +// adapted from https://github.com/SaschaWillems/Vulkan/blob/master/data/shaders/glsl/texturecubemap/reflect.frag +#version 450 + +layout (binding = 1) uniform samplerCube samplerColor; + +layout (binding = 0) uniform UBO +{ + mat4 projection; + mat4 model; + mat4 invModel; + float lodBias; +} 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)); + + // Convert world coordinates to cubemap coordinate space + // the inverse model-view matrix already deals with the transformation from right-handed to left-handed coordinates + cR = vec3(ubo.invModel * vec4(cR, 0.0)); + + vec4 color = texture(samplerColor, cR, 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); +} diff --git a/examples/texture_cubemap/shaders/reflect.vert b/examples/texture_cubemap/shaders/reflect.vert new file mode 100644 index 000000000..5dcd6e0a1 --- /dev/null +++ b/examples/texture_cubemap/shaders/reflect.vert @@ -0,0 +1,30 @@ +// copied from https://github.com/SaschaWillems/Vulkan/blob/master/data/shaders/glsl/texturecubemap/reflect.vert +#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; +} 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/examples/texture_cubemap/shaders/skybox.frag b/examples/texture_cubemap/shaders/skybox.frag new file mode 100644 index 000000000..6ade2878d --- /dev/null +++ b/examples/texture_cubemap/shaders/skybox.frag @@ -0,0 +1,13 @@ +// copied from https://github.com/SaschaWillems/Vulkan/blob/master/data/shaders/glsl/texturecubemap/skybox.frag +#version 450 + +layout (binding = 1) uniform samplerCube samplerCubeMap; + +layout (location = 0) in vec3 inUVW; + +layout (location = 0) out vec4 outFragColor; + +void main() +{ + outFragColor = texture(samplerCubeMap, inUVW); +} diff --git a/examples/texture_cubemap/shaders/skybox.vert b/examples/texture_cubemap/shaders/skybox.vert new file mode 100644 index 000000000..6ae3b1549 --- /dev/null +++ b/examples/texture_cubemap/shaders/skybox.vert @@ -0,0 +1,22 @@ +// adapted from https://github.com/SaschaWillems/Vulkan/blob/master/data/shaders/glsl/texturecubemap/skybox.vert +#version 450 + +layout (location = 0) in vec3 inPos; + +layout (binding = 0) uniform UBO +{ + mat4 projection; + mat4 model; +} ubo; + +layout (location = 0) out vec3 outUVW; + +void main() +{ + // the skybox vertices are already in a left-handed coordinate system, + // so we can pass them to the fragment shader as-is for texture coordinate lookup + outUVW = inPos; + + // the model matrix of the skybox takes care of transforming its vertices to right-handed world coordinates + gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0); +} diff --git a/examples/texture_cubemap/source/texture_cubemap.cpp b/examples/texture_cubemap/source/texture_cubemap.cpp new file mode 100644 index 000000000..03255896a --- /dev/null +++ b/examples/texture_cubemap/source/texture_cubemap.cpp @@ -0,0 +1,368 @@ +#include +#include + +// There are several options for this example application: +enum struct options +{ + one_dds_file, + one_ktx_file, + six_jpeg_files +}; + +// Set the following define one of the options above to +// load different cube maps: +const auto gSelectedOption = options::one_dds_file; + +class texture_cubemap_app : public gvk::invokee +{ + struct data_for_draw_call + { + avk::buffer mPositionsBuffer; + avk::buffer mNormalsBuffer; + avk::buffer mIndexBuffer; + }; + + struct view_projection_matrices { + glm::mat4 mProjectionMatrix; + glm::mat4 mModelViewMatrix; + glm::mat4 mInverseModelViewMatrix; + float mLodBias = 0.0f; + }; + +public: // v== avk::invokee overrides which will be invoked by the framework ==v + texture_cubemap_app(avk::queue& aQueue) + : mQueue{ &aQueue } + , mScale{1.0f, 1.0f, 1.0f} + {} + + void initialize() override + { + // Create a descriptor cache that helps us to conveniently create descriptor sets: + mDescriptorCache = gvk::context().create_descriptor_cache(); + + // Load cube map from file or from cache file: + const std::string cacheFilePath("assets/cubemap.cache"); + auto serializer = gvk::serializer(cacheFilePath); + + // Load a cubemap image file + // The cubemap texture coordinates start in the upper right corner of the skybox faces, + // which coincides with the memory layout of the textures. Therefore we don't need to flip them along the y axis. + // Note that lookup operations in a cubemap are defined in a left-handed coordinate system, + // i.e. when looking at the positive Z face from inside the cube, the positive X face is to the right. + avk::image cubemapImage; + + // Load the textures for all cubemap faces from one file (.ktx or .dds format), or from six individual files + if constexpr (gSelectedOption == options::one_dds_file) { + cubemapImage = gvk::create_cubemap_from_file_cached( + serializer, + "assets/yokohama_at_night-All-Mipmaps-Srgb-RGBA8-DXT1-SRGB.dds", + true, // <-- load in HDR if possible + true, // <-- load in sRGB if applicable + false // <-- flip along the y-axis + ); + } + else if constexpr (gSelectedOption == options::one_ktx_file) { + cubemapImage = gvk::create_cubemap_from_file_cached( + serializer, + "assets/yokohama_at_night-All-Mipmaps-Srgb-RGB8-DXT1-SRGB.ktx", + true, // <-- load in HDR if possible + true, // <-- load in sRGB if applicable + false // <-- flip along the y-axis + ); + } + else if constexpr (gSelectedOption == options::six_jpeg_files) { + cubemapImage = gvk::create_cubemap_from_file_cached( + serializer, + { "assets/posx.jpg", "assets/negx.jpg", "assets/posy.jpg", "assets/negy.jpg", "assets/posz.jpg", "assets/negz.jpg" }, + true, // <-- load in HDR if possible + true, // <-- load in sRGB if applicable + false // <-- flip along the y-axis + ); + } + else { + throw std::logic_error("invalid option selected"); + } + + auto cubemapSampler = gvk::context().create_sampler(avk::filter_mode::trilinear, avk::border_handling_mode::clamp_to_edge, static_cast(cubemapImage->config().mipLevels)); + auto cubemapImageView = gvk::context().create_image_view(avk::owned(cubemapImage), {}, avk::image_usage::general_cube_map_texture); + mImageSamplerCubemap = gvk::context().create_image_sampler(avk::owned(cubemapImageView), avk::owned(cubemapSampler)); + + // Load a cube as the skybox from file + // Since the cubemap uses a left-handed coordinate system, we declare the cube to be defined in the same coordinate system as well. + // This simplifies coordinate transformations later on. To transform the cube vertices back to right-handed world coordinates for display, + // we adjust its model matrix accordingly. Note that this also changes the winding order of faces, i.e. front faces + // of the cube that have CCW order when viewed from the outside now have CCW order when viewed from inside the cube. + { + auto cube = gvk::model_t::load_from_file("assets/cube.obj", aiProcess_Triangulate | aiProcess_PreTransformVertices); + + auto& newElement = mDrawCallsSkybox.emplace_back(); + + // 2. Build all the buffers for the GPU + std::vector indices = { 0 }; + + auto modelMeshSelection = gvk::make_models_and_meshes_selection(cube, indices); + + auto [mPositionsBuffer, mIndexBuffer] = gvk::create_vertex_and_index_buffers({ modelMeshSelection }); + + newElement.mPositionsBuffer = std::move(mPositionsBuffer); + newElement.mIndexBuffer = std::move(mIndexBuffer); + } + + // Load object from file + { + auto object = gvk::model_t::load_from_file("assets/stanford_bunny.obj", aiProcess_Triangulate | aiProcess_PreTransformVertices); + + auto& newElement = mDrawCallsReflect.emplace_back(); + + // 2. Build all the buffers for the GPU + std::vector indices = { 0 }; + + auto modelMeshSelection = gvk::make_models_and_meshes_selection(object, indices); + + auto [mPositionsBuffer, mIndexBuffer] = gvk::create_vertex_and_index_buffers({ modelMeshSelection }); + + newElement.mPositionsBuffer = std::move(mPositionsBuffer); + newElement.mIndexBuffer = std::move(mIndexBuffer); + + newElement.mNormalsBuffer = gvk::create_normals_buffer({ modelMeshSelection }); + } + + mViewProjBufferSkybox = gvk::context().create_buffer( + avk::memory_usage::host_visible, {}, + avk::uniform_buffer_meta::create_from_data(view_projection_matrices()) + ); + + mViewProjBufferReflect = gvk::context().create_buffer( + avk::memory_usage::host_visible, {}, + avk::uniform_buffer_meta::create_from_data(view_projection_matrices()) + ); + + mPipelineSkybox = gvk::context().create_graphics_pipeline_for( + // Specify which shaders the pipeline consists of: + avk::vertex_shader("shaders/skybox.vert"), + avk::fragment_shader("shaders/skybox.frag"), + // The next line defines the format and location of the vertex shader inputs: + // (The dummy values (like glm::vec3) tell the pipeline the format of the respective input) + avk::from_buffer_binding(0)->stream_per_vertex()->to_location(0), // <-- corresponds to vertex shader's inPosition + // Some further settings: + avk::cfg::front_face::define_front_faces_to_be_counter_clockwise(), + avk::cfg::viewport_depth_scissors_config::from_framebuffer(gvk::context().main_window()->backbuffer_at_index(0)), + // We'll render to the back buffer, which has a color attachment always, and in our case additionally a depth + // attachment, which has been configured when creating the window. See main() function! + avk::attachment::declare(gvk::format_from_window_color_buffer(gvk::context().main_window()), avk::on_load::clear, avk::color(0), avk::on_store::store), // But not in presentable format, because ImGui comes after + avk::attachment::declare(gvk::format_from_window_depth_buffer(gvk::context().main_window()), avk::on_load::clear, avk::depth_stencil(), avk::on_store::store), + // The following define additional data which we'll pass to the pipeline: + avk::descriptor_binding(0, 0, mViewProjBufferSkybox), + avk::descriptor_binding(0, 1, mImageSamplerCubemap) + ); + + mPipelineReflect = gvk::context().create_graphics_pipeline_for( + // Specify which shaders the pipeline consists of: + avk::vertex_shader("shaders/reflect.vert"), + avk::fragment_shader("shaders/reflect.frag"), + // The next 2 lines define the format and location of the vertex shader inputs: + // (The dummy values (like glm::vec3) tell the pipeline the format of the respective input) + avk::from_buffer_binding(0)->stream_per_vertex()->to_location(0), // <-- corresponds to vertex shader's inPosition + avk::from_buffer_binding(1)->stream_per_vertex()->to_location(1), // <-- corresponds to vertex shader's inNormal + // Some further settings: + avk::cfg::front_face::define_front_faces_to_be_counter_clockwise(), + avk::cfg::viewport_depth_scissors_config::from_framebuffer(gvk::context().main_window()->backbuffer_at_index(0)), + // We'll render to the back buffer, which has a color attachment always, and in our case additionally a depth + // attachment, which has been configured when creating the window. See main() function! + avk::attachment::declare(gvk::format_from_window_color_buffer(gvk::context().main_window()), avk::on_load::load, avk::color(0), avk::on_store::store), // But not in presentable format, because ImGui comes after + avk::attachment::declare(gvk::format_from_window_depth_buffer(gvk::context().main_window()), avk::on_load::load, avk::depth_stencil(), avk::on_store::dont_care), + // The following define additional data which we'll pass to the pipeline: + avk::descriptor_binding(0, 0, mViewProjBufferReflect), + avk::descriptor_binding(0, 1, mImageSamplerCubemap) + ); + + mUpdater.emplace(); + + // Make pipelines usable with the updater + mPipelineSkybox.enable_shared_ownership(); + mUpdater->on(gvk::shader_files_changed_event(mPipelineSkybox)).update(mPipelineSkybox); + mPipelineReflect.enable_shared_ownership(); + mUpdater->on(gvk::shader_files_changed_event(mPipelineReflect)).update(mPipelineReflect); + + // Add the camera to the composition (and let it handle the updates) + mQuakeCam.set_translation({ 0.0f, 0.0f, 2.5f }); + mQuakeCam.set_perspective_projection(glm::radians(60.0f), gvk::context().main_window()->aspect_ratio(), 0.3f, 1000.0f); + //mQuakeCam.set_orthographic_projection(-5, 5, -5, 5, 0.5, 100); + gvk::current_composition()->add_element(mQuakeCam); + + mUpdater->on(gvk::swapchain_resized_event(gvk::context().main_window())) + .update(mPipelineSkybox, mPipelineReflect) + .invoke([this]() { + this->mQuakeCam.set_aspect_ratio(gvk::context().main_window()->aspect_ratio()); + }); + + auto imguiManager = gvk::current_composition()->element_by_type(); + if(nullptr != imguiManager) { + imguiManager->add_callback([this](){ + ImGui::Begin("Info & Settings"); + ImGui::SetWindowPos(ImVec2(1.0f, 1.0f), ImGuiCond_FirstUseEver); + ImGui::Text("%.3f ms/frame", 1000.0f / ImGui::GetIO().Framerate); + ImGui::Text("%.1f FPS", ImGui::GetIO().Framerate); + ImGui::TextColored(ImVec4(0.f, .6f, .8f, 1.f), "[F1]: Toggle input-mode"); + ImGui::TextColored(ImVec4(0.f, .6f, .8f, 1.f), " (UI vs. scene navigation)"); + ImGui::End(); + }); + } + } + + void update_uniform_buffers() + { + // mirror x axis to transform cubemap coordinates to righ-handed coordinates + auto mirroredViewMatrix = gvk::mirror_matrix(mQuakeCam.view_matrix(), gvk::principal_axis::x); + + view_projection_matrices viewProjMat{ + mQuakeCam.projection_matrix(), + mQuakeCam.view_matrix(), + glm::inverse(mirroredViewMatrix), + 0.0f + }; + + mViewProjBufferReflect->fill(&viewProjMat, 0, avk::sync::not_required()); + + // scale skybox, mirror x axis, cancel out translation + viewProjMat.mModelViewMatrix = gvk::cancel_translation_from_matrix(mirroredViewMatrix * mModelMatrixSkybox); + + mViewProjBufferSkybox->fill(&viewProjMat, 0, avk::sync::not_required()); + } + + void render() override + { + update_uniform_buffers(); + + auto *mainWnd = gvk::context().main_window(); + + auto& commandPool = gvk::context().get_command_pool_for_single_use_command_buffers(*mQueue); + auto cmdbfr = commandPool->alloc_command_buffer(vk::CommandBufferUsageFlagBits::eOneTimeSubmit); + cmdbfr->begin_recording(); + cmdbfr->begin_render_pass_for_framebuffer(mPipelineSkybox->get_renderpass(), gvk::context().main_window()->current_backbuffer()); + + cmdbfr->bind_pipeline(avk::const_referenced(mPipelineSkybox)); + cmdbfr->bind_descriptors(mPipelineSkybox->layout(), mDescriptorCache.get_or_create_descriptor_sets({ + avk::descriptor_binding(0, 0, mViewProjBufferSkybox), + avk::descriptor_binding(0, 1, mImageSamplerCubemap) + })); + + for (auto& drawCall : mDrawCallsSkybox) { + // Make the draw call: + cmdbfr->draw_indexed( + // Bind and use the index buffer: + avk::const_referenced(drawCall.mIndexBuffer), + // Bind the vertex input buffers in the right order (corresponding to the layout specifiers in the vertex shader) + avk::const_referenced(drawCall.mPositionsBuffer) + ); + } + + cmdbfr->bind_pipeline(avk::const_referenced(mPipelineReflect)); + cmdbfr->bind_descriptors(mPipelineReflect->layout(), mDescriptorCache.get_or_create_descriptor_sets({ + avk::descriptor_binding(0, 0, mViewProjBufferReflect), + avk::descriptor_binding(0, 1, mImageSamplerCubemap) + })); + + for (auto& drawCall : mDrawCallsReflect) { + // Make the draw call: + cmdbfr->draw_indexed( + // Bind and use the index buffer: + avk::const_referenced(drawCall.mIndexBuffer), + // Bind the vertex input buffers in the right order (corresponding to the layout specifiers in the vertex shader) + avk::const_referenced(drawCall.mPositionsBuffer), avk::const_referenced(drawCall.mNormalsBuffer) + ); + } + + cmdbfr->end_render_pass(); + cmdbfr->end_recording(); + + // The swap chain provides us with an "image available semaphore" for the current frame. + // Only after the swapchain image has become available, we may start rendering into it. + auto imageAvailableSemaphore = mainWnd->consume_current_image_available_semaphore(); + + // Submit the draw call and take care of the command buffer's lifetime: + mQueue->submit(cmdbfr, imageAvailableSemaphore); + mainWnd->handle_lifetime(avk::owned(cmdbfr)); + } + + void update() override + { + if (gvk::input().key_pressed(gvk::key_code::escape)) { + // Stop the current composition: + gvk::current_composition()->stop(); + } + if (gvk::input().key_pressed(gvk::key_code::f1)) { + auto *imguiManager = gvk::current_composition()->element_by_type(); + if (mQuakeCam.is_enabled()) { + mQuakeCam.disable(); + if (nullptr != imguiManager) { imguiManager->enable_user_interaction(true); } + } + else { + mQuakeCam.enable(); + if (nullptr != imguiManager) { imguiManager->enable_user_interaction(false); } + } + } + } + +private: // v== Member variables ==v + + avk::queue* mQueue; + avk::descriptor_cache mDescriptorCache; + + avk::buffer mViewProjBufferSkybox; + avk::buffer mViewProjBufferReflect; + + avk::image_sampler mImageSamplerCubemap; + + std::vector mDrawCallsSkybox; + avk::graphics_pipeline mPipelineSkybox; + + std::vector mDrawCallsReflect; + avk::graphics_pipeline mPipelineReflect; + + gvk::quake_camera mQuakeCam; + + glm::vec3 mScale; + + const float mScaleSkybox = 100.f; + const glm::mat4 mModelMatrixSkybox = glm::scale(glm::vec3(mScaleSkybox)); +}; +// texture_cubemap_app + +int main() // <== Starting point == +{ + try { + // Create a window and open it + auto* mainWnd = gvk::context().create_window("Texture Cubemap"); + mainWnd->set_resolution({ 640, 480 }); + mainWnd->request_srgb_framebuffer(true); + mainWnd->enable_resizing(true); + mainWnd->set_additional_back_buffer_attachments({ + avk::attachment::declare(vk::Format::eD32Sfloat, avk::on_load::clear, avk::depth_stencil(), avk::on_store::dont_care) + }); + mainWnd->set_presentaton_mode(gvk::presentation_mode::mailbox); + mainWnd->set_number_of_concurrent_frames(3u); + mainWnd->open(); + + auto& singleQueue = gvk::context().create_queue({}, avk::queue_selection_preference::versatile_queue, mainWnd); + mainWnd->add_queue_family_ownership(singleQueue); + mainWnd->set_present_queue(singleQueue); + + // Create an instance of our main avk::element which contains all the functionality: + auto app = texture_cubemap_app(singleQueue); + // Create another element for drawing the UI with ImGui + auto ui = gvk::imgui_manager(singleQueue); + + // GO: + gvk::start( + gvk::application_name("Gears-Vk + Auto-Vk Example: Texture Cubemap"), + mainWnd, + app, + ui + ); + } + catch (gvk::logic_error&) {} + catch (gvk::runtime_error&) {} + catch (avk::logic_error&) {} + catch (avk::runtime_error&) {} +} diff --git a/examples/texture_cubemap/texture_cubemap.md b/examples/texture_cubemap/texture_cubemap.md new file mode 100644 index 000000000..d3bc33bfb --- /dev/null +++ b/examples/texture_cubemap/texture_cubemap.md @@ -0,0 +1,4 @@ +# "Texture Cubemap" Example's Root Folder + +This is the root directory of the "Texture Cubemap" example. It contains all the source code for the example. + diff --git a/framework/include/gvk.hpp b/framework/include/gvk.hpp index 3ddd84a56..152477a7a 100644 --- a/framework/include/gvk.hpp +++ b/framework/include/gvk.hpp @@ -200,6 +200,7 @@ namespace gvk { #include "model.hpp" #include "orca_scene.hpp" #include "serializer.hpp" +#include "image_data.hpp" #include "material_image_helpers.hpp" #include "composition.hpp" diff --git a/framework/include/image_data.hpp b/framework/include/image_data.hpp new file mode 100644 index 000000000..1ae63219a --- /dev/null +++ b/framework/include/image_data.hpp @@ -0,0 +1,281 @@ +#pragma once +#include + +namespace gvk +{ + class image_data; + class image_data_implementor; + + + // interface of image_data type, used for abstraction and implementor in bridge pattern + class image_data_interface + { + public: + // type that represents the size of 1D, 2D, and 3D images + typedef vk::Extent3D extent_type; + + virtual ~image_data_interface() = default; + image_data_interface(image_data_interface&&) noexcept = default; + image_data_interface(const image_data_interface&) = delete; + image_data_interface& operator=(image_data_interface&&) noexcept = default; + image_data_interface& operator=(const image_data_interface&) = delete; + + // load image resource into memory + // perform this as an extra step to facilitate caching of image resources + virtual void load() = 0; + + virtual vk::Format get_format() const = 0; + + virtual vk::ImageType target() const = 0; + + // size of image resource, in pixels + virtual extent_type extent(const uint32_t level = 0) const = 0; + + // Note: the return type cannot be const void* because this would result in a compile-time error with the deserializer; + // The function cannot be const either or gli would call a function overload that returns a const void* + virtual void* get_data(const uint32_t layer, const uint32_t face, const uint32_t level) = 0; + // size of whole image resource, in bytes + virtual size_t size() const = 0; + // size of one mipmap level of image resource, in bytes + virtual size_t size(const uint32_t level) const = 0; + + virtual bool empty() const = 0; + + // Vulkan uses uint32_t type for levels and layers (faces) + virtual uint32_t levels() const = 0; + + // array layers, for texture arrays + virtual uint32_t layers() const = 0; + + // faces in image, must be 6 for cubemaps, 1 for anything else + virtual uint32_t faces() const = 0; + + // returns true if image format and image library support y-flipping when loading + // TODO: make protected or remove? + virtual bool can_flip() const = 0; + + // returns if image resource is a hdr format + virtual bool is_hdr() const = 0; + + std::string path() const + { + return mPaths[0]; + } + + std::vector paths() const + { + return mPaths; + } + + protected: + + image_data_interface(const std::string& aPath, const bool aLoadHdrIfPossible = false, const bool aLoadSrgbIfApplicable = false, const bool aFlip = false, const int aPreferredNumberOfTextureComponents = 4) + : mPaths({ aPath }), mLoadHdrIfPossible(aLoadHdrIfPossible), mLoadSrgbIfApplicable(aLoadSrgbIfApplicable), mFlip(aFlip), mPreferredNumberOfTextureComponents(aPreferredNumberOfTextureComponents) + { + } + + // for cubemaps loaded from six individual images + image_data_interface(const std::vector& aPaths, const bool aLoadHdrIfPossible = false, const bool aLoadSrgbIfApplicable = false, const bool aFlip = false, const int aPreferredNumberOfTextureComponents = 4) + : mPaths(aPaths), mLoadHdrIfPossible(aLoadHdrIfPossible), mLoadSrgbIfApplicable(aLoadSrgbIfApplicable), mFlip(aFlip), mPreferredNumberOfTextureComponents(aPreferredNumberOfTextureComponents) + { + } + + // Note that boolean flags are not applicable in all cases; stb_image converts image data to or from float format, but other image loading libraries, like GLI, do not; + // Likewise, sRGB is ignored for GLI loader, and it may not be possible to flip images on loading (e.g. compressed textures) + static std::unique_ptr load_image_data_from_file(const std::string& aPath, const bool aLoadHdrIfPossible = true, const bool aLoadSrgbIfApplicable = true, const bool aFlip = true, const int aPreferredNumberOfTextureComponents = 4); + + // for cubemaps loaded from six individual files + // Order of faces +X, -X, +Y, -Y, +Z, -Z + static std::unique_ptr load_image_data_from_file(const std::vector& aPaths, const bool aLoadHdrIfPossible = true, const bool aLoadSrgbIfApplicable = true, const bool aFlip = true, const int aPreferredNumberOfTextureComponents = 4); + + std::vector mPaths; + + bool mLoadHdrIfPossible; + bool mLoadSrgbIfApplicable; + // if image should be flipped vertically when loaded, if possible + bool mFlip; + int mPreferredNumberOfTextureComponents; + }; + + // base class of implementor of image_data type in bridge pattern + class image_data_implementor : public image_data_interface + { + public: + // Default implementations for subclasses that don't support these optional features + + // Mipmap levels; 1 if no Mipmapping, 0 if Mipmaps should be created after loading + virtual uint32_t levels() const + { + return 1; + } + + // array layers, for texture arrays + virtual uint32_t layers() const + { + return 1; + } + + // faces in cubemap, must be 6 for cubemaps, 1 for anything else + virtual uint32_t faces() const + { + return 1; + } + + // TODO: make protected or remove? + virtual bool can_flip() const + { + return false; + } + + virtual bool is_hdr() const + { + return false; + } + + protected: + explicit image_data_implementor(const std::string& aPath, const bool aLoadHdrIfPossible = false, const bool aLoadSrgbIfApplicable = false, const bool aFlip = false, const int aPreferredNumberOfTextureComponents = 4) + : image_data_interface(aPath, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents) + { + } + + explicit image_data_implementor(const std::vector& aPaths, const bool aLoadHdrIfPossible = false, const bool aLoadSrgbIfApplicable = false, const bool aFlip = false, const int aPreferredNumberOfTextureComponents = 4) + : image_data_interface(aPaths, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents) + { + } + }; + + // base class of abstraction in bridge pattern + class image_data : public image_data_interface + { + public: + explicit image_data(const std::string& aPath, const bool aLoadHdrIfPossible = false, const bool aLoadSrgbIfApplicable = false, const bool aFlip = false, const int aPreferredNumberOfTextureComponents = 4) + : image_data_interface(aPath, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents), pimpl(nullptr) + { + } + + explicit image_data(const std::vector& aPaths, const bool aLoadHdrIfPossible = false, const bool aLoadSrgbIfApplicable = false, const bool aFlip = false, const int aPreferredNumberOfTextureComponents = 4) + : image_data_interface(aPaths, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents), pimpl(nullptr) + { + } + + virtual void load() + { + if (!empty()) + { + return; + } + + assert(mPaths.size() == 1 || mPaths.size() == 6); + + if (mPaths.size() == 1) + { + pimpl = load_image_data_from_file(mPaths[0], mLoadHdrIfPossible, mLoadSrgbIfApplicable, mFlip, mPreferredNumberOfTextureComponents); + } + else + { + pimpl = load_image_data_from_file(mPaths, mLoadHdrIfPossible, mLoadSrgbIfApplicable, mFlip, mPreferredNumberOfTextureComponents); + } + + pimpl->load(); + } + + virtual vk::Format get_format() const + { + assert(!empty()); + + return pimpl->get_format(); + } + + virtual vk::ImageType target() const + { + assert(!empty()); + + return pimpl->target(); + } + + virtual extent_type extent(const uint32_t level = 0) const + { + assert(!empty()); + assert(level < pimpl->levels()); + + return pimpl->extent(level); + } + + virtual void* get_data(const uint32_t layer, const uint32_t face, const uint32_t level) + { + assert(!empty()); + assert(layer < pimpl->layers()); + assert(face < pimpl->faces()); + assert(level < pimpl->levels()); + + return pimpl->get_data(layer, face, level); + } + + virtual size_t size() const + { + assert(!empty()); + + return pimpl->size(); + } + + virtual size_t size(const uint32_t level) const + { + assert(!empty()); + assert(level < pimpl->levels()); + + return pimpl->size(level); + } + + virtual uint32_t levels() const + { + assert(!empty()); + + return pimpl->levels(); + } + + // array layers, for texture arrays + virtual uint32_t layers() const + { + assert(!empty()); + + return pimpl->layers(); + } + + // faces in image, must be 6 for cubemaps, 1 for anything else + virtual uint32_t faces() const + { + assert(!empty()); + + return pimpl->faces(); + } + + // TODO: make protected or remove? + virtual bool can_flip() const + { + assert(!empty()); + + return pimpl->can_flip(); + } + + virtual bool is_hdr() const + { + assert(!empty()); + + return pimpl->is_hdr(); + } + + virtual bool empty() const + { + return !(pimpl && !pimpl->empty()); + } + + private: + // pimpl idiom: use unique_ptr + // allocate pimpl in out-of-line constructor + // deallocate in out-of-line destructor (complete type only known after class definition) + // for user-defined destructor, there is no compiler-generated copy constructor and move-assignment operator; define out-of-line if needed + + // pointer-to-implementation + std::unique_ptr pimpl; + }; +} diff --git a/framework/include/material_image_helpers.hpp b/framework/include/material_image_helpers.hpp index c04510b5c..37dc9e875 100644 --- a/framework/include/material_image_helpers.hpp +++ b/framework/include/material_image_helpers.hpp @@ -71,415 +71,92 @@ namespace gvk return create_1px_texture_cached(aColor, aFormat, aMemoryUsage, aImageUsage, std::move(aSyncHandler), aSerializer); } - static avk::image create_image_from_file_cached(const std::string& aPath, vk::Format aFormat, bool aFlip = true, avk::memory_usage aMemoryUsage = avk::memory_usage::device, avk::image_usage aImageUsage = avk::image_usage::general_texture, avk::sync aSyncHandler = avk::sync::wait_idle(), std::optional aAlreadyLoadedGliTexture = {}, std::optional> aSerializer = {}) + // create image_data from texture files + static image_data get_image_data(const std::string& aPath, bool aLoadHdrIfPossible = true, bool aLoadSrgbIfApplicable = true, bool aFlip = true, + int aPreferredNumberOfTextureComponents = 4) { - std::vector stagingBuffers; - int width = 0; - int height = 0; - - // ============ Compressed formats (DDS) ========== - if (avk::is_block_compressed_format(aFormat)) { - size_t texSize = 0; - void* texData = nullptr; - if (!aSerializer || - (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::serialize)) { - if (!aAlreadyLoadedGliTexture.has_value()) { - aAlreadyLoadedGliTexture = gli::load(aPath); - } - auto& gliTex = aAlreadyLoadedGliTexture.value(); - - if (gliTex.target() != gli::TARGET_2D) { - throw gvk::runtime_error(fmt::format("The image '{}' is not intended to be used as 2D image. Can't load it.", aPath)); - } - - texSize = gliTex.size(); - texData = gliTex.data(); - - width = gliTex.extent()[0]; - height = gliTex.extent()[1]; - } - - if (aSerializer) { - aSerializer->get().archive(texSize); - aSerializer->get().archive(width); - aSerializer->get().archive(height); - } - - auto& sb = stagingBuffers.emplace_back(context().create_buffer( - AVK_STAGING_BUFFER_MEMORY_USAGE, - vk::BufferUsageFlagBits::eTransferSrc, - avk::generic_buffer_meta::create_from_size(texSize) - )); - - if (!aSerializer) { - sb->fill(texData, 0, avk::sync::not_required()); - } - else if (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::serialize) { - sb->fill(texData, 0, avk::sync::not_required()); - aSerializer->get().archive_memory(texData, texSize); - } - else if (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::deserialize) { - aSerializer->get().archive_buffer(sb); - } - } - // ============ RGB 8-bit formats ========== - else if (avk::is_uint8_format(aFormat) || avk::is_int8_format(aFormat)) { - size_t imageSize = 0; - stbi_uc* pixels = nullptr; - if (!aSerializer || - (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::serialize)) { - stbi_set_flip_vertically_on_load(aFlip); - int desiredColorChannels = STBI_rgb_alpha; - - if (!avk::is_4component_format(aFormat)) { - if (avk::is_3component_format(aFormat)) { - desiredColorChannels = STBI_rgb; - } - else if (avk::is_2component_format(aFormat)) { - desiredColorChannels = STBI_grey_alpha; - } - else if (avk::is_1component_format(aFormat)) { - desiredColorChannels = STBI_grey; - } - } - - int channelsInFile = 0; - pixels = stbi_load(aPath.c_str(), &width, &height, &channelsInFile, desiredColorChannels); - imageSize = static_cast(width) * static_cast(height) * static_cast(desiredColorChannels); - - if (!pixels) { - throw gvk::runtime_error(fmt::format("Couldn't load image from '{}' using stbi_load", aPath)); - } - } - - if (aSerializer) { - aSerializer->get().archive(imageSize); - aSerializer->get().archive(width); - aSerializer->get().archive(height); - } - - auto& sb = stagingBuffers.emplace_back(context().create_buffer( - AVK_STAGING_BUFFER_MEMORY_USAGE, - vk::BufferUsageFlagBits::eTransferSrc, - avk::generic_buffer_meta::create_from_size(imageSize) - )); - - if (!aSerializer) { - sb->fill(pixels, 0, avk::sync::not_required()); - } - else if (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::serialize) { - sb->fill(pixels, 0, avk::sync::not_required()); - aSerializer->get().archive_memory(pixels, imageSize); - } - else if (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::deserialize) { - aSerializer->get().archive_buffer(sb); - } - } - // ============ RGB 16-bit float formats (HDR) ========== - else if (avk::is_float16_format(aFormat)) { - size_t imageSize = 0; - float* pixels = nullptr; - if (!aSerializer || - (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::serialize)) { - stbi_set_flip_vertically_on_load(true); - int desiredColorChannels = STBI_rgb_alpha; - - if (!avk::is_4component_format(aFormat)) { - if (avk::is_3component_format(aFormat)) { - desiredColorChannels = STBI_rgb; - } - else if (avk::is_2component_format(aFormat)) { - desiredColorChannels = STBI_grey_alpha; - } - else if (avk::is_1component_format(aFormat)) { - desiredColorChannels = STBI_grey; - } - } - - int channelsInFile = 0; - pixels = stbi_loadf(aPath.c_str(), &width, &height, &channelsInFile, desiredColorChannels); - imageSize = static_cast(width) * static_cast(height) * static_cast(desiredColorChannels); - - if (!pixels) { - throw gvk::runtime_error(fmt::format("Couldn't load image from '{}' using stbi_loadf", aPath)); - } - } - - if (aSerializer) { - aSerializer->get().archive(imageSize); - aSerializer->get().archive(width); - aSerializer->get().archive(height); - } + image_data result(aPath, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents); - auto& sb = stagingBuffers.emplace_back(context().create_buffer( - AVK_STAGING_BUFFER_MEMORY_USAGE, - vk::BufferUsageFlagBits::eTransferSrc, - avk::generic_buffer_meta::create_from_size(imageSize) - )); - - if (!aSerializer) { - sb->fill(pixels, 0, avk::sync::not_required()); - } - else if (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::serialize) { - sb->fill(pixels, 0, avk::sync::not_required()); - aSerializer->get().archive_memory(pixels, imageSize); - } - else if (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::deserialize) { - aSerializer->get().archive_buffer(sb); - } - } - else { - throw gvk::runtime_error("No loader for the given image format implemented."); - } - - auto& commandBuffer = aSyncHandler.get_or_create_command_buffer(); - aSyncHandler.establish_barrier_before_the_operation(avk::pipeline_stage::transfer, avk::read_memory_access{avk::memory_access::transfer_read_access}); - - auto img = context().create_image(width, height, aFormat, 1, aMemoryUsage, aImageUsage); - auto finalTargetLayout = img->target_layout(); // save for later, because first, we need to transfer something into it - - // 1. Transition image layout to eTransferDstOptimal - img->transition_to_layout(vk::ImageLayout::eTransferDstOptimal, avk::sync::auxiliary_with_barriers(aSyncHandler, {}, {})); // no need for additional sync - // TODO: The original implementation transitioned into cgb::image_format(_Format) format here, not to eTransferDstOptimal => Does it still work? If so, eTransferDstOptimal is fine. - - // 2. Copy buffer to image - assert(stagingBuffers.size() == 1); - avk::copy_buffer_to_image(avk::const_referenced(stagingBuffers.front()), avk::referenced(img), {}, avk::sync::auxiliary_with_barriers(aSyncHandler, {}, {})); // There should be no need to make any memory available or visible, the transfer-execution dependency chain should be fine - // TODO: Verify the above ^ comment - // Are MIP-maps required? - if (img->config().mipLevels > 1u) { - if (avk::is_block_compressed_format(aFormat)) { - size_t levels = 0; - if (!aSerializer || - (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::serialize)) { - assert(aAlreadyLoadedGliTexture.has_value()); - levels = aAlreadyLoadedGliTexture.value().levels(); - } - if (aSerializer) { - aSerializer->get().archive(levels); - } - // 1st level is contained in stagingBuffer - // - // Now let's load further levels from the GliTexture and upload them directly into the sub-levels - - // TODO: Do we have to account for gliTex.base_level() and gliTex.max_level()? - for(size_t level = 1; level < levels; ++level) - { - size_t texSize = 0; - void* texData = nullptr; - glm::tvec3 levelExtent; - - if (!aSerializer || - (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::serialize)) { - texSize = aAlreadyLoadedGliTexture.value().size(level); - texData = aAlreadyLoadedGliTexture.value().data(0, 0, level); - auto& gliTex = aAlreadyLoadedGliTexture.value(); - levelExtent = gliTex.extent(level); - } - if (aSerializer) { - aSerializer->get().archive(texSize); - aSerializer->get().archive(levelExtent); - } -#if _DEBUG - { - auto imgExtent = img->config().extent; - auto levelDivisor = std::pow(2u, level); - imgExtent.width = imgExtent.width > 1u ? imgExtent.width / levelDivisor : 1u; - imgExtent.height = imgExtent.height > 1u ? imgExtent.height / levelDivisor : 1u; - imgExtent.depth = imgExtent.depth > 1u ? imgExtent.depth / levelDivisor : 1u; - assert (levelExtent.x == static_cast(imgExtent.width )); - assert (levelExtent.y == static_cast(imgExtent.height)); - assert (levelExtent.z == static_cast(imgExtent.depth )); - } -#endif - - auto& sb = stagingBuffers.emplace_back(context().create_buffer( - AVK_STAGING_BUFFER_MEMORY_USAGE, - vk::BufferUsageFlagBits::eTransferSrc, - avk::generic_buffer_meta::create_from_size(texSize) - )); - - if (!aSerializer) { - sb->fill(texData, 0, avk::sync::not_required()); - } - else if (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::serialize) { - sb->fill(texData, 0, avk::sync::not_required()); - aSerializer->get().archive_memory(texData, texSize); - } - else if (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::deserialize) { - aSerializer->get().archive_buffer(sb); - } + return result; + } - // Memory writes are not overlapping => no barriers should be fine. - avk::copy_buffer_to_image_mip_level(avk::const_referenced(sb), avk::referenced(img), level, {}, avk::sync::auxiliary_with_barriers(aSyncHandler, {}, {})); - } - } - else { - // For uncompressed formats, create MIP-maps via BLIT: - img->generate_mip_maps(avk::sync::auxiliary_with_barriers(aSyncHandler, {}, {})); - } - } + static image_data get_image_data(const std::vector& aPaths, bool aLoadHdrIfPossible = true, bool aLoadSrgbIfApplicable = true, bool aFlip = true, + int aPreferredNumberOfTextureComponents = 4) + { + image_data result(aPaths, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents); - commandBuffer.set_custom_deleter([lOwnedStagingBuffers = std::move(stagingBuffers)](){}); + return result; + } - // 3. Transition image layout to its target layout and handle lifetime of things via sync - img->transition_to_layout(finalTargetLayout, avk::sync::auxiliary_with_barriers(aSyncHandler, {}, {})); + // create cubemap from image data + avk::image create_cubemap_from_image_data_cached(image_data& aImageData, avk::memory_usage aMemoryUsage = avk::memory_usage::device, + avk::image_usage aImageUsage = avk::image_usage::general_cube_map_texture, avk::sync aSyncHandler = avk::sync::wait_idle(), std::optional> aSerializer = {}); - aSyncHandler.establish_barrier_after_the_operation(avk::pipeline_stage::transfer, avk::write_memory_access{ avk::memory_access::transfer_write_access }); - auto result = aSyncHandler.submit_and_sync(); - assert(!result.has_value()); - return img; + static avk::image create_cubemap_from_image_data_cached(gvk::serializer& aSerializer, image_data& aImageData, avk::memory_usage aMemoryUsage = avk::memory_usage::device, + avk::image_usage aImageUsage = avk::image_usage::general_cube_map_texture, avk::sync aSyncHandler = avk::sync::wait_idle()) + { + return create_cubemap_from_image_data_cached(aImageData, aMemoryUsage, aImageUsage, std::move(aSyncHandler), aSerializer); } - static avk::image create_image_from_file_cached(gvk::serializer& aSerializer,const std::string& aPath, vk::Format aFormat, bool aFlip = true, avk::memory_usage aMemoryUsage = avk::memory_usage::device, avk::image_usage aImageUsage = avk::image_usage::general_texture, avk::sync aSyncHandler = avk::sync::wait_idle(), std::optional aAlreadyLoadedGliTexture = {}) + static avk::image create_cubemap_from_image_data(image_data& aImageData, avk::memory_usage aMemoryUsage = avk::memory_usage::device, + avk::image_usage aImageUsage = avk::image_usage::general_cube_map_texture, avk::sync aSyncHandler = avk::sync::wait_idle()) { - return create_image_from_file_cached(aPath, aFormat, aFlip, aMemoryUsage, aImageUsage, std::move(aSyncHandler), std::move(aAlreadyLoadedGliTexture), aSerializer); + return create_cubemap_from_image_data_cached(aImageData, aMemoryUsage, aImageUsage, std::move(aSyncHandler)); } - static avk::image create_image_from_file(const std::string& aPath, vk::Format aFormat, bool aFlip = true, avk::memory_usage aMemoryUsage = avk::memory_usage::device, avk::image_usage aImageUsage = avk::image_usage::general_texture, avk::sync aSyncHandler = avk::sync::wait_idle(), std::optional aAlreadyLoadedGliTexture = {}) + // create cubemap from a single file + avk::image create_cubemap_from_file_cached(const std::string& aPath, bool aLoadHdrIfPossible = true, bool aLoadSrgbIfApplicable = true, bool aFlip = true, + int aPreferredNumberOfTextureComponents = 4, avk::memory_usage aMemoryUsage = avk::memory_usage::device, + avk::image_usage aImageUsage = avk::image_usage::general_cube_map_texture, avk::sync aSyncHandler = avk::sync::wait_idle(), std::optional> aSerializer = {}); + + static avk::image create_cubemap_from_file_cached(gvk::serializer& aSerializer, const std::string& aPath, bool aLoadHdrIfPossible = true, bool aLoadSrgbIfApplicable = true, bool aFlip = true, + int aPreferredNumberOfTextureComponents = 4, avk::memory_usage aMemoryUsage = avk::memory_usage::device, + avk::image_usage aImageUsage = avk::image_usage::general_cube_map_texture, avk::sync aSyncHandler = avk::sync::wait_idle()) { - return create_image_from_file_cached(aPath, aFormat, aFlip, aMemoryUsage, aImageUsage, std::move(aSyncHandler), std::move(aAlreadyLoadedGliTexture)); + return create_cubemap_from_file_cached(aPath, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents, aMemoryUsage, aImageUsage, std::move(aSyncHandler), aSerializer); } - static avk::image create_image_from_file_cached(const std::string& aPath, bool aLoadHdrIfPossible = true, bool aLoadSrgbIfApplicable = true, bool aFlip = true, int aPreferredNumberOfTextureComponents = 4, avk::memory_usage aMemoryUsage = avk::memory_usage::device, avk::image_usage aImageUsage = avk::image_usage::general_texture, avk::sync aSyncHandler = avk::sync::wait_idle(), std::optional> aSerializer = {}) + static avk::image create_cubemap_from_file(const std::string& aPath, bool aLoadHdrIfPossible = true, bool aLoadSrgbIfApplicable = true, bool aFlip = true, + int aPreferredNumberOfTextureComponents = 4, avk::memory_usage aMemoryUsage = avk::memory_usage::device, + avk::image_usage aImageUsage = avk::image_usage::general_cube_map_texture, avk::sync aSyncHandler = avk::sync::wait_idle()) { - std::optional imFmt = {}; - - std::optional gliTex = {}; - if (!aSerializer || - (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::serialize)) { - gliTex = gli::load(aPath); - if (!gliTex.value().empty()) { - - if (aFlip && (!gli::is_compressed(gliTex.value().format()) || gli::is_s3tc_compressed(gliTex.value().format()))) { - gliTex = gli::flip(gliTex.value()); - } - - auto gliFmt = gliTex.value().format(); - switch (gliFmt) { - // See "Khronos Data Format Specification": https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#S3TC - // And Vulkan specification: https://www.khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap42.html#appendix-compressedtex-bc - case gli::format::FORMAT_RGB_DXT1_UNORM_BLOCK8: - imFmt = vk::Format::eBc1RgbUnormBlock; - break; - case gli::format::FORMAT_RGB_DXT1_SRGB_BLOCK8: - imFmt = vk::Format::eBc1RgbSrgbBlock; - break; - case gli::format::FORMAT_RGBA_DXT1_UNORM_BLOCK8: - imFmt = vk::Format::eBc1RgbaUnormBlock; - break; - case gli::format::FORMAT_RGBA_DXT1_SRGB_BLOCK8: - imFmt = vk::Format::eBc1RgbaSrgbBlock; - break; - case gli::format::FORMAT_RGBA_DXT3_UNORM_BLOCK16: - imFmt = vk::Format::eBc2UnormBlock; - break; - case gli::format::FORMAT_RGBA_DXT3_SRGB_BLOCK16: - imFmt = vk::Format::eBc2SrgbBlock; - break; - case gli::format::FORMAT_RGBA_DXT5_UNORM_BLOCK16: - imFmt = vk::Format::eBc3UnormBlock; - break; - case gli::format::FORMAT_RGBA_DXT5_SRGB_BLOCK16: - imFmt = vk::Format::eBc3SrgbBlock; - break; - case gli::format::FORMAT_R_ATI1N_UNORM_BLOCK8: - imFmt = vk::Format::eBc4UnormBlock; - break; - // See "Khronos Data Format Specification": https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#RGTC - // And Vulkan specification: https://www.khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap42.html#appendix-compressedtex-bc - case gli::format::FORMAT_R_ATI1N_SNORM_BLOCK8: - imFmt = vk::Format::eBc4SnormBlock; - break; - case gli::format::FORMAT_RG_ATI2N_UNORM_BLOCK16: - imFmt = vk::Format::eBc5UnormBlock; - break; - case gli::format::FORMAT_RG_ATI2N_SNORM_BLOCK16: - imFmt = vk::Format::eBc5SnormBlock; - } - } - else { - gliTex.reset(); - } - - if (!imFmt.has_value() && aLoadHdrIfPossible) { - if (stbi_is_hdr(aPath.c_str())) { - switch (aPreferredNumberOfTextureComponents) { - case 4: - imFmt = default_rgb16f_4comp_format(); - break; - // Attention: There's a high likelihood that your GPU does not support formats with less than four color components! - case 3: - imFmt = default_rgb16f_3comp_format(); - break; - case 2: - imFmt = default_rgb16f_2comp_format(); - break; - case 1: - imFmt = default_rgb16f_1comp_format(); - break; - default: - imFmt = default_rgb16f_4comp_format(); - break; - } - } - } + return create_cubemap_from_file_cached(aPath, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents, aMemoryUsage, aImageUsage, std::move(aSyncHandler)); + } - if (!imFmt.has_value() && aLoadSrgbIfApplicable) { - switch (aPreferredNumberOfTextureComponents) { - case 4: - imFmt = gvk::default_srgb_4comp_format(); - break; - // Attention: There's a high likelihood that your GPU does not support formats with less than four color components! - case 3: - imFmt = gvk::default_srgb_3comp_format(); - break; - case 2: - imFmt = gvk::default_srgb_2comp_format(); - break; - case 1: - imFmt = gvk::default_srgb_1comp_format(); - break; - default: - imFmt = gvk::default_srgb_4comp_format(); - break; - } - } + // create cubemap from six individual files + avk::image create_cubemap_from_file_cached(const std::vector& aPaths, bool aLoadHdrIfPossible = true, bool aLoadSrgbIfApplicable = true, bool aFlip = true, + int aPreferredNumberOfTextureComponents = 4, avk::memory_usage aMemoryUsage = avk::memory_usage::device, + avk::image_usage aImageUsage = avk::image_usage::general_cube_map_texture, avk::sync aSyncHandler = avk::sync::wait_idle(), std::optional> aSerializer = {}); - if (!imFmt.has_value()) { - switch (aPreferredNumberOfTextureComponents) { - case 4: - imFmt = gvk::default_rgb8_4comp_format(); - break; - // Attention: There's a high likelihood that your GPU does not support formats with less than four color components! - case 3: - imFmt = gvk::default_rgb8_3comp_format(); - break; - case 2: - imFmt = gvk::default_rgb8_2comp_format(); - break; - case 1: - imFmt = gvk::default_rgb8_1comp_format(); - break; - default: - imFmt = gvk::default_rgb8_4comp_format(); - break; - } - } + static avk::image create_cubemap_from_file_cached(gvk::serializer& aSerializer, const std::vector& aPaths, bool aLoadHdrIfPossible = true, bool aLoadSrgbIfApplicable = true, bool aFlip = true, int aPreferredNumberOfTextureComponents = 4, avk::memory_usage aMemoryUsage = avk::memory_usage::device, avk::image_usage aImageUsage = avk::image_usage::general_cube_map_texture, avk::sync aSyncHandler = avk::sync::wait_idle()) + { + return create_cubemap_from_file_cached(aPaths, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents, aMemoryUsage, aImageUsage, std::move(aSyncHandler), aSerializer); + } - } + static avk::image create_cubemap_from_file(const std::vector& aPaths, bool aLoadHdrIfPossible = true, bool aLoadSrgbIfApplicable = true, bool aFlip = true, int aPreferredNumberOfTextureComponents = 4, avk::memory_usage aMemoryUsage = avk::memory_usage::device, avk::image_usage aImageUsage = avk::image_usage::general_cube_map_texture, avk::sync aSyncHandler = avk::sync::wait_idle()) + { + return create_cubemap_from_file_cached(aPaths, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents, aMemoryUsage, aImageUsage, std::move(aSyncHandler)); + } - if (aSerializer) { - aSerializer->get().archive(imFmt); - } + // create image from image data + avk::image create_image_from_image_data_cached(image_data& aImageData, avk::memory_usage aMemoryUsage = avk::memory_usage::device, + avk::image_usage aImageUsage = avk::image_usage::general_texture, avk::sync aSyncHandler = avk::sync::wait_idle(), std::optional> aSerializer = {}); - if (!imFmt.has_value()) { - throw gvk::runtime_error(fmt::format("Could not determine the image format of image '{}'", aPath)); - } + static avk::image create_image_from_image_data_cached(gvk::serializer& aSerializer, image_data& aImageData, avk::memory_usage aMemoryUsage = avk::memory_usage::device, + avk::image_usage aImageUsage = avk::image_usage::general_texture, avk::sync aSyncHandler = avk::sync::wait_idle()) + { + return create_image_from_image_data_cached(aImageData, aMemoryUsage, aImageUsage, std::move(aSyncHandler), aSerializer); + } - return create_image_from_file_cached(aPath, imFmt.value(), aFlip, aMemoryUsage, aImageUsage, std::move(aSyncHandler), std::move(gliTex), aSerializer); + static avk::image create_image_from_image_data(image_data& aImageData, avk::memory_usage aMemoryUsage = avk::memory_usage::device, + avk::image_usage aImageUsage = avk::image_usage::general_texture, avk::sync aSyncHandler = avk::sync::wait_idle()) + { + return create_image_from_image_data_cached(aImageData, aMemoryUsage, aImageUsage, std::move(aSyncHandler)); } + // create image from a file + avk::image create_image_from_file_cached(const std::string& aPath, bool aLoadHdrIfPossible = true, bool aLoadSrgbIfApplicable = true, bool aFlip = true, int aPreferredNumberOfTextureComponents = 4, avk::memory_usage aMemoryUsage = avk::memory_usage::device, avk::image_usage aImageUsage = avk::image_usage::general_texture, avk::sync aSyncHandler = avk::sync::wait_idle(), std::optional> aSerializer = {}); + static avk::image create_image_from_file_cached(gvk::serializer& aSerializer, const std::string& aPath, bool aLoadHdrIfPossible = true, bool aLoadSrgbIfApplicable = true, bool aFlip = true, int aPreferredNumberOfTextureComponents = 4, avk::memory_usage aMemoryUsage = avk::memory_usage::device, avk::image_usage aImageUsage = avk::image_usage::general_texture, avk::sync aSyncHandler = avk::sync::wait_idle()) { return create_image_from_file_cached(aPath, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents, aMemoryUsage, aImageUsage, std::move(aSyncHandler), aSerializer); diff --git a/framework/include/math_utils.hpp b/framework/include/math_utils.hpp index a4d06b556..203594f63 100644 --- a/framework/include/math_utils.hpp +++ b/framework/include/math_utils.hpp @@ -12,6 +12,23 @@ namespace gvk /// extract the translation part out of a matrix extern glm::vec3 get_translation_from_matrix(const glm::mat4& m); + /** Returns a new matrix which is the same as the input matrix, but + * the translation part of the matrix is canceled-out, i.e. set to + * zero for each direction, x, y, and z. + */ + glm::mat4 cancel_translation_from_matrix(const glm::mat4& aMatrix); + + /** An enum struct to state the principal axis or direction, which can be x, y, or z. */ + enum struct principal_axis : uint32_t { x = 0u, y, z }; + + /** Returns a new matrix based on the given input matrix, mirrored + * along the given axis direction. + * @param aMatrix The input matrix, a modified version of which is to be returned + * @param aAxis The axis which to mirror + * @return A new matrix which has the given axis mirrored compared to the input matrix + */ + glm::mat4 mirror_matrix(const glm::mat4& aMatrix, principal_axis aAxis); + /// /// Solve a system of equations with 3 unknowns. /// diff --git a/framework/include/serializer.hpp b/framework/include/serializer.hpp index bcbfc04d0..7a29ada8e 100644 --- a/framework/include/serializer.hpp +++ b/framework/include/serializer.hpp @@ -584,3 +584,11 @@ namespace gvk { ); } } + +namespace vk { + template + void serialize(Archive& aArchive, vk::Extent3D& aValue) + { + aArchive(aValue.width, aValue.height, aValue.depth); + } +} diff --git a/framework/src/image_data.cpp b/framework/src/image_data.cpp new file mode 100644 index 000000000..909b2cec3 --- /dev/null +++ b/framework/src/image_data.cpp @@ -0,0 +1,498 @@ +#include + +namespace gvk +{ + // implementation in bridge pattern + class image_data_composite_cubemap : public image_data_implementor + { + public: + explicit image_data_composite_cubemap(const std::vector& aPaths, const bool aLoadHdrIfPossible = false, const bool aLoadSrgbIfApplicable = false, const bool aFlip = false, const int aPreferredNumberOfTextureComponents = 4) + : image_data_implementor(aPaths, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents) + { + assert(aPaths.size() == 6); + + for (const auto& path : aPaths) + { + std::unique_ptr i = load_image_data_from_file(path, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents); + + image_data_implementors.push_back(std::move(i)); + } + + assert(image_data_implementors.size() == 6); + } + + void load() + { + for (auto& r : image_data_implementors) + { + r->load(); + + assert(!r->empty()); + assert(r->layers() == 1); + // target must be 2D + assert(r->target() == vk::ImageType::e2D); + // must not be a cubemap + assert(r->faces() == 1); + + if (!image_data_implementors.empty()) + { + // all image resources must have the same format, target and extent + auto& r0 = image_data_implementors[0]; + assert(r->get_format() == r0->get_format()); + assert(r->target() == r0->target()); + assert(r->extent() == r0->extent()); + // Mipmap levels must agree + assert(r->levels() == r0->levels()); + } + } + } + + bool empty() const + { + auto is_empty = false; + + for (const auto& r : image_data_implementors) + { + is_empty = is_empty || r->empty(); + } + + return is_empty; + } + + vk::Format get_format() const + { + return image_data_implementors[0]->get_format(); + } + + vk::ImageType target() const + { + return image_data_implementors[0]->target(); + } + + extent_type extent(const uint32_t level = 0) const + { + return image_data_implementors[0]->extent(level); + } + + void* get_data(const uint32_t layer, const uint32_t face, const uint32_t level) + { + return image_data_implementors[face]->get_data(0, 0, level); + } + + size_t size() const + { + return image_data_implementors[0]->size(); + } + + size_t size(const uint32_t level) const + { + return image_data_implementors[0]->size(level); + } + + uint32_t levels() const + { + return image_data_implementors[0]->levels(); + } + + uint32_t faces() const + { + return 6; + } + + bool is_hdr() const + { + return image_data_implementors[0]->is_hdr(); + } + + private: + std::vector> image_data_implementors; + }; + + class image_data_gli : public image_data_implementor + { + public: + explicit image_data_gli(const std::string& aPath, const bool aLoadHdrIfPossible = false, const bool aLoadSrgbIfApplicable = false, const bool aFlip = false, const int aPreferredNumberOfTextureComponents = 4) + : image_data_implementor(aPath, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents) + { + } + + void load() + { + gliTex = gli::load(path()); + + if (!gliTex.empty() && mFlip) + { + flip(); + } + } + + vk::Format get_format() const + { + // TODO: what if mLoadHdrIfPossible == false but file has a HDR format? Likewise, mLoadSrgbIfApplicable is not considered here. + return map_format_gli_to_vk(gliTex.format()); + }; + + vk::ImageType target() const + { + switch (gliTex.target()) + { + case gli::TARGET_1D: + case gli::TARGET_1D_ARRAY: + return vk::ImageType::e1D; + case gli::TARGET_2D: + case gli::TARGET_2D_ARRAY: + case gli::TARGET_CUBE: + case gli::TARGET_CUBE_ARRAY: + return vk::ImageType::e2D; + case gli::TARGET_3D: + return vk::ImageType::e3D; + default: + throw std::exception("Unknown target"); + } + } + + extent_type extent(const uint32_t level = 0) const + { + auto e = gliTex.extent(level); + + return vk::Extent3D(e[0], e[1], e[2]); + }; + + void* get_data(const uint32_t layer, const uint32_t face, const uint32_t level) + { + return gliTex.data(layer, face, level); + }; + + size_t size() const + { + return gliTex.size(); + } + + size_t size(const uint32_t level) const + { + return gliTex.size(level); + } + + // Mipmap levels; 1 if no Mipmapping, 0 if Mipmaps should be created after loading + uint32_t levels() const + { + // Vulkan uses uint32_t to store level and layer sizes + return static_cast(gliTex.levels()); + }; + + // array layers, for texture arrays + uint32_t layers() const + { + return static_cast(gliTex.layers()); + }; + + // faces in cubemap + uint32_t faces() const + { + return static_cast(gliTex.faces()); + }; + + bool can_flip() const + { + switch (gliTex.target()) + { + case gli::TARGET_2D: + case gli::TARGET_2D_ARRAY: + case gli::TARGET_CUBE: + case gli::TARGET_CUBE_ARRAY: + return !gli::is_compressed(gliTex.format()) || gli::is_s3tc_compressed(gliTex.format()); + default: + return false; + } + }; + + bool empty() const + { + return gliTex.empty(); + }; + + protected: + void flip() + { + assert(can_flip()); + + gliTex = gli::flip(gliTex); + }; + + private: + static vk::Format map_format_gli_to_vk(const gli::format gliFmt) + { + vk::Format imFmt; + + switch (gliFmt) { + // See "Khronos Data Format Specification": https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#S3TC + // And Vulkan specification: https://www.khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap42.html#appendix-compressedtex-bc + case gli::format::FORMAT_RGB_DXT1_UNORM_BLOCK8: + imFmt = vk::Format::eBc1RgbUnormBlock; + break; + case gli::format::FORMAT_RGB_DXT1_SRGB_BLOCK8: + imFmt = vk::Format::eBc1RgbSrgbBlock; + break; + case gli::format::FORMAT_RGBA_DXT1_UNORM_BLOCK8: + imFmt = vk::Format::eBc1RgbaUnormBlock; + break; + case gli::format::FORMAT_RGBA_DXT1_SRGB_BLOCK8: + imFmt = vk::Format::eBc1RgbaSrgbBlock; + break; + case gli::format::FORMAT_RGBA_DXT3_UNORM_BLOCK16: + imFmt = vk::Format::eBc2UnormBlock; + break; + case gli::format::FORMAT_RGBA_DXT3_SRGB_BLOCK16: + imFmt = vk::Format::eBc2SrgbBlock; + break; + case gli::format::FORMAT_RGBA_DXT5_UNORM_BLOCK16: + imFmt = vk::Format::eBc3UnormBlock; + break; + case gli::format::FORMAT_RGBA_DXT5_SRGB_BLOCK16: + imFmt = vk::Format::eBc3SrgbBlock; + break; + case gli::format::FORMAT_R_ATI1N_UNORM_BLOCK8: + imFmt = vk::Format::eBc4UnormBlock; + break; + // See "Khronos Data Format Specification": https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#RGTC + // And Vulkan specification: https://www.khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap42.html#appendix-compressedtex-bc + case gli::format::FORMAT_R_ATI1N_SNORM_BLOCK8: + imFmt = vk::Format::eBc4SnormBlock; + break; + case gli::format::FORMAT_RG_ATI2N_UNORM_BLOCK16: + imFmt = vk::Format::eBc5UnormBlock; + break; + case gli::format::FORMAT_RG_ATI2N_SNORM_BLOCK16: + imFmt = vk::Format::eBc5SnormBlock; + break; + + // uncompressed formats, TODO other values? + case gli::format::FORMAT_RGBA8_UNORM_PACK8: + imFmt = vk::Format::eR8G8B8A8Unorm; + break; + default: + imFmt = vk::Format::eUndefined; + } + + return imFmt; + } + + gli::texture gliTex; + }; + + class image_data_stb : public image_data_implementor + { + private: + using type_8bit = stbi_uc; + + public: + explicit image_data_stb(const std::string& aPath, const bool aLoadHdrIfPossible = false, const bool aLoadSrgbIfApplicable = false, const bool aFlip = false, const int aPreferredNumberOfTextureComponents = 4) + : image_data_implementor(aPath, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents), mExtent(0, 0, 0), mChannelsInFile(0), sizeofPixelPerChannel(0), mFormat(vk::Format::eUndefined), mData(nullptr, &deleter) + { + } + + void load() + { + stbi_set_flip_vertically_on_load(mFlip); + + int w = 0, h = 0; + + mLoadHdrIfPossible = mLoadHdrIfPossible && stbi_is_hdr(path().c_str()); + + // TODO: load 16 bit per channel files? + if (mLoadHdrIfPossible) + { + void* data = stbi_loadf(path().c_str(), &w, &h, &mChannelsInFile, map_to_stbi_channels(mPreferredNumberOfTextureComponents)); + mData = std::unique_ptr(data, &deleter); + sizeofPixelPerChannel = sizeof(float); + } + else + { + void* data = stbi_load(path().c_str(), &w, &h, &mChannelsInFile, map_to_stbi_channels(mPreferredNumberOfTextureComponents)); + mData = std::unique_ptr(data, &deleter); + sizeofPixelPerChannel = sizeof(stbi_uc); + } + + if (!mData) { + LOG_INFO_EM(fmt::format("Result: {}", std::string(stbi_failure_reason()))); + + throw gvk::runtime_error(fmt::format("Couldn't load image from '{}' using stbi_load{}", path(), mLoadHdrIfPossible ? "f" : "")); + } + assert(mData != nullptr); + + mExtent = vk::Extent3D(w, h, 1); + + mFormat = select_format(mPreferredNumberOfTextureComponents, mLoadHdrIfPossible, mLoadSrgbIfApplicable); + }; + + vk::Format get_format() const + { + return mFormat; + }; + + vk::ImageType target() const + { + // stb_image only supports 2D texture targets + return vk::ImageType::e2D; + } + + extent_type extent(const uint32_t level = 0) const + { + // stb_image does not support mipmap levels + assert(level == 0); + + return mExtent; + }; + + void* get_data(const uint32_t layer, const uint32_t face, const uint32_t level) + { + // stb_image does not support layers, faces or levels + assert(layer == 0 && face == 0 && level == 0); + + return mData.get(); + }; + + size_t size() const + { + return mExtent.width * mExtent.height * mPreferredNumberOfTextureComponents * sizeofPixelPerChannel; + } + + size_t size(const uint32_t level) const + { + assert(level == 0); + + return size(); + } + + bool empty() const + { + return mData == nullptr; + }; + + private: + static int map_to_stbi_channels(const int aPreferredNumberOfTextureComponents) + { + // TODO: autodetect number of channels in file (= 0)? + assert(0 < aPreferredNumberOfTextureComponents && aPreferredNumberOfTextureComponents <= 4); + + switch (aPreferredNumberOfTextureComponents) + { + case 1: + return STBI_grey; + case 2: + return STBI_grey_alpha; + case 3: + return STBI_rgb; + case 4: + default: + return STBI_rgb_alpha; + + } + } + + static vk::Format select_format(const int aPreferredNumberOfTextureComponents, const bool aLoadHdr, const bool aLoadSrgb) + { + vk::Format imFmt = vk::Format::eUndefined; + + if (aLoadHdr) { + switch (aPreferredNumberOfTextureComponents) { + case 4: + default: + imFmt = default_rgb16f_4comp_format(); + break; + // Attention: There's a high likelihood that your GPU does not support formats with less than four color components! + case 3: + imFmt = default_rgb16f_3comp_format(); + break; + case 2: + imFmt = default_rgb16f_2comp_format(); + break; + case 1: + imFmt = default_rgb16f_1comp_format(); + break; + } + //} + } + else if (aLoadSrgb) { + switch (aPreferredNumberOfTextureComponents) { + case 4: + default: + imFmt = gvk::default_srgb_4comp_format(); + break; + // Attention: There's a high likelihood that your GPU does not support formats with less than four color components! + case 3: + imFmt = gvk::default_srgb_3comp_format(); + break; + case 2: + imFmt = gvk::default_srgb_2comp_format(); + break; + case 1: + imFmt = gvk::default_srgb_1comp_format(); + break; + } + } + else { + switch (aPreferredNumberOfTextureComponents) { + case 4: + default: + imFmt = gvk::default_rgb8_4comp_format(); + break; + // Attention: There's a high likelihood that your GPU does not support formats with less than four color components! + case 3: + imFmt = gvk::default_rgb8_3comp_format(); + break; + case 2: + imFmt = gvk::default_rgb8_2comp_format(); + break; + case 1: + imFmt = gvk::default_rgb8_1comp_format(); + break; + } + } + + return imFmt; + } + + // stb_image uses malloc() for memory allocation, therefore it should be deallocated with free() + static void deleter(void* data) { + stbi_image_free(data); + }; + + vk::Extent3D mExtent; + int mChannelsInFile; + size_t sizeofPixelPerChannel; + vk::Format mFormat; + + std::unique_ptr mData; + }; + + std::unique_ptr image_data_interface::load_image_data_from_file(const std::string& aPath, const bool aLoadHdrIfPossible, const bool aLoadSrgbIfApplicable, const bool aFlip, const int aPreferredNumberOfTextureComponents) + { + // try loading with GLI + std::unique_ptr retval(new image_data_gli(aPath, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents)); + retval->load(); + + // try loading with stb + if (retval->empty()) + { + retval = std::unique_ptr(new image_data_stb(aPath, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents)); + retval->load(); + } + + if (retval->empty()) + { + throw gvk::runtime_error(fmt::format("Could not load image from '{}'", aPath)); + } + + return retval; + } + + std::unique_ptr image_data_interface::load_image_data_from_file(const std::vector& aPaths, const bool aLoadHdrIfPossible, const bool aLoadSrgbIfApplicable, const bool aFlip, const int aPreferredNumberOfTextureComponents) + { + std::unique_ptr retval(new image_data_composite_cubemap(aPaths, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents)); + retval->load(); + + return retval; + } + +} diff --git a/framework/src/material_image_helpers.cpp b/framework/src/material_image_helpers.cpp index 93d7fea9e..0b0feba2d 100644 --- a/framework/src/material_image_helpers.cpp +++ b/framework/src/material_image_helpers.cpp @@ -2,6 +2,199 @@ namespace gvk { + avk::image create_cubemap_from_image_data_cached(image_data& aImageData, avk::memory_usage aMemoryUsage, avk::image_usage aImageUsage, avk::sync aSyncHandler, std::optional> aSerializer) + { + // image must have flag set to be used for cubemap + assert((static_cast(aImageUsage) & static_cast(avk::image_usage::cube_compatible)) > 0); + + return create_image_from_image_data_cached(aImageData, aMemoryUsage, aImageUsage, std::move(aSyncHandler), aSerializer); + } + + avk::image create_cubemap_from_file_cached(const std::string& aPath, bool aLoadHdrIfPossible, bool aLoadSrgbIfApplicable, bool aFlip, + int aPreferredNumberOfTextureComponents, avk::memory_usage aMemoryUsage, avk::image_usage aImageUsage, avk::sync aSyncHandler, std::optional> aSerializer) + { + auto cubemapImageData = get_image_data(aPath, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents); + + return create_cubemap_from_image_data_cached(cubemapImageData, aMemoryUsage, aImageUsage, std::move(aSyncHandler), aSerializer); + } + + avk::image create_cubemap_from_file_cached(const std::vector& aPaths, bool aLoadHdrIfPossible, bool aLoadSrgbIfApplicable, bool aFlip, + int aPreferredNumberOfTextureComponents, avk::memory_usage aMemoryUsage, avk::image_usage aImageUsage, avk::sync aSyncHandler, std::optional> aSerializer) + { + auto cubemapImageData = get_image_data(aPaths, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents); + + return create_cubemap_from_image_data_cached(cubemapImageData, aMemoryUsage, aImageUsage, std::move(aSyncHandler), aSerializer); + } + + avk::image create_image_from_image_data_cached(image_data& aImageData, avk::memory_usage aMemoryUsage, avk::image_usage aImageUsage, avk::sync aSyncHandler, std::optional> aSerializer) + { + uint32_t width = 0; + uint32_t height = 0; + vk::Format format = vk::Format::eUndefined; + uint32_t numLayers = 0; + + if (!aSerializer || + (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::serialize)) { + + // load the image to memory + aImageData.load(); + + assert(!aImageData.empty()); + + if (aImageData.target() != vk::ImageType::e2D) { + throw gvk::runtime_error(fmt::format("The image loaded from '{}' is not intended to be used as 2D image. Can't load it.", aImageData.path())); + } + + bool is_cube_compatible = (static_cast(aImageUsage) & static_cast(avk::image_usage::cube_compatible)) > 0; + if (is_cube_compatible && aImageData.faces() != 6) { + throw gvk::runtime_error(fmt::format("The image loaded from '{}' is not intended to be used as a cubemap image.", aImageData.path())); + } + + width = aImageData.extent().width; + height = aImageData.extent().height; + + format = aImageData.get_format(); + + // number of layers in Vulkan image: equals (number of layers) x (number of faces) in image_data + // a cubemap image in Vulkan must have six layers, one for each side of the cube + // TODO: support texture/cubemap arrays + numLayers = aImageData.faces(); + } + + if (aSerializer) { + aSerializer->get().archive(width); + aSerializer->get().archive(height); + aSerializer->get().archive(format); + aSerializer->get().archive(numLayers); + } + + auto& commandBuffer = aSyncHandler.get_or_create_command_buffer(); + aSyncHandler.establish_barrier_before_the_operation(avk::pipeline_stage::transfer, avk::read_memory_access{ avk::memory_access::transfer_read_access }); + + // TODO: if image resource does not have a full mipmap pyramid, create image with fewer levels + auto img = context().create_image(width, height, format, numLayers, aMemoryUsage, aImageUsage); + auto finalTargetLayout = img->target_layout(); // save for later, because first, we need to transfer something into it + + // 1. Transition image layout to eTransferDstOptimal + img->transition_to_layout(vk::ImageLayout::eTransferDstOptimal, avk::sync::auxiliary_with_barriers(aSyncHandler, {}, {})); // no need for additional sync + // TODO: The original implementation transitioned into cgb::image_format(_Format) format here, not to eTransferDstOptimal => Does it still work? If so, eTransferDstOptimal is fine. + + // 2. Copy buffer to image + // Load all Mipmap levels from file, or load only the base level and generate other levels from that + + size_t maxLevels = 0; + size_t maxFaces = 0; + + if (!aSerializer || + (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::serialize)) { + + // number of levels to load from image resource + maxLevels = aImageData.levels(); + + maxFaces = aImageData.faces(); + + // if number of levels is 0, generate all mipmaps after loading + if (maxLevels == 0) + { + maxLevels = 1; + } + + assert(maxLevels >= 1); + // TODO: handle the case where some but not all mipmap levels are loaded from image resource? + assert(maxLevels == 1 || maxLevels == img->config().mipLevels); + } + + if (aSerializer) { + aSerializer->get().archive(maxLevels); + aSerializer->get().archive(maxFaces); + } + + std::vector stagingBuffers; + + // TODO: Do we have to account for gliTex.base_level() and gliTex.max_level()? + for (uint32_t level = 0; level < maxLevels; ++level) + { + for (uint32_t face = 0; face < maxFaces; ++face) + { + size_t texSize = 0; + void* texData = nullptr; + gvk::image_data::extent_type levelExtent; + + if (!aSerializer || + (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::serialize)) { + texSize = aImageData.size(level); + texData = aImageData.get_data(0, face, level); + levelExtent = aImageData.extent(level); + } + if (aSerializer) { + aSerializer->get().archive(texSize); + aSerializer->get().archive(levelExtent); + } +#if _DEBUG + { + auto imgExtent = img->config().extent; + auto levelDivisor = 1u << level; + imgExtent.width = imgExtent.width > 1u ? imgExtent.width / levelDivisor : 1u; + imgExtent.height = imgExtent.height > 1u ? imgExtent.height / levelDivisor : 1u; + imgExtent.depth = imgExtent.depth > 1u ? imgExtent.depth / levelDivisor : 1u; + assert(levelExtent.width == static_cast(imgExtent.width)); + assert(levelExtent.height == static_cast(imgExtent.height)); + assert(levelExtent.depth == static_cast(imgExtent.depth)); + } +#endif + + auto& sb = stagingBuffers.emplace_back(context().create_buffer( + AVK_STAGING_BUFFER_MEMORY_USAGE, + vk::BufferUsageFlagBits::eTransferSrc, + avk::generic_buffer_meta::create_from_size(texSize) + )); + + if (!aSerializer) { + sb->fill(texData, 0, avk::sync::not_required()); + } + else if (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::serialize) { + sb->fill(texData, 0, avk::sync::not_required()); + aSerializer->get().archive_memory(texData, texSize); + } + else if (aSerializer && aSerializer->get().mode() == gvk::serializer::mode::deserialize) { + aSerializer->get().archive_buffer(sb); + LOG_INFO_EM("Buffer loaded from cache"); + } + + // Memory writes are not overlapping => no barriers should be fine. + avk::copy_buffer_to_image_layer_mip_level(avk::const_referenced(sb), avk::referenced(img), face, level, {}, avk::sync::auxiliary_with_barriers(aSyncHandler, {}, {})); + // There should be no need to make any memory available or visible, the transfer-execution dependency chain should be fine + // TODO: Verify the above ^ comment + } + } + + if (maxLevels == 1 && img->config().mipLevels > 1) + { + // can't create MIP-maps for compressed formats + assert(!avk::is_block_compressed_format(aImageData.get_format())); + + // For uncompressed formats, create MIP-maps via BLIT: + img->generate_mip_maps(avk::sync::auxiliary_with_barriers(aSyncHandler, {}, {})); + } + + commandBuffer.set_custom_deleter([lOwnedStagingBuffers = std::move(stagingBuffers)](){}); + + // 3. Transition image layout to its target layout and handle lifetime of things via sync + img->transition_to_layout(finalTargetLayout, avk::sync::auxiliary_with_barriers(aSyncHandler, {}, {})); + + aSyncHandler.establish_barrier_after_the_operation(avk::pipeline_stage::transfer, avk::write_memory_access{ avk::memory_access::transfer_write_access }); + auto result = aSyncHandler.submit_and_sync(); + assert(!result.has_value()); + return img; + } + + avk::image create_image_from_file_cached(const std::string& aPath, bool aLoadHdrIfPossible, bool aLoadSrgbIfApplicable, bool aFlip, int aPreferredNumberOfTextureComponents, avk::memory_usage aMemoryUsage, avk::image_usage aImageUsage, avk::sync aSyncHandler, std::optional> aSerializer) + { + auto imageData = get_image_data(aPath, aLoadHdrIfPossible, aLoadSrgbIfApplicable, aFlip, aPreferredNumberOfTextureComponents); + + return gvk::create_image_from_image_data_cached(imageData, aMemoryUsage, aImageUsage, std::move(aSyncHandler), aSerializer); + } + std::tuple, std::vector> get_vertices_and_indices(const std::vector, std::vector>>& aModelsAndSelectedMeshes) { std::vector positionsData; @@ -54,7 +247,7 @@ namespace gvk return normalsData; } - + std::vector get_tangents(const std::vector, std::vector>>& aModelsAndSelectedMeshes) { std::vector tangentsData; @@ -322,7 +515,7 @@ namespace gvk aSerializer.archive(aMipMapMaxLod); return context().create_sampler(aFilterMode, aBorderHandlingModes, aMipMapMaxLod, std::move(aAlterConfigBeforeCreation)); } - + avk::sampler create_sampler_cached(gvk::serializer& aSerializer, avk::filter_mode aFilterMode, std::array aBorderHandlingModes, float aMipMapMaxLod, std::function aAlterConfigBeforeCreation) { return create_sampler_cached(aSerializer, aFilterMode, { aBorderHandlingModes[0], aBorderHandlingModes[1], aBorderHandlingModes[1] }, aMipMapMaxLod, std::move(aAlterConfigBeforeCreation)); @@ -331,5 +524,5 @@ namespace gvk avk::sampler create_sampler_cached(gvk::serializer& aSerializer, avk::filter_mode aFilterMode, avk::border_handling_mode aBorderHandlingMode, float aMipMapMaxLod, std::function aAlterConfigBeforeCreation) { return create_sampler_cached(aSerializer, aFilterMode, { aBorderHandlingMode, aBorderHandlingMode, aBorderHandlingMode }, aMipMapMaxLod, std::move(aAlterConfigBeforeCreation)); - } -} + } + } diff --git a/framework/src/math_utils.cpp b/framework/src/math_utils.cpp index c86758310..ea2e8d1e5 100644 --- a/framework/src/math_utils.cpp +++ b/framework/src/math_utils.cpp @@ -25,6 +25,20 @@ namespace gvk return glm::vec3(m[3][0], m[3][1], m[3][2]); } + glm::mat4 cancel_translation_from_matrix(const glm::mat4& aMatrix) + { + return glm::mat4(aMatrix[0], aMatrix[1], aMatrix[2], glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); + } + + glm::mat4 mirror_matrix(const glm::mat4& aMatrix, principal_axis aAxis) + { + auto axisIndex = static_cast::type>(aAxis); + + auto result(aMatrix); + result[axisIndex] *= -1.0f; + return result; + } + bool solve_system_of_equations(const glm::dmat3& A, const glm::dvec3& c, glm::dvec3& outX) { if (glm::abs(glm::determinant(A)) < glm::epsilon()) { diff --git a/visual_studio/examples/texture_cubemap/cg_stdafx.cpp b/visual_studio/examples/texture_cubemap/cg_stdafx.cpp new file mode 100644 index 000000000..75870fb83 --- /dev/null +++ b/visual_studio/examples/texture_cubemap/cg_stdafx.cpp @@ -0,0 +1,8 @@ +// cg_stdafx.cpp : source file that includes just the standard includes +// cg_stdafx.pch will be the pre-compiled header +// cg_stdafx.obj will contain the pre-compiled type information + +#include "cg_stdafx.hpp" + +// TODO: reference any additional headers you need in cg_stdafx.hpp +// and not in this file diff --git a/visual_studio/examples/texture_cubemap/cg_stdafx.hpp b/visual_studio/examples/texture_cubemap/cg_stdafx.hpp new file mode 100644 index 000000000..62c07bd0b --- /dev/null +++ b/visual_studio/examples/texture_cubemap/cg_stdafx.hpp @@ -0,0 +1,11 @@ +// cg_stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// +#pragma once + +#include "cg_targetver.hpp" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include "gvk.hpp" \ No newline at end of file diff --git a/visual_studio/examples/texture_cubemap/cg_targetver.hpp b/visual_studio/examples/texture_cubemap/cg_targetver.hpp new file mode 100644 index 000000000..87c0086de --- /dev/null +++ b/visual_studio/examples/texture_cubemap/cg_targetver.hpp @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/visual_studio/examples/texture_cubemap/texture_cubemap.vcxproj b/visual_studio/examples/texture_cubemap/texture_cubemap.vcxproj new file mode 100644 index 000000000..32e6d88fd --- /dev/null +++ b/visual_studio/examples/texture_cubemap/texture_cubemap.vcxproj @@ -0,0 +1,211 @@ + + + + + Debug_Vulkan + x64 + + + Publish_Vulkan + x64 + + + Release_Vulkan + x64 + + + + + + Create + Create + Create + + + + + + + + + + + + + + + + {602f842f-50c1-466d-8696-1707937d8ab9} + + + + + Document + + + + + Document + + + + + + + + + + + + + 15.0 + {bcfffe1c-fc08-432a-9a07-4c049ae398bd} + Win32Proj + texture_cubemap + 10.0 + texture_cubemap + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + $(ProjectDir)bin\$(Configuration)_$(Platform)\ + $(ProjectDir)temp\intermediate\$(Configuration)_$(Platform)\ + Build + + + false + $(ProjectDir)bin\$(Configuration)_$(Platform)\executable\ + $(ProjectDir)temp\intermediate\$(Configuration)_$(Platform)\ + Build + + + true + $(ProjectDir)bin\$(Configuration)_$(Platform)\ + $(ProjectDir)temp\intermediate\$(Configuration)_$(Platform)\ + Build + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpplatest + cg_stdafx.hpp + 4715 + cg_stdafx.hpp + + + Console + true + true + true + + + powershell.exe -ExecutionPolicy Bypass -File "$(ToolsBin)invoke_post_build_helper.ps1" -msbuild "$(MsBuildToolsPath)" -configuration "$(Configuration)" -framework "$(FrameworkRoot)\" -platform "$(Platform)" -vcxproj "$(ProjectPath)" -filters "$(ProjectPath).filters" -output "$(OutputPath)\" -executable "$(TargetPath)" -external "$(ExternalRoot)\" + some-non-existant-file-to-always-run-the-custom-build-step.txt;%(Outputs) + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpplatest + cg_stdafx.hpp + 4715 + cg_stdafx.hpp + + + Console + true + true + true + + + powershell.exe -ExecutionPolicy Bypass -File "$(ToolsBin)invoke_post_build_helper.ps1" -msbuild "$(MsBuildToolsPath)" -configuration "$(Configuration)" -framework "$(FrameworkRoot)\" -platform "$(Platform)" -vcxproj "$(ProjectPath)" -filters "$(ProjectPath).filters" -output "$(OutputPath)\" -executable "$(TargetPath)" -external "$(ExternalRoot)\" + some-non-existant-file-to-always-run-the-custom-build-step.txt;%(Outputs) + + + + + Use + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpplatest + cg_stdafx.hpp + 4715 + cg_stdafx.hpp + + + Console + true + + + powershell.exe -ExecutionPolicy Bypass -File "$(ToolsBin)invoke_post_build_helper.ps1" -msbuild "$(MsBuildToolsPath)" -configuration "$(Configuration)" -framework "$(FrameworkRoot)\" -platform "$(Platform)" -vcxproj "$(ProjectPath)" -filters "$(ProjectPath).filters" -output "$(OutputPath)\" -executable "$(TargetPath)" -external "$(ExternalRoot)\" + some-non-existant-file-to-always-run-the-custom-build-step.txt;%(Outputs) + + + + + + \ No newline at end of file diff --git a/visual_studio/examples/texture_cubemap/texture_cubemap.vcxproj.filters b/visual_studio/examples/texture_cubemap/texture_cubemap.vcxproj.filters new file mode 100644 index 000000000..c158b0eb1 --- /dev/null +++ b/visual_studio/examples/texture_cubemap/texture_cubemap.vcxproj.filters @@ -0,0 +1,75 @@ + + + + + {24240a51-8fdb-478f-8c1c-27cbca7adc3f} + False + + + {1d345cf5-0451-42e0-83a8-6a3f5a7203ca} + + + {a498e4bc-580a-49d7-9a8e-ec57803fdcd4} + + + + + precompiled_headers + + + + + + shaders + + + shaders + + + shaders + + + shaders + + + assets + + + assets + + + assets + + + + + precompiled_headers + + + precompiled_headers + + + + + assets + + + assets + + + assets + + + assets + + + assets + + + assets + + + assets + + + \ No newline at end of file diff --git a/visual_studio/examples/texture_cubemap/texture_cubemap.vcxproj.user b/visual_studio/examples/texture_cubemap/texture_cubemap.vcxproj.user new file mode 100644 index 000000000..61918ac10 --- /dev/null +++ b/visual_studio/examples/texture_cubemap/texture_cubemap.vcxproj.user @@ -0,0 +1,15 @@ + + + + $(OutputPath) + WindowsLocalDebugger + + + $(OutputPath) + WindowsLocalDebugger + + + $(OutputPath) + WindowsLocalDebugger + + \ No newline at end of file diff --git a/visual_studio/gears-vk.sln b/visual_studio/gears-vk.sln index 2c0b5fe20..2f5b71a97 100644 --- a/visual_studio/gears-vk.sln +++ b/visual_studio/gears-vk.sln @@ -34,6 +34,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "framebuffer", "examples\fra EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "multi_invokee_rendering", "examples\multi_invokee_rendering\multi_invokee_rendering.vcxproj", "{67E56BCA-00F5-4AEE-AEB7-E0E064428AA8}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "textures", "textures", "{8C843027-6CE3-4F0D-A640-16C5C188BCD6}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "texture_cubemap", "examples\texture_cubemap\texture_cubemap.vcxproj", "{BCFFFE1C-FC08-432A-9A07-4C049AE398BD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug_Vulkan|x64 = Debug_Vulkan|x64 @@ -107,6 +111,12 @@ Global {67E56BCA-00F5-4AEE-AEB7-E0E064428AA8}.Publish_Vulkan|x64.Build.0 = Publish_Vulkan|x64 {67E56BCA-00F5-4AEE-AEB7-E0E064428AA8}.Release_Vulkan|x64.ActiveCfg = Release_Vulkan|x64 {67E56BCA-00F5-4AEE-AEB7-E0E064428AA8}.Release_Vulkan|x64.Build.0 = Release_Vulkan|x64 + {BCFFFE1C-FC08-432A-9A07-4C049AE398BD}.Debug_Vulkan|x64.ActiveCfg = Debug_Vulkan|x64 + {BCFFFE1C-FC08-432A-9A07-4C049AE398BD}.Debug_Vulkan|x64.Build.0 = Debug_Vulkan|x64 + {BCFFFE1C-FC08-432A-9A07-4C049AE398BD}.Publish_Vulkan|x64.ActiveCfg = Publish_Vulkan|x64 + {BCFFFE1C-FC08-432A-9A07-4C049AE398BD}.Publish_Vulkan|x64.Build.0 = Publish_Vulkan|x64 + {BCFFFE1C-FC08-432A-9A07-4C049AE398BD}.Release_Vulkan|x64.ActiveCfg = Release_Vulkan|x64 + {BCFFFE1C-FC08-432A-9A07-4C049AE398BD}.Release_Vulkan|x64.Build.0 = Release_Vulkan|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -126,6 +136,8 @@ Global {D8329EE0-A6B8-40FD-A427-5D4AC5C56CAD} = {683E25DF-C29D-4BC6-980E-88F7C09D024F} {BFFBAB2F-A0C4-451F-BBCB-279F218FAB1F} = {08A10CAA-9B1B-41DB-9EB5-8547AC3077EA} {67E56BCA-00F5-4AEE-AEB7-E0E064428AA8} = {08A10CAA-9B1B-41DB-9EB5-8547AC3077EA} + {8C843027-6CE3-4F0D-A640-16C5C188BCD6} = {42ECE233-FCB5-4525-BBC9-024CE075FC38} + {BCFFFE1C-FC08-432A-9A07-4C049AE398BD} = {8C843027-6CE3-4F0D-A640-16C5C188BCD6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A8961D43-F08D-46E3-B3BB-29BA8AA39C3E} diff --git a/visual_studio/gears_vk/gears-vk.vcxproj b/visual_studio/gears_vk/gears-vk.vcxproj index e19a52eb6..83537c217 100644 --- a/visual_studio/gears_vk/gears-vk.vcxproj +++ b/visual_studio/gears_vk/gears-vk.vcxproj @@ -34,6 +34,7 @@ + @@ -152,6 +153,7 @@ + diff --git a/visual_studio/gears_vk/gears-vk.vcxproj.filters b/visual_studio/gears_vk/gears-vk.vcxproj.filters index 363b0fd80..cdda1f3e6 100644 --- a/visual_studio/gears_vk/gears-vk.vcxproj.filters +++ b/visual_studio/gears_vk/gears-vk.vcxproj.filters @@ -121,6 +121,9 @@ gears-vk_src\utils + + gears-vk_src\data + @@ -507,6 +510,9 @@ gears-vk_include\data + + gears-vk_include\data +