From 46c428b13a2f20f6a7ea63f853d251c8bfcf933c Mon Sep 17 00:00:00 2001 From: Slendi Date: Sun, 11 Jan 2026 11:28:48 +0200 Subject: [PATCH] CPU texture Signed-off-by: Slendi --- meson.build | 2 + shaders/meson.build | 2 + shaders/skybox.frag | 12 ++++ shaders/skybox.vert | 14 ++++ src/Application.cpp | 158 +++++++++++++++++++++++++++++++++++++++-- src/Application.h | 9 +++ src/CPUTexture.cpp | 79 +++++++++++++++++++++ src/CPUTexture.h | 20 ++++++ src/VulkanRenderer.cpp | 16 ++--- src/VulkanRenderer.h | 28 ++++++-- 10 files changed, 321 insertions(+), 19 deletions(-) create mode 100644 shaders/skybox.frag create mode 100644 shaders/skybox.vert create mode 100644 src/CPUTexture.cpp create mode 100644 src/CPUTexture.h diff --git a/meson.build b/meson.build index ceed1c8..f3b6ec8 100644 --- a/meson.build +++ b/meson.build @@ -95,6 +95,7 @@ add_project_arguments( '-Wno-zero-as-null-pointer-constant', '-Wno-unused-macros', '-Wno-reserved-macro-identifier', + '-Wno-reserved-identifier', '-Wno-suggest-override', '-Wno-macro-redefined', '-DVULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE', @@ -159,6 +160,7 @@ exe = executable('vr-compositor', 'src/Pipeline.cpp', 'src/Loader.cpp', 'src/DescriptorWriter.cpp', + 'src/CPUTexture.cpp', 'src/VulkanRenderer.cpp', 'src/Application.cpp', ], diff --git a/shaders/meson.build b/shaders/meson.build index 2f3c641..989d533 100644 --- a/shaders/meson.build +++ b/shaders/meson.build @@ -19,6 +19,8 @@ shader_sources = files( 'triangle_mesh.frag', 'triangle_mesh.vert', 'tex_image.frag', + 'skybox.frag', + 'skybox.vert', ) spirv_shaders = [] diff --git a/shaders/skybox.frag b/shaders/skybox.frag new file mode 100644 index 0000000..2461345 --- /dev/null +++ b/shaders/skybox.frag @@ -0,0 +1,12 @@ +#version 450 + +layout (location = 0) in vec3 in_dir; + +layout (location = 0) out vec4 out_frag_color; + +layout (set = 0, binding = 0) uniform samplerCube environment_map; + +void main() { + vec3 dir = normalize(in_dir); + out_frag_color = vec4(texture(environment_map, dir).rgb, 1.0f); +} diff --git a/shaders/skybox.vert b/shaders/skybox.vert new file mode 100644 index 0000000..b9c26d1 --- /dev/null +++ b/shaders/skybox.vert @@ -0,0 +1,14 @@ +#version 450 + +layout (location = 0) in vec3 in_position; +layout (location = 0) out vec3 out_dir; + +layout(push_constant) uniform constants { + mat4 mvp; +} PushConstants; + +void main() { + out_dir = in_position; + vec4 pos = PushConstants.mvp * vec4(in_position, 1.0f); + gl_Position = vec4(pos.xy, pos.w, pos.w); +} diff --git a/src/Application.cpp b/src/Application.cpp index 68d170f..d00199d 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -5,13 +5,17 @@ #include #include #include +#include #include +#include #include #include #include #include #include #include +#include +#include #include #if defined(__clang__) @@ -32,6 +36,7 @@ #endif #include +#include #include #include #include @@ -46,6 +51,7 @@ #include +#include "GraphicsPipelineBuilder.h" #include "Util.h" #include "VulkanRenderer.h" @@ -413,6 +419,8 @@ Application::Application() } m_renderer = std::make_unique(m_window, m_logger); + init_skybox_pipeline(); + init_test_meshes(); init_input(); @@ -427,6 +435,15 @@ Application::Application() Application::~Application() { + if (m_renderer) { + for (auto const &mesh : m_test_meshes) { + m_renderer->destroy_buffer(mesh->mesh_buffers.index_buffer); + m_renderer->destroy_buffer(mesh->mesh_buffers.vertex_buffer); + } + } + m_test_meshes.clear(); + + m_skybox_pipeline.reset(); m_renderer.reset(); shutdown_input(); @@ -437,7 +454,143 @@ Application::~Application() m_logger.info("App destroy done!"); } +auto Application::init_skybox_pipeline() -> void +{ + struct SkyboxPushConstants { + smath::Mat4 mvp; + }; + + Pipeline::Builder builder { m_renderer->device(), m_logger }; + + uint8_t skybox_vert_shader_data[] { +#embed "skybox_vert.spv" + }; + auto skybox_vert_shader + = vkutil::load_shader_module(std::span(skybox_vert_shader_data, + sizeof(skybox_vert_shader_data)), + m_renderer->device()); + if (!skybox_vert_shader) { + m_logger.err("Failed to load skybox vert shader"); + } + + uint8_t skybox_frag_shader_data[] { +#embed "skybox_frag.spv" + }; + auto skybox_frag_shader + = vkutil::load_shader_module(std::span(skybox_frag_shader_data, + sizeof(skybox_frag_shader_data)), + m_renderer->device()); + if (!skybox_frag_shader) { + m_logger.err("Failed to load skybox frag shader"); + } + + vk::PushConstantRange push_constant_range {}; + push_constant_range.stageFlags = vk::ShaderStageFlagBits::eVertex; + push_constant_range.offset = 0; + push_constant_range.size = sizeof(SkyboxPushConstants); + + std::array push_constant_ranges { push_constant_range }; + builder.set_push_constant_ranges(push_constant_ranges); + std::array descriptor_set_layouts { + m_renderer->single_image_descriptor_layout(), + }; + builder.set_descriptor_set_layouts(descriptor_set_layouts); + + m_skybox_pipeline = builder.build_graphics( + [&](GraphicsPipelineBuilder &pipeline_builder) + -> GraphicsPipelineBuilder & { + return pipeline_builder + .set_shaders(skybox_vert_shader.get(), skybox_frag_shader.get()) + .set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST) + .set_polygon_mode(VK_POLYGON_MODE_FILL) + .set_cull_mode( + VK_CULL_MODE_FRONT_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE) + .set_multisampling(static_cast( + m_renderer->msaa_samples())) + .disable_blending() + .enable_depth_testing(false, VK_COMPARE_OP_LESS_OR_EQUAL) + .set_color_attachment_format( + static_cast(m_renderer->draw_image_format())) + .set_depth_format( + static_cast(m_renderer->depth_image_format())); + }); +} + +auto Application::binary_directory() const -> std::filesystem::path +{ + auto const *base_path = SDL_GetBasePath(); + if (!base_path) { + return std::filesystem::current_path(); + } + std::filesystem::path base_dir { base_path }; + SDL_free(const_cast(base_path)); + return base_dir; +} + +auto Application::asset_directory() -> std::filesystem::path +{ + std::vector candidates; + + auto add_xdg_path = [&](std::filesystem::path const &base) { + candidates.emplace_back(base / "lunar" / "assets"); + }; + + if (auto const *xdg_data_home = getenv("XDG_DATA_HOME"); + xdg_data_home && *xdg_data_home) { + add_xdg_path(xdg_data_home); + } + + if (auto const *xdg_data_dirs = getenv("XDG_DATA_DIRS"); + xdg_data_dirs && *xdg_data_dirs) { + std::string_view dirs_view { xdg_data_dirs }; + size_t start = 0; + while (start <= dirs_view.size()) { + size_t end = dirs_view.find(':', start); + if (end == std::string_view::npos) { + end = dirs_view.size(); + } + auto segment = dirs_view.substr(start, end - start); + if (!segment.empty()) { + add_xdg_path(std::filesystem::path { segment }); + } + start = end + 1; + } + } else { + add_xdg_path("/usr/local/share"); + add_xdg_path("/usr/share"); + } + + auto base_dir = binary_directory(); + candidates.emplace_back(base_dir / "assets"); + candidates.emplace_back(base_dir / "../assets"); + + for (auto const &candidate : candidates) { + if (std::filesystem::exists(candidate) + && std::filesystem::is_directory(candidate)) { + return candidate; + } + } + + m_logger.warn( + "Assets directory not found, using {}", (base_dir / "assets").string()); + return base_dir / "assets"; +} + +auto Application::init_test_meshes() -> void +{ + auto assets_dir = asset_directory(); + auto mesh_path = assets_dir / "basicmesh.glb"; + auto meshes = Mesh::load_gltf_meshes(*m_renderer, mesh_path); + if (!meshes) { + m_logger.err("Failed to load test mesh: {}", mesh_path.string()); + return; + } + + m_test_meshes = std::move(*meshes); +} + auto Application::run() -> void + { SDL_Event e; @@ -500,9 +653,6 @@ auto Application::run() -> void auto const target_distance { target_offset.magnitude() }; auto const target_polar { PolarCoordinate::from_vec3( target_offset) }; - // Keep cursor angles in sync with externally-updated targets so - // view aligns to the target direction. Preserve radius when the - // target sits on top of the camera to avoid collapsing it. if (target_distance > 0.0f) { m_cursor.r = target_distance; m_cursor.theta = target_polar.theta; @@ -711,7 +861,7 @@ auto Application::run() -> void gl.set_transform(view_projection); gl.set_texture(); - auto const &meshes { m_renderer->test_meshes() }; + auto const &meshes { m_test_meshes }; if (meshes.size() > 2 && !meshes[2]->surfaces.empty()) { auto const &surface = meshes[2]->surfaces[0]; gl.draw_mesh(meshes[2]->mesh_buffers, diff --git a/src/Application.h b/src/Application.h index 42e1768..8e71bfb 100644 --- a/src/Application.h +++ b/src/Application.h @@ -2,13 +2,16 @@ #include #include +#include #include #include #include #include +#include "Loader.h" #include "Logger.h" +#include "Pipeline.h" #include "Types.h" struct libinput; @@ -25,6 +28,7 @@ struct Application { ~Application(); auto run() -> void; + auto binary_directory() const -> std::filesystem::path; auto mouse_captured(bool new_state) -> void; auto mouse_captured() const -> bool { return m_mouse_captured; } @@ -33,6 +37,9 @@ struct Application { private: auto init_input() -> void; + auto init_skybox_pipeline() -> void; + auto init_test_meshes() -> void; + auto asset_directory() -> std::filesystem::path; auto shutdown_input() -> void; auto process_libinput_events() -> void; auto handle_keyboard_event(libinput_event_keyboard *event) -> void; @@ -41,6 +48,8 @@ private: SDL_Window *m_window { nullptr }; Logger m_logger { "Lunar" }; std::unique_ptr m_renderer; + Pipeline m_skybox_pipeline; + std::vector> m_test_meshes; udev *m_udev { nullptr }; libinput *m_libinput { nullptr }; diff --git a/src/CPUTexture.cpp b/src/CPUTexture.cpp new file mode 100644 index 0000000..95c77c7 --- /dev/null +++ b/src/CPUTexture.cpp @@ -0,0 +1,79 @@ +#include "CPUTexture.h" + +#include +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wreserved-identifier" +# pragma clang diagnostic ignored "-Wcast-qual" +# pragma clang diagnostic ignored "-Wimplicit-fallthrough" +# pragma clang diagnostic ignored "-Wmissing-field-initializers" +# pragma clang diagnostic ignored "-Wused-but-marked-unused" +# pragma clang diagnostic ignored "-Wmissing-prototypes" +# pragma clang diagnostic ignored "-Wextra-semi-stmt" +# pragma clang diagnostic ignored "-Wimplicit-int-conversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wshorten-64-to-32" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wcomma" +# pragma clang diagnostic ignored "-Wdouble-promotion" +# pragma clang diagnostic ignored "-Wimplicit-float-conversion" +# pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +# pragma clang diagnostic ignored "-Wdeprecated-declarations" +# pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wpacked" +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wexit-time-destructors" +# pragma clang diagnostic ignored "-Wglobal-constructors" +# pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +# pragma clang diagnostic ignored "-Wcast-align" +# pragma clang diagnostic ignored "-Wcast-qual" +# pragma clang diagnostic ignored "-Wshadow" +# pragma clang diagnostic ignored "-Wnewline-eof" +# pragma clang diagnostic ignored "-Wformat-nonliteral" +# pragma clang diagnostic ignored "-Wswitch-default" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# pragma clang diagnostic ignored "-Wdocumentation" +# pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +# pragma clang diagnostic ignored "-Wextra-semi" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# pragma clang diagnostic ignored "-Wc++98-compat" +# pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +# pragma clang diagnostic ignored "-Wweak-vtables" +# pragma clang diagnostic ignored "-Wswitch" +# pragma clang diagnostic ignored "-Wunused-macros" +# pragma clang diagnostic ignored "-Wextra" +#endif +#define STB_IMAGE_IMPLEMENTATION +#include "../thirdparty/stb/stb_image.h" +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +namespace Lunar { + +CPUTexture::CPUTexture(std::filesystem::path const &path) +{ + int width_out = 0; + int height_out = 0; + int channels_out = 0; + stbi_uc *data = stbi_load(path.string().c_str(), &width_out, &height_out, + &channels_out, STBI_rgb_alpha); + if (!data) { + throw std::runtime_error( + std::format("Failed to load texture: {}", path.string())); + } + + width = static_cast(width_out); + height = static_cast(height_out); + format = vk::Format::eR8G8B8A8Unorm; + pixels.assign(data, data + (width * height * 4)); + stbi_image_free(data); +} + +} // namespace Lunar diff --git a/src/CPUTexture.h b/src/CPUTexture.h new file mode 100644 index 0000000..df39161 --- /dev/null +++ b/src/CPUTexture.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +#include + +namespace Lunar { + +struct CPUTexture { + std::vector pixels; + uint32_t width { 0 }; + uint32_t height { 0 }; + vk::Format format { vk::Format::eR8G8B8A8Unorm }; + + explicit CPUTexture(std::filesystem::path const &path); +}; + +} // namespace Lunar diff --git a/src/VulkanRenderer.cpp b/src/VulkanRenderer.cpp index 185cc26..feb4687 100644 --- a/src/VulkanRenderer.cpp +++ b/src/VulkanRenderer.cpp @@ -1302,15 +1302,7 @@ auto VulkanRenderer::default_data_init() -> void m_vk.rectangle = upload_mesh(rect_indices, rect_vertices); - m_vk.test_meshes - = Mesh::load_gltf_meshes(*this, "assets/basicmesh.glb").value(); - m_vk.deletion_queue.emplace([&]() { - for (auto &mesh : m_vk.test_meshes) { - destroy_buffer(mesh->mesh_buffers.index_buffer); - destroy_buffer(mesh->mesh_buffers.vertex_buffer); - } - destroy_buffer(m_vk.rectangle.index_buffer); destroy_buffer(m_vk.rectangle.vertex_buffer); }); @@ -2162,6 +2154,14 @@ auto VulkanRenderer::create_image(void const *data, vk::Extent3D size, return new_image; } +auto VulkanRenderer::create_image(CPUTexture const &texture, + vk::ImageUsageFlags flags, bool mipmapped) -> AllocatedImage +{ + vk::Extent3D size { texture.width, texture.height, 1 }; + return create_image( + texture.pixels.data(), size, texture.format, flags, mipmapped); +} + auto VulkanRenderer::destroy_image(AllocatedImage const &img) -> void { if (img.image_view) { diff --git a/src/VulkanRenderer.h b/src/VulkanRenderer.h index 8893c83..048fd1e 100644 --- a/src/VulkanRenderer.h +++ b/src/VulkanRenderer.h @@ -15,6 +15,7 @@ #include #include +#include "CPUTexture.h" #include "Colors.h" #include "DeletionQueue.h" #include "Loader.h" @@ -140,14 +141,13 @@ struct VulkanRenderer { bool clear_frame_descriptors = true) -> void; auto upload_mesh(std::span indices, std::span vertices) -> GPUMeshBuffers; + auto destroy_buffer(AllocatedBuffer const &buffer) -> void; + auto create_image(CPUTexture const &texture, vk::ImageUsageFlags flags, + bool mipmapped = false) -> AllocatedImage; auto rectangle_mesh() const -> GPUMeshBuffers const & { return m_vk.rectangle; } - auto test_meshes() const -> std::vector> const & - { - return m_vk.test_meshes; - } auto white_texture() const -> AllocatedImage const & { return m_vk.white_image; @@ -167,6 +167,23 @@ struct VulkanRenderer { auto draw_extent() const -> vk::Extent2D { return m_vk.draw_extent; } auto mesh_pipeline() -> Pipeline & { return m_vk.mesh_pipeline; } auto triangle_pipeline() -> Pipeline & { return m_vk.triangle_pipeline; } + auto device() const -> vk::Device { return m_device; } + auto draw_image_format() const -> vk::Format + { + return m_vk.draw_image.format; + } + auto depth_image_format() const -> vk::Format + { + return m_vk.depth_image.format; + } + auto msaa_samples() const -> vk::SampleCountFlagBits + { + return m_vk.msaa_samples; + } + auto single_image_descriptor_layout() const -> vk::DescriptorSetLayout + { + return m_vk.single_image_descriptor_layout; + } auto gl_api() -> GL & { return gl; } auto get_screenshot() const -> std::optional { @@ -249,7 +266,6 @@ private: auto create_buffer(size_t alloc_size, vk::BufferUsageFlags usage, VmaMemoryUsage memory_usage) -> AllocatedBuffer; - auto destroy_buffer(AllocatedBuffer const &buffer) -> void; auto enqueue_render_command(RenderCommand &&command) -> void; auto process_render_commands() -> void; auto apply_antialiasing(AntiAliasingKind kind) -> void; @@ -326,8 +342,6 @@ private: uint64_t frame_number { 0 }; - std::vector> test_meshes; - AllocatedImage white_image {}; AllocatedImage black_image {}; AllocatedImage gray_image {};