diff --git a/.gitignore b/.gitignore index 1e66a681..f05914af 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ # Temporary files to ignore compile_commands.json +settings.VisualStudio.json *.patch *.cso *.sln diff --git a/source/code/core/core/public/ice/profiler.hxx b/source/code/core/core/public/ice/profiler.hxx index b7f2cd85..593cc0d5 100644 --- a/source/code/core/core/public/ice/profiler.hxx +++ b/source/code/core/core/public/ice/profiler.hxx @@ -34,6 +34,18 @@ # define IPT_DEALLOC_POOL( ptr, name ) TracyFreeN( ptr, ice::string::begin(name) ) # define IPT_MESSAGE( txt ) TracyMessage( txt, ice::count(txt) ) +# define IPT_MESSAGE_STR( txt ) TracyMessage( ice::string::begin(txt), ice::string::size(txt) ) + + +# if defined(TRACY_FIBERS) +# define IPT_FIBERS 1 +# define IPT_FIBER_START( name ) TracyFiberEnter( name ) +# define IPT_FIBER_END TracyFiberLeave +# else +# define IPT_FIBERS 0 +# define IPT_FIBER_START( name ) +# define IPT_FIBER_END +# endif #else // #if ICE_PROFILE @@ -55,5 +67,18 @@ # define IPT_DEALLOC_POOL( ptr, name ) # define IPT_MESSAGE( txt ) +# define IPT_MESSAGE_STR( txt ) + +# define IPT_FIBERS 0 +# define IPT_FIBER_START( name ) +# define IPT_FIBER_END #endif // #if ICE_PROFILE + +namespace ice::profiling +{ + + static constexpr bool is_enabled = IPT_ENABLED; + static constexpr bool has_fiber_support = IPT_FIBERS; + +} // namespace ice::profiling diff --git a/source/code/core/devui/private/devui_imgui.cxx b/source/code/core/devui/private/devui_imgui.cxx index 46b28857..e407f187 100644 --- a/source/code/core/devui/private/devui_imgui.cxx +++ b/source/code/core/devui/private/devui_imgui.cxx @@ -2,6 +2,7 @@ /// SPDX-License-Identifier: MIT #include +#include #include namespace ice::detail @@ -19,6 +20,25 @@ namespace ice::detail } #endif + auto textinput_heapstring_callback(ImGuiInputTextCallbackData* data) noexcept -> ice::i32 + { + ice::HeapString<>* const str = reinterpret_cast*>(data->UserData); + ICE_ASSERT_CORE(str != nullptr); + + if ((data->EventFlag & ImGuiInputTextFlags_CallbackResize) == ImGuiInputTextFlags_CallbackResize) + { + ICE_ASSERT_CORE(ice::string::begin(*str) == data->Buf); + if (ice::string::capacity(*str) <= ice::ucount(data->BufTextLen)) + { + ice::string::grow(*str, data->BufSize); + } + + ice::string::resize(*str, data->BufTextLen); + data->Buf = ice::string::begin(*str); + } + return 0; + } + } // namespace ice namespace ImGui @@ -39,6 +59,20 @@ namespace ImGui ImGui::TextEx(begin, end, ImGuiTextFlags_NoWidthForLargeClippedText); } + bool InputText(ice::String label, ice::HeapString<>& out_string, ImGuiInputTextFlags flags) noexcept + { + ice::string::reserve(out_string, 1); + + return ImGui::InputText( + ice::string::begin(label), + ice::string::begin(out_string), + ice::string::capacity(out_string), + flags | ImGuiInputTextFlags_CallbackResize, + ice::detail::textinput_heapstring_callback, + ice::addressof(out_string) + ); + } + bool BeginLargeButton(std::string_view label, int& inout_status, ImVec2 const& size_arg, ImGuiButtonFlags flags) noexcept { ImGuiWindow* window = GetCurrentWindow(); diff --git a/source/code/core/devui/public/ice/devui_imgui.hxx b/source/code/core/devui/public/ice/devui_imgui.hxx index eebfd793..12721d00 100644 --- a/source/code/core/devui/public/ice/devui_imgui.hxx +++ b/source/code/core/devui/public/ice/devui_imgui.hxx @@ -18,6 +18,35 @@ namespace ImGui { + // Helpers + + inline bool Begin(ice::String name, bool* inout_open = nullptr, ImGuiWindowFlags flags = 0) noexcept + { + return ImGui::Begin(ice::string::begin(name), inout_open, flags); + } + + inline bool BeginListBox(ice::String label, ice::vec2f size = {}) noexcept + { + return ImGui::BeginListBox(ice::string::begin(label), ImVec2{ size.x, size.y }); + } + + inline void TextUnformatted(ice::String text) noexcept + { + ImGui::TextUnformatted(ice::string::begin(text), ice::string::end(text)); + } + + inline bool Selectable( + ice::String label, + bool selected = false, + ImGuiSelectableFlags flags = 0, + ice::vec2f size = {} + ) noexcept + { + return ImGui::Selectable(ice::begin(label), selected, flags, ImVec2{ size.x, size.y }); + } + + // Extensions + namespace Detail { @@ -84,6 +113,12 @@ namespace ImGui ImGui::PopStyleColor(); } + bool InputText( + ice::String label, + ice::HeapString<>& out_string, + ImGuiInputTextFlags flags = ImGuiInputTextFlags_None + ) noexcept; + bool BeginLargeButton( std::string_view label, int& inout_status, diff --git a/source/code/core/memsys/public/ice/mem_allocator_utils.hxx b/source/code/core/memsys/public/ice/mem_allocator_utils.hxx index 4c0f74ff..b78ff5b3 100644 --- a/source/code/core/memsys/public/ice/mem_allocator_utils.hxx +++ b/source/code/core/memsys/public/ice/mem_allocator_utils.hxx @@ -9,6 +9,11 @@ namespace ice inline auto data_copy(ice::Allocator& alloc, ice::Data data) noexcept -> ice::Memory { + if (data.size == 0_B) + { + return {}; + } + ice::Memory const result = alloc.allocate({ data.size, data.alignment }); if (result.location != nullptr) { diff --git a/source/code/core/tasks/public/ice/impl/task_utils.inl b/source/code/core/tasks/public/ice/impl/task_utils.inl index 5727a3dd..37d010ee 100644 --- a/source/code/core/tasks/public/ice/impl/task_utils.inl +++ b/source/code/core/tasks/public/ice/impl/task_utils.inl @@ -14,6 +14,12 @@ namespace ice out_result = co_await ice::move(task); } + template + auto output_result_task(ice::TaskExpected task, ice::Expected& out_result) noexcept -> ice::Task<> + { + out_result = co_await ice::move(task); + } + } // namespace detail inline auto resume_on(ice::TaskScheduler& scheduler) noexcept @@ -118,4 +124,13 @@ namespace ice barrier.wait(); } + template + inline auto wait_for_expected(ice::TaskExpected task) noexcept -> ice::Expected + { + ice::Expected result; + ice::wait_for(ice::detail::output_result_task(ice::move(task), result)); + return result; + } + } // namespace ice + diff --git a/source/code/core/tasks/public/ice/task_expected_promise.hxx b/source/code/core/tasks/public/ice/task_expected_promise.hxx index 0fb2064e..ba11b4cc 100644 --- a/source/code/core/tasks/public/ice/task_expected_promise.hxx +++ b/source/code/core/tasks/public/ice/task_expected_promise.hxx @@ -73,7 +73,7 @@ namespace ice inline void return_value(TypeExpected&& expected) noexcept(std::is_nothrow_move_constructible_v) { ICE_ASSERT_CORE(expected.valid()); - if (expected) + if (expected.succeeded()) { new (&_value) Result{ ice::forward(expected).value() }; } diff --git a/source/code/core/tasks/public/ice/task_utils.hxx b/source/code/core/tasks/public/ice/task_utils.hxx index 448f278f..830cc3ff 100644 --- a/source/code/core/tasks/public/ice/task_utils.hxx +++ b/source/code/core/tasks/public/ice/task_utils.hxx @@ -103,6 +103,11 @@ namespace ice void manual_wait_for_scheduled(ice::ManualResetBarrier& evnt, ice::Span> tasks, ice::TaskScheduler& scheduler) noexcept; + //////////////////////////////////////////////////////////////// + + template + inline auto wait_for_expected(ice::TaskExpected task) noexcept -> ice::Expected; + //////////////////////////////////////////////////////////////// diff --git a/source/code/core/utils/public/ice/expected.hxx b/source/code/core/utils/public/ice/expected.hxx index 741dbfec..5c57a25f 100644 --- a/source/code/core/utils/public/ice/expected.hxx +++ b/source/code/core/utils/public/ice/expected.hxx @@ -40,7 +40,7 @@ namespace ice { if (_state == 1u) // Already checking our state { - new (ice::addressof(_value)) Value { ice::forward(other)._value }; + new (ice::addressof(_value)) Value { ice::forward(other) }; } else { diff --git a/source/code/iceshard/engine/public/ice/engine_asset_categories.hxx b/source/code/iceshard/engine/public/ice/engine_asset_categories.hxx new file mode 100644 index 00000000..2322da57 --- /dev/null +++ b/source/code/iceshard/engine/public/ice/engine_asset_categories.hxx @@ -0,0 +1,15 @@ +/// Copyright 2025 - 2025, Dandielo +/// SPDX-License-Identifier: MIT + +#pragma once +#include +#include + +namespace ice +{ + + static constexpr ice::AssetCategory AssetCategory_StaticObject = ice::make_asset_category("ice/object/static-object"); + static constexpr ice::AssetCategory AssetCategory_DynamicObject = ice::make_asset_category("ice/object/dynamic-object"); + static constexpr ice::AssetCategory AssetCategory_Tileset = ice::make_asset_category("ice/object/tileset"); + +} // namespace ice diff --git a/source/code/iceshard/iceshard/private/gfx/traits/iceshard_gfx_image_storage_trait.cxx b/source/code/iceshard/iceshard/private/gfx/traits/iceshard_gfx_image_storage_trait.cxx index 21553968..007cae75 100644 --- a/source/code/iceshard/iceshard/private/gfx/traits/iceshard_gfx_image_storage_trait.cxx +++ b/source/code/iceshard/iceshard/private/gfx/traits/iceshard_gfx_image_storage_trait.cxx @@ -36,12 +36,51 @@ namespace ice::gfx Trait_GfxImageStorage::Trait_GfxImageStorage(ice::TraitContext& ctx, ice::Allocator& alloc) noexcept : ice::Trait{ ctx } - , _loaded_images{ alloc } + , ice::TraitDevUI{ {.category = "Engine/Gfx", .name = "Images"} } + , _allocator{ alloc, "gfx-image-storage" } + , _loaded_images{ _allocator } { _context.bind<&Trait_GfxImageStorage::gfx_update, Render>(ice::gfx::ShardID_RenderFrameUpdate); _context.bind<&Trait_GfxImageStorage::gfx_shutdown, Render>(ice::gfx::ShardID_GfxShutdown); } + void Trait_GfxImageStorage::build_content() noexcept + { + static ice::i32 selected = -1; + ice::Span images = ice::hashmap::values(_loaded_images); + + ice::String const preview = selected < 0 ? "" : ice::stringid_hint(images[selected].asset.name()); + + if (ImGui::BeginCombo("Loaded Image", ice::string::begin(preview))) + { + if (ImGui::Selectable("##empty")) + { + selected = -1; + } + + ice::i32 idx = 0; + for (GfxImageEntry const& entry : images) + { + if (ImGui::Selectable(ice::stringid_hint(entry.asset.name()), selected == idx)) + { + selected = idx; + } + idx += 1; + } + ImGui::EndCombo(); + } + + if (selected >= 0) + { + GfxImageEntry const& selected_entry = images[selected]; + ImTextureRef const ref{ ImTextureID(selected_entry.image) }; + float const avail_width = ImGui::GetContentRegionAvail().x; + ImVec2 const size{ avail_width, avail_width }; + + ImGui::Image(ref, size); + } + } + auto Trait_GfxImageStorage::on_asset_released(ice::Asset const& asset) noexcept -> ice::Task<> { GfxImageEntry* entry = ice::hashmap::try_get(_loaded_images, ice::hash(asset.name())); @@ -55,81 +94,98 @@ namespace ice::gfx ice::AssetStorage& assets ) noexcept -> ice::Task<> { - // Handle up to 4 requests at the same time each frame. + using namespace ice::render; + ice::AssetRequest* request = assets.aquire_request(ice::render::AssetCategory_Texture2D, AssetState::Runtime); - while(request != nullptr) + ice::AssetResolveData resolve_success{ .resolver = this, .result = AssetRequestResult::Success }; + + ice::u32 upload_count = 0; + ice::AssetRequest* uploading_requests[4]; + ice::render::Buffer transfer_buffers[4]; + ice::render::BufferUpdateInfo update_infos[4]; + ice::render::Image created_images[4]; + ice::vec2u created_extents[4]; + + while(request != nullptr && upload_count < 4) { ice::AssetState const state = request->state(); ICE_ASSERT_CORE(state == AssetState::Loaded); // The image needs to be loaded. - ice::StringID const image_hash = request->asset_name(); - GfxImageEntry* entry = ice::hashmap::try_get(_loaded_images, ice::hash(image_hash)); - ICE_ASSERT_CORE(entry == nullptr || entry->released); - - using namespace ice::render; - RenderDevice& device = params.context.device(); - + ice::StringID const nameid = request->asset_name(); + GfxImageEntry* entry = ice::hashmap::try_get(_loaded_images, ice::hash(nameid)); if (entry && entry->image != Image::Invalid) { - // Destroy the previous image object, as it might be outdated. - device.destroy_image(entry->image); - } + // Allocates a handle for it... (TODO: Rework?) + resolve_success.memory = request->allocate(ice::size_of); + *reinterpret_cast(resolve_success.memory.location) = entry->image; - ice::Data const metadata_data = request->metadata(); - if (metadata_data.location == nullptr) - { - request->resolve({ .result = ice::AssetRequestResult::Error }); + // Return the existing image object + // TODO: Handle updates in later version. + ice::Asset asset_result = request->resolve(resolve_success); + + // Get the next queued request request = assets.aquire_request(ice::render::AssetCategory_Texture2D, AssetState::Runtime); continue; } - ice::Config const meta = ice::config::from_data(metadata_data); - - ice::i32 image_format; - ice::vec2i size; - - [[maybe_unused]] - bool valid_data = ice::config::get(meta, "texture.format", image_format); - valid_data &= ice::config::get(meta, "texture.size.x", size.x); - valid_data &= ice::config::get(meta, "texture.size.y", size.y); + ICE_ASSERT_CORE(entry == nullptr || entry->released); - [[maybe_unused]] - ice::Data const texture_data = request->data(); + // The render device + RenderDevice& device = params.context.device(); - // Creates the image object - ImageInfo image_info{ - .type = ImageType::Image2D, - .format = (ImageFormat) image_format, - .usage = ImageUsageFlags::Sampled | ImageUsageFlags::TransferDst, - .width = (ice::u32) size.x, - .height = (ice::u32) size.y, + // Get the image data + Data const request_data = request->data(); + ImageInfo const* image_info = reinterpret_cast(request_data.location); + Data const image_data{ + .location = image_info->data, + .size = ice::usize::subtract(request_data.size, ice::size_of), + .alignment = ice::ualign::b_4, }; - Image image = device.create_image(image_info, texture_data); - if (image == Image::Invalid) + + // Create image and transfer buffer + created_images[upload_count] = device.create_image(*image_info, image_data); + if (created_images[upload_count] == Image::Invalid) { request->resolve({ .result = AssetRequestResult::Error }); - co_return; + continue; } - ice::render::Buffer transfer_buffer = device.create_buffer(BufferType::Transfer, ice::u32(texture_data.size.value)); - BufferUpdateInfo const buffer_updates[]{ - BufferUpdateInfo{ - .buffer = transfer_buffer, - .data = texture_data, - .offset = 0 - } + // Store upload data + uploading_requests[upload_count] = request; + + created_extents[upload_count].x = image_info->width; + created_extents[upload_count].y = image_info->height; + + transfer_buffers[upload_count] = device.create_buffer(BufferType::Transfer, ice::u32(image_data.size.value)); + update_infos[upload_count] = BufferUpdateInfo{ + .buffer = transfer_buffers[upload_count], + .data = image_data, + .offset = 0 }; - device.update_buffers(buffer_updates); + // Increase the upload count + upload_count += 1; + + // Get the next queued request + request = assets.aquire_request(ice::render::AssetCategory_Texture2D, AssetState::Runtime); + } + + if (upload_count > 0) + { + // The render device + RenderDevice& device = params.context.device(); + device.update_buffers({ update_infos, upload_count }); + + TaskStage const stage_transfer = params.stages.frame_transfer; + TaskStage const stage_end = params.stages.frame_end; RenderCommands& api = device.get_commands(); - CommandBuffer const cmds = co_await params.stages.frame_transfer; + CommandBuffer const cmds = co_await stage_transfer; ImageBarrier barriers[4]{ }; - - for (ice::u32 idx = 0; idx < 1; ++idx) + for (ice::u32 idx = 0; idx < upload_count; ++idx) { - barriers[idx].image = image; + barriers[idx].image = created_images[idx]; barriers[idx].source_layout = ImageLayout::Undefined; barriers[idx].destination_layout = ImageLayout::TransferDstOptimal; barriers[idx].source_access = AccessFlags::None; @@ -138,21 +194,24 @@ namespace ice::gfx api.pipeline_image_barrier( cmds, - PipelineStage::TopOfPipe, + PipelineStage::BottomOfPipe, PipelineStage::Transfer, - { barriers, 1 } + { barriers, upload_count } ); - api.update_texture_v2( - cmds, - image, - transfer_buffer, - ice::vec2u{ size } - ); + for (ice::u32 idx = 0; idx < upload_count; ++idx) + { + api.update_texture_v2( + cmds, + created_images[idx], + transfer_buffers[idx], + created_extents[idx] + ); + } - for (ice::u32 idx = 0; idx < 1; ++idx) + for (ice::u32 idx = 0; idx < upload_count; ++idx) { - barriers[idx].image = image; + barriers[idx].image = created_images[idx]; barriers[idx].source_layout = ImageLayout::TransferDstOptimal; barriers[idx].destination_layout = ImageLayout::ShaderReadOnly; barriers[idx].source_access = AccessFlags::TransferWrite; @@ -163,33 +222,40 @@ namespace ice::gfx cmds, PipelineStage::Transfer, PipelineStage::FramentShader, - { barriers, 1 } + { barriers, upload_count } ); - co_await params.stages.frame_end; - - device.destroy_buffer(transfer_buffer); - - ICE_LOG(LogSeverity::Info, LogTag::Game, "TextureStorage - Loaded image: {}", request->asset_name()); - - // Allocates a handle for it... (TODO: Rework?) - ice::Memory const result = request->allocate(ice::size_of); - *reinterpret_cast(result.location) = image; - - // Reslove the request (will resume all awaiting tasks) - ice::Asset asset = request->resolve({ .resolver = this, .result = AssetRequestResult::Success, .memory = result }); - // send("iceshard:images-internal:loaded"_shardid, asset); - - // Save the image handle - ice::hashmap::set(_loaded_images, ice::hash(image_hash), { .asset = ice::move(asset), .image = image}); + co_await stage_end; - // // Reslove the request (will resume all awaiting tasks) - // request->resolve({ .resolver = this, .result = AssetRequestResult::Success, .memory = result }); + for (ice::u32 idx = 0; idx < upload_count; ++idx) + { + device.destroy_buffer(transfer_buffers[idx]); + } - // Get the next queued request - request = assets.aquire_request(ice::render::AssetCategory_Texture2D, AssetState::Runtime); + for (ice::u32 idx = 0; idx < upload_count; ++idx) + { + ice::AssetRequest* const uploaded_request = uploading_requests[idx]; + ICE_LOG( + LogSeverity::Info, LogTag::Game, + "TextureStorage - Loaded image: {}", + uploaded_request->asset_name() + ); + + // Allocates a handle for it... (TODO: Rework?) + resolve_success.memory = uploaded_request->allocate(ice::size_of); + *reinterpret_cast(resolve_success.memory.location) = created_images[idx]; + + ice::u64 const asset_hash = ice::hash(uploaded_request->asset_name()); + ice::Asset asset = uploaded_request->resolve(resolve_success); + + // Save the image handle + ice::hashmap::set( + _loaded_images, + asset_hash, + { .asset = ice::move(asset), .image = created_images[idx] } + ); + } } - co_return; } diff --git a/source/code/iceshard/iceshard/private/gfx/traits/iceshard_gfx_image_storage_trait.hxx b/source/code/iceshard/iceshard/private/gfx/traits/iceshard_gfx_image_storage_trait.hxx index 294012d6..8418cd5e 100644 --- a/source/code/iceshard/iceshard/private/gfx/traits/iceshard_gfx_image_storage_trait.hxx +++ b/source/code/iceshard/iceshard/private/gfx/traits/iceshard_gfx_image_storage_trait.hxx @@ -8,6 +8,7 @@ #include #include #include +#include namespace ice::gfx { @@ -21,12 +22,18 @@ namespace ice::gfx class Trait_GfxImageStorage final : public ice::Trait + , public ice::TraitDevUI , public ice::AssetRequestResolver + , public ice::InterfaceSelectorOf { public: // Implementation of: ice::Trait Trait_GfxImageStorage(ice::TraitContext& ctx, ice::Allocator& alloc) noexcept; ~Trait_GfxImageStorage() noexcept override = default; + public: // Implementation of: ice::TraitDevUI + auto trait_name() const noexcept -> ice::String override { return "Gfx.ImageStorage"; } + void build_content() noexcept override; + public: // Implementation of: ice::AssetRequestResolver auto on_asset_released(ice::Asset const& asset) noexcept -> ice::Task<> override; @@ -41,6 +48,7 @@ namespace ice::gfx ) noexcept -> ice::Task<>; private: + ice::ProxyAllocator _allocator; ice::HashMap _loaded_images; }; diff --git a/source/code/modules/imgui_module/private/imgui_trait.cxx b/source/code/modules/imgui_module/private/imgui_trait.cxx index c72a96b8..1fc0581c 100644 --- a/source/code/modules/imgui_module/private/imgui_trait.cxx +++ b/source/code/modules/imgui_module/private/imgui_trait.cxx @@ -173,30 +173,41 @@ namespace ice::devui : AssetDataBinding{ .state = AssetState::Loaded } , _allocator{ alloc } { + using namespace ice::render; + ice::Data const texture_data{ texture.Pixels, ice::usize(texture.GetSizeInBytes()), - ice::ualign::b_default + ice::ualign::b_1 }; - ice::ConfigBuilder config{ _allocator }; - ice::ConfigBuilderValue mtex = config["texture"]; - mtex["format"] = ice::i32(ice::render::ImageFormat::UNORM_RGBA); - mtex["size"]["x"] = ice::i32(texture.Width); - mtex["size"]["y"] = ice::i32(texture.Height); - _metadata = config.finalize(_allocator); - - this->content = texture_data; - this->metadata = ice::data_view(_metadata); + _texdata = alloc.allocate( + ice::AllocRequest{ + ice::size_of + texture_data.size, + ice::align_of + } + ); + + ice::Memory texmem = ice::ptr_add(_texdata, ice::size_of); + ice::memcpy(texmem, { texture_data }); + + ImageInfo* const info = reinterpret_cast(_texdata.location); + info->width = texture.Width; + info->height = texture.Height; + info->format = ImageFormat::UNORM_RGBA; + info->type = ImageType::Image2D; + info->usage = ImageUsageFlags::Sampled | ImageUsageFlags::TransferDst; + info->data = texmem.location; + this->content = ice::data_view(_texdata); } ~ImTextureAssetDataBinding() { - _allocator.deallocate(_metadata); + _allocator.deallocate(_texdata); } ice::Allocator& _allocator; - ice::Memory _metadata; + ice::Memory _texdata; }; inline auto total_command_count(ImDrawData const& draw_data) noexcept -> ice::u32 diff --git a/source/code/modules/vulkan_renderer/private/vk_utility.hxx b/source/code/modules/vulkan_renderer/private/vk_utility.hxx index 2715e880..205ffe4b 100644 --- a/source/code/modules/vulkan_renderer/private/vk_utility.hxx +++ b/source/code/modules/vulkan_renderer/private/vk_utility.hxx @@ -220,6 +220,8 @@ namespace ice::render::vk { switch (stage) { + case PipelineStage::BottomOfPipe: + return VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; case PipelineStage::TopOfPipe: return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; case PipelineStage::Transfer: diff --git a/source/code/platforms/platform_linux/private/linux_sdl2_utils.cxx b/source/code/platforms/platform_linux/private/linux_sdl2_utils.cxx index 3b2db9f7..c7786940 100644 --- a/source/code/platforms/platform_linux/private/linux_sdl2_utils.cxx +++ b/source/code/platforms/platform_linux/private/linux_sdl2_utils.cxx @@ -65,6 +65,8 @@ namespace ice::platform::linux::sdl2 case SDL_SCANCODE_MINUS: return KeyboardKey::Minus; case SDL_SCANCODE_PERIOD: return KeyboardKey::Period; case SDL_SCANCODE_SLASH: return KeyboardKey::Slash; + case SDL_SCANCODE_HOME: return KeyboardKey::Home; + case SDL_SCANCODE_END: return KeyboardKey::End; case SDL_SCANCODE_SEMICOLON: return KeyboardKey::SemiColon; case SDL_SCANCODE_EQUALS: return KeyboardKey::Equals; diff --git a/source/code/platforms/platform_win32/private/win32_sdl2_utils.cxx b/source/code/platforms/platform_win32/private/win32_sdl2_utils.cxx index fd84610e..6874a6e3 100644 --- a/source/code/platforms/platform_win32/private/win32_sdl2_utils.cxx +++ b/source/code/platforms/platform_win32/private/win32_sdl2_utils.cxx @@ -65,6 +65,8 @@ namespace ice::platform::win32::sdl2 case SDL_SCANCODE_MINUS: return KeyboardKey::Minus; case SDL_SCANCODE_PERIOD: return KeyboardKey::Period; case SDL_SCANCODE_SLASH: return KeyboardKey::Slash; + case SDL_SCANCODE_HOME: return KeyboardKey::Home; + case SDL_SCANCODE_END: return KeyboardKey::End; case SDL_SCANCODE_SEMICOLON: return KeyboardKey::SemiColon; case SDL_SCANCODE_EQUALS: return KeyboardKey::Equals; diff --git a/source/code/systems/asset_system/private/asset_data.hxx b/source/code/systems/asset_system/private/asset_data.hxx index 697db15d..3f657706 100644 --- a/source/code/systems/asset_system/private/asset_data.hxx +++ b/source/code/systems/asset_system/private/asset_data.hxx @@ -226,9 +226,12 @@ namespace ice ice::UniquePtr result = ice::create_asset_data_entry( alloc, state, data_alloc, ice::data_copy(data_alloc, asset_data) ); - result->_next = ice::create_asset_data_entry( - alloc, state, data_alloc, ice::data_copy(data_alloc, metadata_data), AssetDataFlags::Metadata - ); + if (metadata_data.size > 0_B) + { + result->_next = ice::create_asset_data_entry( + alloc, state, data_alloc, ice::data_copy(data_alloc, metadata_data), AssetDataFlags::Metadata + ); + } return result; } diff --git a/source/code/systems/asset_system/private/asset_request_awaitable.cxx b/source/code/systems/asset_system/private/asset_request_awaitable.cxx index 27bf35e5..accb49c9 100644 --- a/source/code/systems/asset_system/private/asset_request_awaitable.cxx +++ b/source/code/systems/asset_system/private/asset_request_awaitable.cxx @@ -95,7 +95,6 @@ namespace ice _transaction.set_result_data(_asset_shelve.asset_allocator(), _transaction.target_state, resolve_data); _result_data = resolve_data.memory; - _transaction.asset._refcount.fetch_add(1, std::memory_order_release); asset_handle = Asset{ ice::addressof(_transaction.asset) }; } diff --git a/source/code/systems/asset_system/private/asset_shelve.cxx b/source/code/systems/asset_system/private/asset_shelve.cxx index 2a2efdff..f955deb3 100644 --- a/source/code/systems/asset_system/private/asset_shelve.cxx +++ b/source/code/systems/asset_system/private/asset_shelve.cxx @@ -69,7 +69,7 @@ namespace ice _allocator, AssetState::Exists, resource ); - ice::u64 const name_hash = ice::hash(name); + ice::u64 const name_hash = ice::hash(ice::stringid(name)); if constexpr (ice::AssetEntry::HoldsDebugData) { ice::HeapString<> asset_name{ _allocator, name }; @@ -108,7 +108,7 @@ namespace ice data_binding.metadata ); - ice::u64 const name_hash = ice::hash(name); + ice::u64 const name_hash = ice::hash(ice::stringid(name)); if constexpr (ice::AssetEntry::HoldsDebugData) { ice::HeapString<> asset_name{ _allocator, name }; diff --git a/source/code/systems/asset_system/public/ice/asset_category_archive.hxx b/source/code/systems/asset_system/public/ice/asset_category_archive.hxx index 1029e32b..79d614ea 100644 --- a/source/code/systems/asset_system/public/ice/asset_category_archive.hxx +++ b/source/code/systems/asset_system/public/ice/asset_category_archive.hxx @@ -12,6 +12,9 @@ namespace ice struct AssetCategoryDefinition { + constexpr auto valid() const noexcept { return ice::span::any(resource_extensions); } + + ice::String name; ice::Span asset_params; ice::Span resource_extensions; diff --git a/source/code/systems/render_system/public/ice/render/render_pipeline.hxx b/source/code/systems/render_system/public/ice/render/render_pipeline.hxx index f2fc107c..c4c3ca22 100644 --- a/source/code/systems/render_system/public/ice/render/render_pipeline.hxx +++ b/source/code/systems/render_system/public/ice/render/render_pipeline.hxx @@ -20,6 +20,7 @@ namespace ice::render enum class PipelineStage : ice::u32 { + BottomOfPipe, TopOfPipe, Transfer, ColorAttachmentOutput, diff --git a/source/code/systems/resource_system/natvis/resource.natvis b/source/code/systems/resource_system/natvis/resource.natvis index b1ccde25..f720c601 100644 --- a/source/code/systems/resource_system/natvis/resource.natvis +++ b/source/code/systems/resource_system/natvis/resource.natvis @@ -3,16 +3,19 @@ - URI {{ path={_uri,sb} }} - URI {{ urn, path={_uri,s8b} }} - URI {{ file, path={_uri,s8b} }} + URI {{ urn, path={_uri,s8b} }} + URI {{ file, path={_uri,s8b} }} + URI {{ hailstorm, path={_uri,s8b} }} URI {{ dynlib, path={_uri,s8b} }} + URI {{ path={_uri,sb} }} - urn - file + urn + file + hailstorm dynlib + invalid _uri,sb diff --git a/source/code/systems/resource_system/private/resource_filesystem_loose.cxx b/source/code/systems/resource_system/private/resource_filesystem_loose.cxx index 4ae6dfc3..d2eb7efe 100644 --- a/source/code/systems/resource_system/private/resource_filesystem_loose.cxx +++ b/source/code/systems/resource_system/private/resource_filesystem_loose.cxx @@ -56,6 +56,7 @@ namespace ice ice::string::push_back(native_filepath, ISP_PATH_LITERAL(".isrm")); } + IPT_MESSAGE_STR(filepath); ice::Expected handle = ice::native_file::open_file(aioport, native_filepath); if (handle) { diff --git a/source/code/systems/resource_system/private/resource_provider_filesystem.cxx b/source/code/systems/resource_system/private/resource_provider_filesystem.cxx index 011fb641..cbaec23b 100644 --- a/source/code/systems/resource_system/private/resource_provider_filesystem.cxx +++ b/source/code/systems/resource_system/private/resource_provider_filesystem.cxx @@ -4,6 +4,8 @@ #include "resource_provider_filesystem.hxx" #include "resource_provider_hailstorm.hxx" #include "resource_filesystem_baked.hxx" +#include +#include namespace ice { @@ -52,6 +54,58 @@ namespace ice return ice::Scheme_File; } + auto FileSystemResourceProvider::filter_resource_uris( + ice::ResourceFilter const& filter, + ice::Array& out_uris + ) noexcept -> ice::TaskExpected + { + ice::ucount collected = 0; + for (ice::FileSystemResource const* resource : _resources) + { + if (filter.allows_resource(resource)) + { + ice::Data metadata_data{}; + if (filter.requires_metadata()) + { + metadata_data = co_await load_resource(resource, "meta"); + + if (filter.filter_thread() != nullptr) + { + co_await *filter.filter_thread(); + } + + if (metadata_data.location == nullptr || metadata_data.size == 0_B) + { + continue; + } + + ice::Config metadata{}; + ice::Memory metadata_mem{}; + if (reinterpret_cast(metadata_data.location)[0] == '{') + { + metadata = ice::config::from_json(_named_allocator, ice::string::from_data(metadata_data), metadata_mem); + } + else + { + metadata = ice::config::from_data(metadata_data); + } + + if (filter.allows_metadata(metadata) == false) + { + _named_allocator.deallocate(metadata_mem); + continue; + } + + _named_allocator.deallocate(metadata_mem); + } + + ice::array::push_back(out_uris, resource->uri()); + collected += 1; + } + } + co_return collected; + } + auto FileSystemResourceProvider::collect( ice::Array& out_changes ) noexcept -> ice::ucount diff --git a/source/code/systems/resource_system/private/resource_provider_filesystem.hxx b/source/code/systems/resource_system/private/resource_provider_filesystem.hxx index b25e1883..f5c4fe90 100644 --- a/source/code/systems/resource_system/private/resource_provider_filesystem.hxx +++ b/source/code/systems/resource_system/private/resource_provider_filesystem.hxx @@ -44,6 +44,11 @@ namespace ice auto schemeid() const noexcept -> ice::StringID override; + auto filter_resource_uris( + ice::ResourceFilter const& filter, + ice::Array& out_uris + ) noexcept -> ice::TaskExpected override; + auto collect( ice::Array& out_changes ) noexcept -> ice::ucount override; diff --git a/source/code/systems/resource_system/private/resource_tracker.cxx b/source/code/systems/resource_system/private/resource_tracker.cxx index 8a05fc56..b6262904 100644 --- a/source/code/systems/resource_system/private/resource_tracker.cxx +++ b/source/code/systems/resource_system/private/resource_tracker.cxx @@ -3,6 +3,7 @@ #include "resource_tracker.hxx" #include "resource_internal.hxx" +#include #include @@ -80,14 +81,17 @@ namespace ice } auto ResourceTrackerImplementation::attach_writer( - ice::UniquePtr provider + ice::UniquePtr writer ) noexcept -> ice::ResourceWriter* { - ice::ResourceWriter* const result = provider.get(); + ice::ResourceWriter* const result = writer.get(); + ice::ResourceProvider* const provider = attach_provider(ice::move(writer)); + ICE_ASSERT_CORE(provider != nullptr); + ice::multi_hashmap::insert( _resource_writers, - ice::hash(provider->schemeid()), - ice::move(provider) + ice::hash(result->schemeid()), + result ); return result; } @@ -104,12 +108,14 @@ namespace ice this->sync_provider(temp_resources, *provider); } +#if 0 for (auto const& writer : _resource_writers) { ice::array::clear(temp_resources); this->sync_provider(temp_resources, *writer); } +#endif } auto ResourceTrackerImplementation::find_resource( @@ -143,6 +149,41 @@ namespace ice return result; } + auto ResourceTrackerImplementation::filter_resource_provider_uris( + ice::ResourceProvider& provider, + ice::ResourceFilter const& filter, + ice::Array& out_uris + ) const noexcept -> ice::TaskExpected + { + co_return co_await provider.filter_resource_uris(filter, out_uris); + } + + auto ResourceTrackerImplementation::filter_resource_uris( + ice::ResourceFilter const& filter, + ice::Array& out_uris + ) const noexcept -> ice::TaskExpected + { + ice::ucount collected = 0; + for (ice::UniquePtr const& provider : _resource_providers) + { + static constexpr ice::String const keywords[]{ "blocked", "allowed" }; + bool const scheme_allowed = filter.allows_scheme(provider->schemeid()); + bool const hostname_allowed = filter.allows_hostname(provider->hostname()); + ICE_LOG_IF( + !scheme_allowed || !hostname_allowed, + LogSeverity::Debug, LogTag::Engine, + "Provider was filtered out. [scheme: {} ({}), hostname: {} ({})]", + provider->schemeid(), keywords[i32(scheme_allowed)], + provider->hostname(), keywords[i32(hostname_allowed)] + ); + if (scheme_allowed && hostname_allowed) + { + collected += co_await filter_resource_provider_uris(*provider, filter, out_uris); + } + } + co_return collected; + } + auto ResourceTrackerImplementation::set_resource( ice::URI const& uri, ice::ResourceHandle const& resource_handle @@ -239,7 +280,7 @@ namespace ice while (it != nullptr) { [[maybe_unused]] - ice::ResourceWriter* candidate_writer = it.value().get(); + ice::ResourceWriter* candidate_writer = it.value(); // We only allow to create resources for specific writers. if (candidate_writer->hostname() == resource_uri.host()) diff --git a/source/code/systems/resource_system/private/resource_tracker.hxx b/source/code/systems/resource_system/private/resource_tracker.hxx index 22311c68..9853a290 100644 --- a/source/code/systems/resource_system/private/resource_tracker.hxx +++ b/source/code/systems/resource_system/private/resource_tracker.hxx @@ -149,6 +149,17 @@ namespace ice ice::ResourceHandle const& resource_handle ) const noexcept -> ice::ResourceHandle override; + auto filter_resource_provider_uris( + ice::ResourceProvider& provider, + ice::ResourceFilter const& filter, + ice::Array& out_uris + ) const noexcept -> ice::TaskExpected; + + auto filter_resource_uris( + ice::ResourceFilter const& filter, + ice::Array& out_uris + ) const noexcept -> ice::TaskExpected override; + auto set_resource( ice::URI const& uri, @@ -217,7 +228,7 @@ namespace ice ice::HashMap _resources; ice::HashMap, ContainerLogic::Complex> _resource_providers; - ice::HashMap, ContainerLogic::Complex> _resource_writers; + ice::HashMap _resource_writers; ice::UniquePtr _devui_widget; }; diff --git a/source/code/systems/resource_system/private/resource_writer_filesystem.cxx b/source/code/systems/resource_system/private/resource_writer_filesystem.cxx index 333a2f77..cbfeeaf0 100644 --- a/source/code/systems/resource_system/private/resource_writer_filesystem.cxx +++ b/source/code/systems/resource_system/private/resource_writer_filesystem.cxx @@ -3,6 +3,7 @@ #include "resource_writer_filesystem.hxx" #include "resource_filesystem_writable.hxx" +#include namespace ice { @@ -48,6 +49,58 @@ namespace ice return ice::Scheme_File; } + auto FileSystemResourceWriter::filter_resource_uris( + ice::ResourceFilter const& filter, + ice::Array& out_uris + ) noexcept -> ice::TaskExpected + { + ice::ucount collected = 0; + for (ice::FileSystemResource const* resource : _resources) + { + if (filter.allows_resource(resource)) + { + ice::Data metadata_data{}; + if (filter.requires_metadata()) + { + metadata_data = co_await load_resource(resource, "meta"); + + if (filter.filter_thread() != nullptr) + { + co_await *filter.filter_thread(); + } + + if (metadata_data.location == nullptr || metadata_data.size == 0_B) + { + continue; + } + + ice::Config metadata{}; + ice::Memory metadata_mem{}; + if (reinterpret_cast(metadata_data.location)[0] == '{') + { + metadata = ice::config::from_json(_named_allocator, ice::string::from_data(metadata_data), metadata_mem); + } + else + { + metadata = ice::config::from_data(metadata_data); + } + + if (filter.allows_metadata(metadata) == false) + { + _named_allocator.deallocate(metadata_mem); + continue; + } + + _named_allocator.deallocate(metadata_mem); + } + + ice::array::push_back(out_uris, resource->uri()); + collected += 1; + } + } + co_return collected; + } + auto FileSystemResourceWriter::collect( ice::Array& out_changes ) noexcept -> ice::ucount diff --git a/source/code/systems/resource_system/private/resource_writer_filesystem.hxx b/source/code/systems/resource_system/private/resource_writer_filesystem.hxx index e9f61b8f..4be6ef51 100644 --- a/source/code/systems/resource_system/private/resource_writer_filesystem.hxx +++ b/source/code/systems/resource_system/private/resource_writer_filesystem.hxx @@ -43,6 +43,11 @@ namespace ice auto hostname() const noexcept -> ice::String override { return _virtual_hostname; } + auto filter_resource_uris( + ice::ResourceFilter const& filter, + ice::Array& out_uris + ) noexcept -> ice::TaskExpected override; + auto collect( ice::Array& out_changes ) noexcept -> ice::ucount override; diff --git a/source/code/systems/resource_system/public/ice/resource.hxx b/source/code/systems/resource_system/public/ice/resource.hxx index 3972832d..c08e3de4 100644 --- a/source/code/systems/resource_system/public/ice/resource.hxx +++ b/source/code/systems/resource_system/public/ice/resource.hxx @@ -11,8 +11,6 @@ namespace ice { - struct Metadata; - class Resource { public: diff --git a/source/code/systems/resource_system/public/ice/resource_filter.hxx b/source/code/systems/resource_system/public/ice/resource_filter.hxx new file mode 100644 index 00000000..144aba56 --- /dev/null +++ b/source/code/systems/resource_system/public/ice/resource_filter.hxx @@ -0,0 +1,57 @@ +#pragma once +#include +#include +#include +#include + +namespace ice +{ + + class ResourceFilter + { + public: + virtual ~ResourceFilter() noexcept = default; + + virtual auto filter_thread() const noexcept -> ice::TaskScheduler* { return nullptr; } + + virtual bool requires_metadata() const noexcept { return false; } + + virtual bool allows_scheme(ice::StringID_Arg scheme) const noexcept { return true; } + + virtual bool allows_hostname(ice::String hostname) const noexcept { return true; } + + virtual bool allows_resource( + ice::Resource const* resource + ) const noexcept = 0; + + virtual bool allows_metadata( + ice::Config const& metadata + ) const noexcept { return false; } + }; + + class FileResourceFilter : public ResourceFilter + { + public: + FileResourceFilter(ice::String extension, ice::String hostname = {}) noexcept + : _extension{ extension } + , _hostname{ hostname } + { } + + bool allows_hostname(ice::String hostname) const noexcept override + { + return ice::string::empty(_hostname) || hostname == _hostname; + } + + bool allows_resource( + ice::Resource const* resource + ) const noexcept override + { + return ice::string::empty(_extension) || ice::path::extension(resource->origin()) == _extension; + } + + private: + ice::String _extension; + ice::String _hostname; + }; + +} // namespace ice diff --git a/source/code/systems/resource_system/public/ice/resource_provider.hxx b/source/code/systems/resource_system/public/ice/resource_provider.hxx index 91c19e9f..a726bd09 100644 --- a/source/code/systems/resource_system/public/ice/resource_provider.hxx +++ b/source/code/systems/resource_system/public/ice/resource_provider.hxx @@ -11,6 +11,7 @@ #include #include #include +#include namespace ice { @@ -31,6 +32,14 @@ namespace ice virtual auto hostname() const noexcept -> ice::String { return {}; } + virtual auto filter_resource_uris( + ice::ResourceFilter const& filter, + ice::Array& out_uris + ) noexcept -> ice::TaskExpected + { + co_return 0; + } + virtual auto collect( ice::Array& out_changes ) noexcept -> ice::ucount diff --git a/source/code/systems/resource_system/public/ice/resource_tracker.hxx b/source/code/systems/resource_system/public/ice/resource_tracker.hxx index 57de23b9..55b07998 100644 --- a/source/code/systems/resource_system/public/ice/resource_tracker.hxx +++ b/source/code/systems/resource_system/public/ice/resource_tracker.hxx @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -81,6 +82,11 @@ namespace ice ice::ResourceHandle const& resource_handle ) const noexcept -> ice::ResourceHandle = 0; + virtual auto filter_resource_uris( + ice::ResourceFilter const& filter, + ice::Array& out_uris + ) const noexcept -> ice::TaskExpected = 0; + virtual auto set_resource( ice::URI const& uri, diff --git a/source/code/systems/resource_system/public/ice/resource_types.hxx b/source/code/systems/resource_system/public/ice/resource_types.hxx index a71739f1..e0c396c5 100644 --- a/source/code/systems/resource_system/public/ice/resource_types.hxx +++ b/source/code/systems/resource_system/public/ice/resource_types.hxx @@ -8,11 +8,11 @@ namespace ice { struct URI; - struct Metadata; struct ResourceHandle; struct ResourceTrackerCreateInfo; class Resource; + class ResourceFilter; class ResourceProvider; class ResourceTracker; class ResourceWriter; diff --git a/source/code/systems/resource_system/public/ice/uri.hxx b/source/code/systems/resource_system/public/ice/uri.hxx index ce83ef90..de5817c8 100644 --- a/source/code/systems/resource_system/public/ice/uri.hxx +++ b/source/code/systems/resource_system/public/ice/uri.hxx @@ -17,6 +17,7 @@ namespace ice struct URI { + constexpr URI() noexcept; constexpr explicit URI(char const* uri_raw) noexcept; constexpr explicit URI(ice::String uri) noexcept; constexpr URI(ice::StringID_Arg scheme, ice::String uri) noexcept; @@ -62,6 +63,20 @@ namespace ice static_assert(ice::count(Constant_KnownSchemes) <= 15); + constexpr bool get_known_scheme_index(ice::StringID_Arg known_scheme) noexcept + { + ice::u8 scheme_idx = 0; + for (ice::StringID_Arg scheme : detail::Constant_KnownSchemes) + { + if (scheme == known_scheme) + { + break; + } + scheme_idx += 1; + } + return scheme_idx; + } + constexpr bool get_scheme_size(ice::String raw_uri, ice::u8& out_size) noexcept { ice::ucount const scheme_end = ice::string::find_first_of(raw_uri, ':'); @@ -160,6 +175,20 @@ namespace ice } // namespace detail + constexpr URI::URI() noexcept + : _uri{ nullptr } + , _forced_scheme{ detail::get_known_scheme_index(Scheme_Invalid) } + , _scheme{ } + , _authority{ } + , _userinfo{ } + , _host{ } + , _port{ } + , _path{ } + , _query{ } + , _fragment{ } + { + } + constexpr URI::URI(char const* uri_raw) noexcept : URI{ ice::String{ uri_raw } } {