diff --git a/CLAUDE.md b/CLAUDE.md index 0dab1f54..491adfe0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,8 +51,20 @@ should not move the picture can be **proven** not to: ## Conventions - Namespace `rx::`; env knobs are `RX_*`. Grep for `base::Option`. -- The tree mixes `base::` containers and std. Match the file you are editing; - do not convert either direction. +- The C++ standard library is banned, and so are exceptions (rx builds with + `-fno-exceptions`). Use `base::` (third_party/equilibrium/base) and the rx + helpers in `engine/core/`: `scalar.h` (`rx::Min/Max/Clamp`, std semantics; + never `base::Min/Max/Clamp`, which differ on NaN), `format.h` + (`rx::StrFormat`/`ToString`, exact `std::format`/`to_string` text), + `file_system.h` (`rx::fs`), `text_reader.h`/`text_writer.h` (getline, `>>`, + ostream), `sort.h` (`rx::StableSort`, `rx::NthElement`), `shared.h`. Allowed + std: `std::initializer_list`, placement new, `std::align_val_t`/`nothrow_t` + in operator new/delete, and third-party signatures that demand std types + (tinyusdz in `usd_loader.cc`, libultragui's `ugui::String`). +- `base::Sort` is not `std::sort`: replace a sort only where keys are unique, + or use `rx::StableSort`. +- A check that must stop the process is `BASE_FATAL_CHECK`. `BASE_BUGCHECK` and + `BASE_DCHECK` only break into a debugger and continue. - Comments explain **why**, and document invariants and failure modes. They do not restate the line. - Prefer failing a load loudly with a `path:line:` message over substituting a diff --git a/CMakeLists.txt b/CMakeLists.txt index e1f49964..dabf639f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -256,9 +256,16 @@ if(NOT TARGET equilibrium::base) message(FATAL_ERROR "equilibrium submodule missing; run: git submodule update --init third_party/equilibrium") endif() - set(EQ_USE_STD_ATOMIC ON CACHE BOOL "" FORCE) + # base::Atomic on its native backend, not std::atomic. Forced so a build + # directory configured when rx still needed the std mode (for zetanet's + # std::memory_order arguments) does not keep it from its cache. + set(EQ_USE_STD_ATOMIC OFF CACHE BOOL "" FORCE) set(EQ_BUILD_TESTS OFF CACHE BOOL "" FORCE) add_subdirectory(third_party/equilibrium equilibrium EXCLUDE_FROM_ALL) + # base's std::formatter specializations exist for consumers that format base + # strings with ; nothing here does, and they drag into every + # TU that names a base::String. + target_compile_definitions(eq_base PUBLIC BASE_NO_STD_FORMAT) endif() add_subdirectory(third_party) @@ -304,13 +311,16 @@ endif() # kinema: the skeletal animation runtime, its own repo since 2026-07 # (github.com/Force67/kinema), consumed as a sibling checkout like the other # co-developed libraries. A parent project may already vendor it; only add it -# when the target is absent. +# when the target is absent. KINEMA_USE_BASE is kinema's equilibrium +# configuration: no STL, no exceptions, base types across its API, built on the +# equilibrium::base target vendored above. if(NOT TARGET kinema) set(RX_KINEMA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../kinema" CACHE PATH "Path to a kinema checkout") if(NOT EXISTS "${RX_KINEMA_DIR}/CMakeLists.txt") message(FATAL_ERROR "kinema not found at ${RX_KINEMA_DIR}; clone https://github.com/Force67/kinema next to rx (or set RX_KINEMA_DIR)") endif() + set(KINEMA_USE_BASE ON CACHE BOOL "" FORCE) set(KINEMA_BUILD_TESTS ${RX_BUILD_TESTS}) add_subdirectory(${RX_KINEMA_DIR} kinema) endif() @@ -318,24 +328,16 @@ endif() # zetanet: the UDP transport rx::net's sessions ride, consumed as an optional # sibling checkout. A parent project may already provide the target; without # either, rx::net still builds its transport-free half (codec, replication, -# interest bubbles) and only the sessions are absent. Zetanet hard-codes -# add_subdirectory(vendor/equilibrium); rather than patch upstream, -# add_subdirectory is locally overridden so that one call reuses the -# equilibrium::base vendored above instead of compiling a second, conflicting -# copy. +# interest bubbles) and only the sessions are absent. ZNET_USE_STL=OFF is +# zetanet's equilibrium configuration: it builds on base alone, without the STL +# or exceptions, and reuses the equilibrium::base target vendored above rather +# than compiling its own copy. if(NOT TARGET zetanet AND "net" IN_LIST RX_MODULES) set(RX_ZETANET_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../zetanet" CACHE PATH "Path to a zetanet checkout (develop branch); missing disables the net sessions") if(EXISTS "${RX_ZETANET_DIR}/CMakeLists.txt") set(ZNET_USE_STL OFF CACHE BOOL "" FORCE) set(ZNET_BUILD_SAMPLES OFF CACHE BOOL "" FORCE) - function(add_subdirectory _dir) - get_filename_component(_leaf "${_dir}" NAME) - if(_leaf STREQUAL "equilibrium" AND TARGET equilibrium::base) - return() # zetanet's vendored copy: reuse the one vendored above - endif() - _add_subdirectory("${_dir}" ${ARGN}) - endfunction() add_subdirectory(${RX_ZETANET_DIR} zetanet EXCLUDE_FROM_ALL) else() message(STATUS "rx: no zetanet at ${RX_ZETANET_DIR}, net sessions disabled") @@ -604,7 +606,7 @@ endif() # links stb and nothing else and starts instantly in a CI loop. if(RX_BUILD_TOOLS) add_executable(rxdiff tools/rxdiff.cc) - target_link_libraries(rxdiff PRIVATE rx::stb_impl) + target_link_libraries(rxdiff PRIVATE rx::stb_impl equilibrium::base) rx_set_warnings(rxdiff) endif() @@ -1334,6 +1336,33 @@ if(RX_BUILD_TESTS AND RX_JOLT AND "character" IN_LIST RX_MODULES) add_test(NAME jetpack_test COMMAND jetpack_test) endif() +# Exceptions are not used anywhere in rx: every target defined in rx's own +# directories builds without them, so a throw, try or catch fails to compile +# instead of creeping back in. Third-party directories keep their own flags. +function(rx_forbid_exceptions dir) + get_property(targets DIRECTORY ${dir} PROPERTY BUILDSYSTEM_TARGETS) + foreach(target ${targets}) + get_target_property(type ${target} TYPE) + if(type MATCHES "^(STATIC_LIBRARY|SHARED_LIBRARY|MODULE_LIBRARY|OBJECT_LIBRARY|EXECUTABLE)$") + if(MSVC) + target_compile_options(${target} PRIVATE $<$:/EHs-c->) + target_compile_definitions(${target} PRIVATE _HAS_EXCEPTIONS=0) + else() + target_compile_options(${target} PRIVATE $<$:-fno-exceptions>) + endif() + endif() + endforeach() + get_property(subdirs DIRECTORY ${dir} PROPERTY SUBDIRECTORIES) + foreach(subdir ${subdirs}) + cmake_path(IS_PREFIX PROJECT_SOURCE_DIR "${subdir}" NORMALIZE inside) + string(FIND "${subdir}" "${PROJECT_SOURCE_DIR}/third_party" third_party_at) + if(inside AND NOT third_party_at EQUAL 0) + rx_forbid_exceptions(${subdir}) + endif() + endforeach() +endfunction() +rx_forbid_exceptions(${CMAKE_CURRENT_SOURCE_DIR}) + # install()/export rules, generated last so every module target (and the # RX_INSTALL_* feature flags the modules set) already exists. if(RX_INSTALL) diff --git a/apps/editor/editor_app.cc b/apps/editor/editor_app.cc index f8959d63..2c66b59a 100644 --- a/apps/editor/editor_app.cc +++ b/apps/editor/editor_app.cc @@ -1,10 +1,12 @@ #include "editor_app.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include "anim/morph.h" #include "app/host.h" @@ -13,16 +15,31 @@ #include "asset/usd_loader.h" #include "asset/primitives.h" #include "asset/vfs.h" +#include "base/algorithm.h" +#include "base/containers/span.h" +#include "base/containers/vector.h" +#include "base/memory/move.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" +#include "core/format.h" #include "core/log.h" +#include "core/scalar.h" #include "edit/hierarchy.h" #include "editor_input.h" #include "render/core/settings.h" #include "scene/components.h" namespace rx::editor { -namespace fs = std::filesystem; namespace { +base::String LowerExtension(base::StringRef path) { + base::String extension(fs::Extension(path)); + for (char &c : extension) + c = static_cast(::tolower(static_cast(c))); + return extension; +} + Mat4 MatOf(const scene::Transform &t) { return MakeTransform( {t.position[0], t.position[1], t.position[2]}, @@ -65,7 +82,7 @@ asset::Mesh MakeTurntable(f32 radius, f32 half_height, asset::AssetId id, const u32 top_ring = static_cast(lod.vertices.size()); for (u32 i = 0; i <= kSegments; ++i) { const f32 angle = static_cast(i) / kSegments * 6.28318530718f; - const f32 x = std::cos(angle), z = std::sin(angle); + const f32 x = ::cos(angle), z = ::sin(angle); lod.vertices.push_back(vertex({x * radius, half_height, z * radius}, {0, 1, 0}, x * 0.5f + 0.5f, z * 0.5f + 0.5f)); @@ -75,7 +92,7 @@ asset::Mesh MakeTurntable(f32 radius, f32 half_height, asset::AssetId id, const u32 bottom_ring = static_cast(lod.vertices.size()); for (u32 i = 0; i <= kSegments; ++i) { const f32 angle = static_cast(i) / kSegments * 6.28318530718f; - const f32 x = std::cos(angle), z = std::sin(angle); + const f32 x = ::cos(angle), z = ::sin(angle); lod.vertices.push_back(vertex({x * radius, -half_height, z * radius}, {0, -1, 0}, x * 0.5f + 0.5f, z * 0.5f + 0.5f)); @@ -83,7 +100,7 @@ asset::Mesh MakeTurntable(f32 radius, f32 half_height, asset::AssetId id, const u32 side_ring = static_cast(lod.vertices.size()); for (u32 i = 0; i <= kSegments; ++i) { const f32 angle = static_cast(i) / kSegments * 6.28318530718f; - const f32 x = std::cos(angle), z = std::sin(angle); + const f32 x = ::cos(angle), z = ::sin(angle); lod.vertices.push_back( vertex({x * radius, half_height, z * radius}, {x, 0, z}, static_cast(i) / kSegments, 0)); @@ -108,7 +125,7 @@ asset::Mesh MakeTurntable(f32 radius, f32 half_height, asset::AssetId id, } lod.submeshes.push_back( {0, static_cast(lod.indices.size()), material}); - mesh.bounds_radius = std::sqrt(radius * radius + half_height * half_height); + mesh.bounds_radius = ::sqrt(radius * radius + half_height * half_height); return mesh; } } // namespace @@ -127,7 +144,7 @@ bool Editor::OnInitialize(app::Services &s) { RegisterEditorInput(*input_map_); - if (vfs_ && fs::exists(asset_root_)) + if (vfs_ && fs::Exists(asset_root_)) vfs_->Mount(asset::MakeLooseFileProvider(asset_root_)); if (vfs_) assets_.emplace(*vfs_); @@ -140,8 +157,8 @@ bool Editor::OnInitialize(app::Services &s) { camera_.set_position({6.0f, 4.5f, 6.0f}); Vec3 d = Normalize(Vec3{0, 1.0f, 0} - camera_.position()); - camera_.set_yaw_pitch(std::atan2(d.x, -d.z), - std::asin(std::clamp(d.y, -1.0f, 1.0f))); + camera_.set_yaw_pitch(::atan2(d.x, -d.z), + ::asin(rx::Clamp(d.y, -1.0f, 1.0f))); // Load a scene or authoring model passed on argv. if (!open_path_.empty()) { @@ -223,7 +240,7 @@ void Editor::SetupDefaultScene() { selection_.Set(cube_e); } -asset::AssetId Editor::UploadPrimitive(const std::string &name, +asset::AssetId Editor::UploadPrimitive(const base::String &name, const asset::Mesh &mesh) { asset::Mesh copy = mesh; // keep a CPU copy for picking if (renderer_) @@ -232,8 +249,8 @@ asset::AssetId Editor::UploadPrimitive(const std::string &name, return copy.id; } -ecs::Entity Editor::SpawnMesh(const std::string &mesh_name, asset::AssetId mesh, - const Vec3 &pos, const std::string &label) { +ecs::Entity Editor::SpawnMesh(const base::String &mesh_name, asset::AssetId mesh, + const Vec3 &pos, const base::String &label) { ecs::Entity e = world_->Create(); world_->Add(e, scene::Transform{.position = {pos.x, pos.y, pos.z}}); world_->Add(e, scene::Renderable{mesh}); @@ -243,14 +260,14 @@ ecs::Entity Editor::SpawnMesh(const std::string &mesh_name, asset::AssetId mesh, return e; } -void Editor::SetName(ecs::Entity e, const std::string &name) { +void Editor::SetName(ecs::Entity e, const base::String &name) { if (scene::Name *n = world_->Get(e)) n->value = name; else world_->Add(e, scene::Name{name}); } -std::string Editor::GetName(ecs::Entity e) const { +base::String Editor::GetName(ecs::Entity e) const { if (scene::Name *n = world_->Get(e)) return n->value; return ""; @@ -265,15 +282,13 @@ void Editor::ScanAssets() { assets_list_.push_back({"assets://meshes/plane.mesh", "plane.mesh", "mesh"}); // Loose files under the asset root, if it exists. - if (fs::exists(asset_root_)) { - for (auto &p : fs::recursive_directory_iterator(asset_root_)) { - if (!p.is_regular_file()) + base::Vector files; + if (fs::Exists(asset_root_) && fs::ListDirectory(asset_root_, &files, /*recursive=*/true)) { + for (const fs::DirEntry &p : files) { + if (!p.is_regular) continue; - std::string ext = p.path().extension().string(); - std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { - return static_cast(std::tolower(c)); - }); - std::string kind; + const base::String ext = LowerExtension(p.path); + base::String kind; if (ext == ".rxscene") kind = "scene"; else if (ext == ".rxterrain") @@ -292,8 +307,7 @@ void Editor::ScanAssets() { kind = "audio"; else continue; - assets_list_.push_back( - {p.path().string(), p.path().filename().string(), kind}); + assets_list_.push_back({p.path, base::String(fs::Filename(p.path)), kind}); } } } @@ -303,7 +317,7 @@ u32 Editor::ConfigureImportedBody(ImportedSkin *skin) { return 0; const asset::Skeleton &skeleton = skin->skeleton; u32 count = 0; - auto available = [&](std::string_view name) { + auto available = [&](base::StringRef name) { return skeleton.Find(name) >= 0; }; auto add_chest = [&](const char *side, const char *driven, @@ -312,7 +326,7 @@ u32 Editor::ConfigureImportedBody(ImportedSkin *skin) { return; const char *driver = available("chest.twk") ? "chest.twk" : driven; anim::BodyRegionConfig region = anim::MakeBodyRegionPreset( - anim::BodyRegionKind::kChest, std::string("chest.") + side, driver, + anim::BodyRegionKind::kChest, base::String("chest.") + side, driver, driven); // On this Genesis rig the chest bones are true deformation helpers. // Gravity/inertial displacement drives them, while source-authored shapes @@ -348,7 +362,7 @@ u32 Editor::ConfigureImportedBody(ImportedSkin *skin) { region.translation_gain = {}; region.rotation_gain = {}; region.frequency_hz *= 0.86f; - region.damping_ratio = std::max(0.5f, region.damping_ratio - 0.16f); + region.damping_ratio = rx::Max(0.5f, region.damping_ratio - 0.16f); region.morphs.push_back( {impact_morph, anim::BodyDeformationSignal::kImpact, 0.38f}); region.morphs.push_back( @@ -375,7 +389,7 @@ u32 Editor::ConfigureImportedBody(ImportedSkin *skin) { // Preview tuning is intentionally readable at turntable distance while // remaining inside the anatomical clamps supplied by each preset. region.frequency_hz *= 0.82f; - region.damping_ratio = std::max(0.52f, region.damping_ratio - 0.17f); + region.damping_ratio = rx::Max(0.52f, region.damping_ratio - 0.17f); region.translation_gain = {1.25f, 1.25f, 1.25f}; region.rotation_gain = {1.2f, 1.2f, 1.2f}; if (morph && *morph) { @@ -400,14 +414,14 @@ u32 Editor::ConfigureImportedBody(ImportedSkin *skin) { return count; } -bool Editor::LoadModelDocument(const std::string &path) { - std::string load_path = path; +bool Editor::LoadModelDocument(const base::String &path) { + base::String load_path = path; bool reused_cache = false; - if (fs::path(path).extension() == ".blend") { + if (fs::Extension(path) == ".blend") { asset::BlendImportOptions options; options.converter_script = RX_BLEND_CONVERTER_SCRIPT; asset::BlendImportResult converted; - std::string error; + base::String error; if (!asset::ConvertBlendScene(path, options, &converted, &error)) { status_message_ = "Blend import failed: " + error; RX_WARN("editor: {}", status_message_); @@ -435,8 +449,8 @@ bool Editor::LoadModelDocument(const std::string &path) { renderer_->UploadMaterial(material); } for (u32 i = 0; i < imported_scene.meshes.size(); ++i) { - UploadPrimitive(fs::path(path).filename().string() + "#" + - std::to_string(i), + UploadPrimitive(base::String(fs::Filename(path)) + "#" + + rx::ToString(i), imported_scene.meshes[i]); } @@ -446,9 +460,9 @@ bool Editor::LoadModelDocument(const std::string &path) { u32 region_count = 0; for (u32 i = 0; i < imported_scene.skeletons.size(); ++i) { ImportedSkin &skin = model.skins[i]; - skin.skeleton = std::move(imported_scene.skeletons[i]); + skin.skeleton = base::move(imported_scene.skeletons[i]); if (i < imported_scene.skin_bindings.size()) - skin.binding = std::move(imported_scene.skin_bindings[i]); + skin.binding = base::move(imported_scene.skin_bindings[i]); skin.pose.ResetToBind(skin.skeleton); region_count += ConfigureImportedBody(&skin); } @@ -461,16 +475,16 @@ bool Editor::LoadModelDocument(const std::string &path) { transform.position[0] = source.position.x; transform.position[1] = source.position.y; transform.position[2] = source.position.z; - std::copy(std::begin(source.rotation), std::end(source.rotation), - transform.rotation); + for (size_t k = 0; k < 4; ++k) + transform.rotation[k] = source.rotation[k]; transform.scale = source.scale; ecs::Entity entity = world_->Create(); world_->Add(entity, transform); world_->Add(entity, scene::Renderable{imported_scene.meshes[source.mesh_index].id}); world_->Add(entity, scene::Transient{}); - SetName(entity, fs::path(path).stem().string() + " " + - std::to_string(model.instances.size() + 1)); + SetName(entity, base::String(fs::Stem(path)) + " " + + rx::ToString(model.instances.size() + 1)); ImportedInstance instance; instance.entity = entity; @@ -487,16 +501,16 @@ bool Editor::LoadModelDocument(const std::string &path) { model.skins[instance.skin].binding); } const u32 instance_index = static_cast(model.instances.size()); - model.instances.push_back(std::move(instance)); + model.instances.push_back(base::move(instance)); imported_entities_[ImportedEntityKey(entity)] = {model_index, instance_index}; } - imported_models_.push_back(std::move(model)); + imported_models_.push_back(base::move(model)); // Frame character imports from the skinned pieces. Authoring scenes often // include a huge cyclorama or ground plane; including those static props in // the focus bounds makes the actual character microscopic in the viewport. - const bool has_skinned_instances = std::any_of( + const bool has_skinned_instances = base::AnyOf( imported_scene.instances.begin(), imported_scene.instances.end(), [](const asset::ImportedScene::Instance &instance) { return instance.skeleton_index >= 0; @@ -536,7 +550,7 @@ bool Editor::LoadModelDocument(const std::string &path) { const Vec3 instance_center = TransformPoint( transform, {mesh.bounds_center[0], mesh.bounds_center[1], mesh.bounds_center[2]}); - radius = std::max(radius, Length(instance_center - center) + + radius = rx::Max(radius, Length(instance_center - center) + mesh.bounds_radius * instance.scale); } ImportedModel &stored_model = imported_models_.back(); @@ -544,7 +558,7 @@ bool Editor::LoadModelDocument(const std::string &path) { // Give character imports a dedicated studio turntable. Its top sits at the // lowest skinned bound and it rotates in lockstep with every skinned piece. if (has_skinned_instances) { - f32 floor_y = std::numeric_limits::max(); + f32 floor_y = FLT_MAX; for (const asset::ImportedScene::Instance &instance : imported_scene.instances) { if (instance.mesh_index >= imported_scene.meshes.size() || instance.skeleton_index < 0) @@ -558,19 +572,19 @@ bool Editor::LoadModelDocument(const std::string &path) { if (mesh.lods.empty()) continue; for (const asset::Vertex &vertex : mesh.lods[0].vertices) { - floor_y = std::min( + floor_y = rx::Min( floor_y, TransformPoint(transform, {vertex.position[0], vertex.position[1], vertex.position[2]}) .y); } } - if (!std::isfinite(floor_y)) + if (!::isfinite(floor_y)) floor_y = center.y - radius; constexpr f32 kPlateHalfHeight = 0.055f; const f32 surface_y = terrain_.SampleHeight(center.x, center.z).value_or(floor_y); - const f32 plate_top = std::max(floor_y, surface_y + 0.035f); + const f32 plate_top = rx::Max(floor_y, surface_y + 0.035f); const f32 character_lift = plate_top - floor_y; center.y += character_lift; for (ImportedInstance &instance : stored_model.instances) { @@ -582,9 +596,9 @@ bool Editor::LoadModelDocument(const std::string &path) { transform->position[1] += character_lift; } } - const f32 plate_radius = std::max(0.9f, radius * 0.72f); + const f32 plate_radius = rx::Max(0.9f, radius * 0.72f); const asset::AssetId plate_material = asset::MakeAssetId( - path + "#editor-turntable-material-" + std::to_string(model_index)); + path + "#editor-turntable-material-" + rx::ToString(model_index)); asset::Material material; material.id = plate_material; material.base_color_factor[0] = 0.055f; @@ -596,7 +610,7 @@ bool Editor::LoadModelDocument(const std::string &path) { if (renderer_) renderer_->UploadMaterial(material); const asset::AssetId plate_mesh = asset::MakeAssetId( - path + "#editor-turntable-mesh-" + std::to_string(model_index)); + path + "#editor-turntable-mesh-" + rx::ToString(model_index)); UploadPrimitive("turntable.mesh", MakeTurntable(plate_radius, kPlateHalfHeight, plate_mesh, plate_material)); @@ -615,11 +629,11 @@ bool Editor::LoadModelDocument(const std::string &path) { camera_.set_position(center + Vec3{radius * 1.4f, radius * 0.75f, radius * 1.8f}); Vec3 direction = Normalize(center - camera_.position()); - camera_.set_yaw_pitch(std::atan2(direction.x, -direction.z), - std::asin(std::clamp(direction.y, -1.0f, 1.0f))); - status_message_ = "Imported " + fs::path(path).filename().string() + ": " + - std::to_string(imported_scene.meshes.size()) + " meshes, " + - std::to_string(region_count) + " jiggle regions" + + camera_.set_yaw_pitch(::atan2(direction.x, -direction.z), + ::asin(rx::Clamp(direction.y, -1.0f, 1.0f))); + status_message_ = "Imported " + base::String(fs::Filename(path)) + ": " + + rx::ToString(imported_scene.meshes.size()) + " meshes, " + + rx::ToString(region_count) + " jiggle regions" + (reused_cache ? " (cached)" : ""); RX_INFO("editor: {}", status_message_); playing_ = region_count > 0; // imported characters preview immediately @@ -629,7 +643,7 @@ bool Editor::LoadModelDocument(const std::string &path) { void Editor::UpdateImportedModels(f32 dt) { for (ImportedModel &model : imported_models_) { - const f32 preview_dt = playing_ ? std::max(dt, 0.0f) : 0.0f; + const f32 preview_dt = playing_ ? rx::Max(dt, 0.0f) : 0.0f; model.preview_time += preview_dt; // Auto preview holds each style for five seconds and crossfades for one, @@ -646,7 +660,7 @@ void Editor::UpdateImportedModels(f32 dt) { model.active_walk_style = anim::WalkStyleKind::kMarch; walk_style = march; } else { - const f32 cycle = std::fmod(model.preview_time, 12.0f); + const f32 cycle = ::fmod(model.preview_time, 12.0f); if (cycle < 5.0f) { model.active_walk_style = anim::WalkStyleKind::kHipSway; walk_style = hip_sway; @@ -723,14 +737,14 @@ void Editor::UpdateImportedModels(f32 dt) { anim::BodyDynamicsFrame frame; if (playing_) { frame.linear_acceleration = { - std::sin(model.preview_time * 2.7f) * 7.0f, - std::sin(model.preview_time * 5.4f) * 5.5f, - std::cos(model.preview_time * 2.1f) * 6.0f, + ::sin(model.preview_time * 2.7f) * 7.0f, + ::sin(model.preview_time * 5.4f) * 5.5f, + ::cos(model.preview_time * 2.1f) * 6.0f, }; frame.angular_acceleration = { - std::sin(model.preview_time * 2.3f) * 2.8f, - std::cos(model.preview_time * 1.7f) * 3.5f, - std::sin(model.preview_time * 3.2f) * 3.0f, + ::sin(model.preview_time * 2.3f) * 2.8f, + ::cos(model.preview_time * 1.7f) * 3.5f, + ::sin(model.preview_time * 3.2f) * 3.0f, }; if (fire_event) { if (event_kind == 0) @@ -758,16 +772,15 @@ void Editor::UpdateImportedModels(f32 dt) { } const MeshRecord *Editor::FindMesh(u64 hash) const { - auto it = meshes_.find(hash); - return it == meshes_.end() ? nullptr : &it->second; + return meshes_.find(hash); } -std::string Editor::EntityLabel(ecs::Entity e) const { - std::string n = GetName(e); +base::String Editor::EntityLabel(ecs::Entity e) const { + base::String n = GetName(e); if (!n.empty()) return n; char buf[32]; - std::snprintf(buf, sizeof(buf), "Entity %u", e.index); + ::snprintf(buf, sizeof(buf), "Entity %u", e.index); return buf; } @@ -789,7 +802,7 @@ void Editor::OnUpdate(f32 dt) { bool lmb_edge = lmb && !prev_lmb_; bool over_vp = CursorOverViewport(); - static const bool input_log = std::getenv("RX_EDITOR_INPUT_LOG") != nullptr; + static const bool input_log = ::getenv("RX_EDITOR_INPUT_LOG") != nullptr; if (input_log && lmb_edge) RX_INFO("editor: lmb down at {:.0f},{:.0f} over_vp={}", in.mouse_x, in.mouse_y, over_vp); @@ -826,7 +839,7 @@ void Editor::OnUpdate(f32 dt) { UpdateCamera(dt); - if (std::getenv("RX_EDITOR_AUTOPILOT")) + if (::getenv("RX_EDITOR_AUTOPILOT")) RunAutopilot(); // keyboard shortcuts @@ -904,8 +917,8 @@ void Editor::FocusSelection() { Vec3 eye = center - camera_.forward() * 4.0f; camera_.set_position(eye); Vec3 d = Normalize(center - eye); - camera_.set_yaw_pitch(std::atan2(d.x, -d.z), - std::asin(std::clamp(d.y, -1.0f, 1.0f))); + camera_.set_yaw_pitch(::atan2(d.x, -d.z), + ::asin(rx::Clamp(d.y, -1.0f, 1.0f))); } // Picking. The engine GPU path (DrawItem::pick_id + Renderer::RequestPick / @@ -934,11 +947,11 @@ void Editor::PollScenePick() { return; if (auto result = renderer_->TakePickResult()) { pick_pending_ = false; - auto it = pick_map_.find(result->pick_id); - if (it != pick_map_.end() && world_->IsAlive(it->second)) { - selection_.Set(it->second); + const ecs::Entity *picked = pick_map_.find(result->pick_id); + if (picked && world_->IsAlive(*picked)) { + selection_.Set(*picked); RX_INFO("editor: pick id {} -> {}", result->pick_id, - EntityLabel(it->second)); + EntityLabel(*picked)); } else { selection_.Clear(); // background RX_INFO("editor: pick id {} -> background", result->pick_id); @@ -953,7 +966,7 @@ bool RayTriangle(const Vec3 &o, const Vec3 &d, const Vec3 &a, const Vec3 &b, Vec3 e1 = b - a, e2 = c - a; Vec3 p = Cross(d, e2); f32 det = Dot(e1, p); - if (std::fabs(det) < 1e-8f) + if (::fabs(det) < 1e-8f) return false; f32 inv = 1.0f / det; Vec3 tv = o - a; @@ -1039,7 +1052,7 @@ Vec2 Editor::ProjectToScreen(const Vec3 &world, bool *in_front) const { f32 w = vp.m[3] * world.x + vp.m[7] * world.y + vp.m[11] * world.z + vp.m[15]; if (in_front) *in_front = w > 1e-4f; - if (std::fabs(w) < 1e-6f) + if (::fabs(w) < 1e-6f) w = 1e-6f; f32 ndc_x = x / w, ndc_y = y / w; return {(ndc_x * 0.5f + 0.5f) * window_->width(), @@ -1087,7 +1100,7 @@ void Editor::UpdateGizmo(f32 mx, f32 my, bool lmb_down, bool lmb_edge) { // Not dragging: on click, test the three axis-handle tips. if (lmb_edge) { - f32 len = std::max(0.5f, Length(origin - camera_.position()) * 0.18f); + f32 len = rx::Max(0.5f, Length(origin - camera_.position()) * 0.18f); for (int a = 0; a < 3; ++a) { Vec3 axis{a == 0 ? 1.0f : 0.0f, a == 1 ? 1.0f : 0.0f, a == 2 ? 1.0f : 0.0f}; @@ -1099,7 +1112,7 @@ void Editor::UpdateGizmo(f32 mx, f32 my, bool lmb_down, bool lmb_edge) { // distance from cursor to the handle tip. The tolerance is authored at // 1x and both operands are pixels, so it scales with the buffer or the // handle gets relatively harder to grab the denser the display. - f32 d = std::hypot(mx - s1.x, my - s1.y); + f32 d = ::hypot(mx - s1.x, my - s1.y); if (d < 14.0f * window_->pixel_density()) { gizmo_drag_.active = true; gizmo_drag_.axis = a; @@ -1108,7 +1121,7 @@ void Editor::UpdateGizmo(f32 mx, f32 my, bool lmb_down, bool lmb_edge) { gizmo_drag_.grab_mouse_x = mx; gizmo_drag_.grab_mouse_y = my; Vec2 sd{s1.x - s0.x, s1.y - s0.y}; - f32 sl = std::max(1.0f, std::hypot(sd.x, sd.y)); + f32 sl = rx::Max(1.0f, ::hypot(sd.x, sd.y)); gizmo_drag_.axis_screen_dir = {sd.x / sl, sd.y / sl}; gizmo_drag_.world_per_pixel = len / sl; undo_.BeginGroup("Move"); @@ -1129,7 +1142,7 @@ void Editor::NewScene() { undo_.EndGroup(); gizmo_drag_.active = false; ClearTerrainVisuals(); - std::vector all; + base::Vector all; world_->Each( [&](ecs::Entity e, scene::Transform &) { all.push_back(e); }); for (ecs::Entity e : all) @@ -1150,11 +1163,8 @@ void Editor::NewScene() { MarkDirty(); } -void Editor::OpenDocument(const std::string &path) { - std::string extension = fs::path(path).extension().string(); - std::transform( - extension.begin(), extension.end(), extension.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); +void Editor::OpenDocument(const base::String &path) { + const base::String extension = LowerExtension(path); if (extension == ".rxscene") { DoLoad(path); } else if (extension == ".gltf" || extension == ".glb" || @@ -1180,57 +1190,55 @@ void Editor::DoBakeWorld() { DoSave(scene_path_); } - const fs::path archive = fs::path(scene_path_).replace_extension(".rxp"); + const base::String archive = fs::ReplaceExtension(scene_path_, ".rxp"); // Stage and rename, so a failed cook cannot leave a half-written archive // where a whole one was. No backup chain: unlike the scene, this is a derived // artifact regenerable from the scene and the code, and a drift of numbered // backups of a build product serves nobody. - const fs::path stage = archive.string() + ".editor-stage"; - std::error_code ignored; - fs::remove(stage, ignored); + const base::String stage = archive + ".editor-stage"; + fs::Remove(stage); // Named for where the archive lands, not for where it is written. The cook // takes an unset name from the path it is given, and that path is the staging // file here, so leaving it unset would bury the world under a directory named // after a temporary nobody will ever open the archive by. world::WorldBakeOptions options = world_bake_options_; - if (options.name.empty()) options.name = world::WorldNameForArchive(archive.string()); + if (options.name.empty()) options.name = world::WorldNameForArchive(archive); world::WorldBakeResult result; - std::string error; - if (!world::BakeWorld(scene_path_, options, stage.string(), &result, &error)) { - fs::remove(stage, ignored); + base::String error; + if (!world::BakeWorld(scene_path_, options, stage, &result, &error)) { + fs::Remove(stage); // The refusals are the point of having this button in the editor at all: // they land where the author is looking instead of in a terminal. status_message_ = "Bake failed: " + error; RX_ERROR("bake world: {}", error); return; } - std::error_code rename_error; - fs::rename(stage, archive, rename_error); - if (rename_error) { - fs::remove(stage, ignored); - status_message_ = "Bake failed: cannot replace " + archive.string(); + if (!fs::Rename(stage, archive)) { + fs::Remove(stage); + status_message_ = "Bake failed: cannot replace " + archive; return; } - std::string message = "Baked " + archive.filename().string() + ": " + - std::to_string(result.cells) + " cells, " + - std::to_string(result.entities) + " entities, " + - std::to_string(result.instances) + " instances"; + base::String message = "Baked " + base::String(fs::Filename(archive)) + ": " + + rx::ToString(result.cells) + " cells, " + + rx::ToString(result.entities) + " entities, " + + rx::ToString(result.instances) + " instances"; if (!result.dropped.empty()) { message += " (dropped "; for (size_t i = 0; i < result.dropped.size(); ++i) { - message += (i != 0 ? ", " : "") + result.dropped[i]; + if (i != 0) message += ", "; + message += result.dropped[i]; } message += ")"; } status_message_ = message; - RX_INFO("bake world: {} -> {} ({} cells, bake {})", scene_path_, archive.string(), result.cells, + RX_INFO("bake world: {} -> {} ({} cells, bake {})", scene_path_, archive, result.cells, result.bake_id); } -void Editor::DoSave(const std::string &path) { +void Editor::DoSave(const base::String &path) { FinishTerrainStroke(); FinishPlacementDrag(); if (scrub_.active) @@ -1239,22 +1247,20 @@ void Editor::DoSave(const std::string &path) { if (gizmo_drag_.active) undo_.EndGroup(); gizmo_drag_.active = false; - const fs::path scene_path(path); - const fs::path terrain_path = fs::path(path).replace_extension(".rxterrain"); - const fs::path scene_stage = scene_path.string() + ".editor-stage"; - const fs::path terrain_stage = terrain_path.string() + ".editor-stage"; - auto backup_path = [](const fs::path &target) { - fs::path candidate = target.string() + ".editor-backup"; - for (u32 suffix = 1; fs::exists(candidate); ++suffix) - candidate = target.string() + ".editor-backup." + std::to_string(suffix); + const base::String &scene_path = path; + const base::String terrain_path = fs::ReplaceExtension(path, ".rxterrain"); + const base::String scene_stage = scene_path + ".editor-stage"; + const base::String terrain_stage = terrain_path + ".editor-stage"; + auto backup_path = [](const base::String &target) { + base::String candidate = target + ".editor-backup"; + for (u32 suffix = 1; fs::Exists(candidate); ++suffix) + candidate = target + ".editor-backup." + rx::ToString(suffix); return candidate; }; - const fs::path scene_backup = backup_path(scene_path); - const fs::path terrain_backup = backup_path(terrain_path); - std::error_code ignored; - for (const fs::path &temporary : {scene_stage, terrain_stage}) { - fs::remove(temporary, ignored); - } + const base::String scene_backup = backup_path(scene_path); + const base::String terrain_backup = backup_path(terrain_path); + fs::Remove(scene_stage); + fs::Remove(terrain_stage); // Terrain tiles are transient visualization entities. The authored data is // the sidecar, so omit those entities from the generic scene serializer. @@ -1263,25 +1269,25 @@ void Editor::DoSave(const std::string &path) { if (world_->IsAlive(visual.entity)) world_->Destroy(visual.entity); } - std::string scene_error; + base::String scene_error; bool scene_saved = - edit::SaveScene(*world_, scene_stage.string(), &scene_error); - for (auto &[key, visual] : terrain_tiles_) - visual.entity = SpawnTerrainTile(key, visual.mesh); + edit::SaveScene(*world_, scene_stage, &scene_error); + for (auto &[key, visual] : terrain_tiles_) { + (void)key; + visual.entity = SpawnTerrainTile(visual.key, visual.mesh); + } const bool has_terrain = static_cast(terrain_.desc().id); - std::string terrain_error; + base::String terrain_error; bool terrain_saved = !has_terrain || - terrain::SaveTerrain(terrain_, terrain_stage.string(), &terrain_error); + terrain::SaveTerrain(terrain_, terrain_stage, &terrain_error); - auto move = [](const fs::path &from, const fs::path &to, std::string *error) { - std::error_code filesystem_error; - fs::rename(from, to, filesystem_error); - if (!filesystem_error) + auto move = [](const base::String &from, const base::String &to, base::String *error) { + if (fs::Rename(from, to)) return true; if (error) - *error = filesystem_error.message(); + *error = ::strerror(errno); return false; }; bool scene_backed_up = false; @@ -1289,21 +1295,21 @@ void Editor::DoSave(const std::string &path) { bool scene_committed = false; if (scene_saved && terrain_saved) { scene_backed_up = - fs::exists(scene_path) && move(scene_path, scene_backup, &scene_error); - if (fs::exists(scene_path) && !scene_backed_up) + fs::Exists(scene_path) && move(scene_path, scene_backup, &scene_error); + if (fs::Exists(scene_path) && !scene_backed_up) scene_saved = false; // Only a document that owns terrain may touch the sidecar. Without this // gate a no-terrain document saved onto a stem with an existing sidecar // would move it to the backup, then delete the backup on success, silently // destroying the user's terrain. if (has_terrain) { - terrain_backed_up = fs::exists(terrain_path) && + terrain_backed_up = fs::Exists(terrain_path) && move(terrain_path, terrain_backup, &terrain_error); - if (fs::exists(terrain_path) && !terrain_backed_up) + if (fs::Exists(terrain_path) && !terrain_backed_up) terrain_saved = false; - } else if (fs::exists(terrain_path)) { + } else if (fs::Exists(terrain_path)) { RX_WARN("editor: leaving unrelated terrain sidecar untouched: {}", - terrain_path.string()); + terrain_path); } } if (scene_saved && terrain_saved) { @@ -1314,53 +1320,45 @@ void Editor::DoSave(const std::string &path) { terrain_saved = move(terrain_stage, terrain_path, &terrain_error); if (!scene_saved || !terrain_saved) { - if (scene_committed) { - std::error_code remove_error; - fs::remove(scene_path, remove_error); - if (remove_error) { - scene_saved = false; - scene_error = - "rollback could not remove new scene: " + remove_error.message(); - } + // An error only when the path is there and would not go, as + // std::filesystem::remove reports it. + if (scene_committed && !fs::Remove(scene_path) && fs::Exists(scene_path)) { + scene_saved = false; + scene_error = base::String("rollback could not remove new scene: ") + ::strerror(errno); } - if (has_terrain && fs::exists(terrain_path) && terrain_backed_up) { - std::error_code remove_error; - fs::remove(terrain_path, remove_error); - if (remove_error) { - terrain_saved = false; - terrain_error = - "rollback could not remove new terrain: " + remove_error.message(); - } + if (has_terrain && fs::Exists(terrain_path) && terrain_backed_up && + !fs::Remove(terrain_path) && fs::Exists(terrain_path)) { + terrain_saved = false; + terrain_error = base::String("rollback could not remove new terrain: ") + ::strerror(errno); } if (scene_backed_up && !move(scene_backup, scene_path, &scene_error)) { scene_saved = false; scene_error = "rollback failed; backup preserved at " + - scene_backup.string() + ": " + scene_error; + scene_backup + ": " + scene_error; } if (terrain_backed_up && !move(terrain_backup, terrain_path, &terrain_error)) { terrain_saved = false; terrain_error = "rollback failed; backup preserved at " + - terrain_backup.string() + ": " + terrain_error; + terrain_backup + ": " + terrain_error; } } - for (const fs::path &temporary : {scene_stage, terrain_stage}) { - fs::remove(temporary, ignored); - } + fs::Remove(scene_stage); + fs::Remove(terrain_stage); if (scene_saved && terrain_saved) { - fs::remove(scene_backup, ignored); - fs::remove(terrain_backup, ignored); + fs::Remove(scene_backup); + fs::Remove(terrain_backup); } if (scene_saved && terrain_saved) { scene_path_ = path; - terrain_path_ = terrain_path.string(); + terrain_path_ = terrain_path; doc_dirty_ = false; terrain_dirty_ = false; status_message_ = has_terrain ? "Saved scene + terrain" : "Saved scene"; RX_INFO("editor: saved {}{}", path, - has_terrain ? " and " + terrain_path.string() - : " without a terrain sidecar"); + has_terrain ? " and " + terrain_path + : base::String(" without a terrain sidecar")); ScanAssets(); } else { doc_dirty_ = true; @@ -1375,7 +1373,7 @@ void Editor::DoSave(const std::string &path) { MarkDirty(); } -void Editor::DoLoad(const std::string &path) { +void Editor::DoLoad(const base::String &path) { FinishTerrainStroke(); FinishPlacementDrag(); if (gizmo_drag_.active) @@ -1388,11 +1386,11 @@ void Editor::DoLoad(const std::string &path) { RX_WARN("editor: no asset database (headless vfs?); cannot load scenes"); return; } - const fs::path terrain_path = fs::path(path).replace_extension(".rxterrain"); - const bool has_terrain = fs::exists(terrain_path); + const base::String terrain_path = fs::ReplaceExtension(path, ".rxterrain"); + const bool has_terrain = fs::Exists(terrain_path); terrain::Terrain loaded_terrain; - std::string terrain_error; - if (has_terrain && !terrain::LoadTerrain(terrain_path.string(), + base::String terrain_error; + if (has_terrain && !terrain::LoadTerrain(terrain_path, &loaded_terrain, &terrain_error)) { status_message_ = "Terrain load failed: " + terrain_error; RX_WARN("editor: {}", status_message_); @@ -1402,10 +1400,10 @@ void Editor::DoLoad(const std::string &path) { // The engine LoadScene leaves existing entities untouched; replacing the open // document is editor policy, so clear the current scene entities first. - std::vector old; + base::Vector old; world_->Each( [&](ecs::Entity e, scene::Transform &) { old.push_back(e); }); - std::string err; + base::String err; if (edit::LoadScene(*world_, *assets_, path, &err)) { // Resolve meshes that are not already resident (notably glTF assets placed // by the surface brush) before discarding the old document's entities. @@ -1416,7 +1414,7 @@ void Editor::DoLoad(const std::string &path) { const auto source = asset::LookupAssetPath(renderable.mesh); if (!source) return; - AssetEntry entry{*source, fs::path(*source).filename().string(), "mesh"}; + AssetEntry entry{*source, base::String(fs::Filename(*source)), "mesh"}; if (asset::AssetId resolved = ResolvePlacementMesh(entry)) renderable.mesh = resolved; }); @@ -1425,12 +1423,12 @@ void Editor::DoLoad(const std::string &path) { imported_models_.clear(); imported_entities_.clear(); ClearTerrainVisuals(); - terrain_ = has_terrain ? std::move(loaded_terrain) : terrain::Terrain{}; + terrain_ = has_terrain ? base::move(loaded_terrain) : terrain::Terrain{}; terrain_brush_layer_ = 0; if (has_terrain) RebuildTerrainVisuals(); scene_path_ = path; - terrain_path_ = terrain_path.string(); + terrain_path_ = terrain_path; doc_dirty_ = false; terrain_dirty_ = false; undo_.Clear(); @@ -1484,24 +1482,24 @@ void Editor::PerformRedo() { void Editor::OpenFileDialog() { dialog_files_.clear(); - auto supported = [](const fs::path &path) { - std::string extension = path.extension().string(); - std::transform( - extension.begin(), extension.end(), extension.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); + auto supported = [](base::StringRef path) { + const base::String extension = LowerExtension(path); return extension == ".rxscene" || extension == ".gltf" || extension == ".glb" || extension == ".blend" || - asset::IsUsdPath(path.string()); + asset::IsUsdPath(path); }; - if (fs::exists(asset_root_)) { - for (auto &p : fs::recursive_directory_iterator(asset_root_)) - if (p.is_regular_file() && supported(p.path())) - dialog_files_.push_back(p.path().string()); + base::Vector entries; + if (fs::Exists(asset_root_) && fs::ListDirectory(asset_root_, &entries, /*recursive=*/true)) { + for (const fs::DirEntry &p : entries) + if (p.is_regular && supported(p.path)) + dialog_files_.push_back(p.path); } // Also supported documents in the working dir. - for (auto &p : fs::directory_iterator(fs::current_path())) - if (p.is_regular_file() && supported(p.path())) - dialog_files_.push_back(p.path().filename().string()); + entries.clear(); + fs::ListDirectory(fs::CurrentPath(), &entries); + for (const fs::DirEntry &p : entries) + if (p.is_regular && supported(p.path)) + dialog_files_.push_back(base::String(fs::Filename(p.path))); dialog_open_ = true; MarkDirty(); } @@ -1539,7 +1537,7 @@ void Editor::RunAutopilot() { BeginScenePick(px.x, px.y); }; auto check_sel = [&](const char *expect) { - std::string got = + base::String got = selection_.primary() ? EntityLabel(selection_.primary()) : "(none)"; RX_INFO("autopilot: selection = '{}' expected '{}' -> {}", got, expect, got == expect ? "PASS" : "FAIL"); @@ -1587,7 +1585,7 @@ void Editor::RunAutopilot() { scene::Transform *t = world_->Get(find_named("Cube")); RX_INFO("autopilot: undo -> cube z={:.2f} expected 0 -> {}", t ? t->position[2] : -99.f, - (t && std::fabs(t->position[2]) < 1e-3f) ? "PASS" : "FAIL"); + (t && ::fabs(t->position[2]) < 1e-3f) ? "PASS" : "FAIL"); MarkDirty(); break; } @@ -1596,7 +1594,7 @@ void Editor::RunAutopilot() { scene::Transform *t = world_->Get(find_named("Cube")); RX_INFO("autopilot: redo -> cube z={:.2f} expected 2 -> {}", t ? t->position[2] : -99.f, - (t && std::fabs(t->position[2] - 2.0f) < 1e-3f) ? "PASS" : "FAIL"); + (t && ::fabs(t->position[2] - 2.0f) < 1e-3f) ? "PASS" : "FAIL"); MarkDirty(); break; } @@ -1614,12 +1612,12 @@ void Editor::RunAutopilot() { brush.falloff = 1.0f; terrain::TerrainChange change = terrain_.ApplyBrush(brush); RebuildTerrainTiles( - std::span(change.dirty_tiles.data(), + base::Span(change.dirty_tiles.data(), change.dirty_tiles.size()), true); terrain_after = terrain_.SampleHeight(0, 0).value_or(-99.0f); const bool pass = !change.empty() && terrain_after > terrain_before; - RecordTerrainChange(std::move(change), "Autopilot Terrain Stroke"); + RecordTerrainChange(base::move(change), "Autopilot Terrain Stroke"); terrain_dirty_ = true; RX_INFO("autopilot: terrain stroke {:.3f} -> {:.3f} -> {}", terrain_before, terrain_after, pass ? "PASS" : "FAIL"); @@ -1630,7 +1628,7 @@ void Editor::RunAutopilot() { const f32 height = terrain_.SampleHeight(0, 0).value_or(-99.0f); RX_INFO("autopilot: terrain undo height={:.3f} expected {:.3f} -> {}", height, terrain_before, - std::fabs(height - terrain_before) < 1e-4f ? "PASS" : "FAIL"); + ::fabs(height - terrain_before) < 1e-4f ? "PASS" : "FAIL"); break; } case 520: { @@ -1638,7 +1636,7 @@ void Editor::RunAutopilot() { const f32 height = terrain_.SampleHeight(0, 0).value_or(-99.0f); RX_INFO("autopilot: terrain redo height={:.3f} expected {:.3f} -> {}", height, terrain_after, - std::fabs(height - terrain_after) < 1e-4f ? "PASS" : "FAIL"); + ::fabs(height - terrain_after) < 1e-4f ? "PASS" : "FAIL"); break; } case 530: @@ -1663,10 +1661,10 @@ void Editor::RunAutopilot() { scene::Transform *t = e ? world_->Get(e) : nullptr; RX_INFO("autopilot: after load cube z={:.2f} expected 2 -> {}", t ? t->position[2] : -99.f, - (t && std::fabs(t->position[2] - 2.0f) < 1e-3f) ? "PASS" : "FAIL"); + (t && ::fabs(t->position[2] - 2.0f) < 1e-3f) ? "PASS" : "FAIL"); const f32 height = terrain_.SampleHeight(0, 0).value_or(-99.0f); - const bool sidecar_pass = fs::exists("scene_saved.rxterrain") && - std::fabs(height - terrain_after) < 2e-3f; + const bool sidecar_pass = fs::Exists("scene_saved.rxterrain") && + ::fabs(height - terrain_after) < 2e-3f; RX_INFO("autopilot: terrain sidecar height={:.3f} expected {:.3f} -> {}", height, terrain_after, sidecar_pass ? "PASS" : "FAIL"); break; @@ -1688,7 +1686,7 @@ void Editor::RunAutopilot() { scene_path_ = "scene_saved.rxscene"; doc_dirty_ = false; DoBakeWorld(); - const bool baked = fs::exists("scene_saved.rxp"); + const bool baked = fs::Exists("scene_saved.rxp"); RX_INFO("autopilot: bake world -> {} -> {}", status_message_, baked ? "PASS" : "FAIL"); break; } @@ -1709,28 +1707,28 @@ void Editor::OnFrameEnd() { // capture RX_EDITOR_QUIT_FRAME=n quit at frame n static int frames = 0; ++frames; - if (const char *p = std::getenv("RX_EDITOR_SHOT"); p && renderer_) { - int every = std::getenv("RX_EDITOR_SHOT_EVERY") - ? std::atoi(std::getenv("RX_EDITOR_SHOT_EVERY")) + if (const char *p = ::getenv("RX_EDITOR_SHOT"); p && renderer_) { + int every = ::getenv("RX_EDITOR_SHOT_EVERY") + ? ::atoi(::getenv("RX_EDITOR_SHOT_EVERY")) : 0; - int at = std::getenv("RX_EDITOR_SHOT_FRAME") - ? std::atoi(std::getenv("RX_EDITOR_SHOT_FRAME")) + int at = ::getenv("RX_EDITOR_SHOT_FRAME") + ? ::atoi(::getenv("RX_EDITOR_SHOT_FRAME")) : 20; if (every > 0) { if (frames % every == 0) { char path[1024]; - std::snprintf(path, sizeof(path), "%s.%03d.png", p, frames / every); + ::snprintf(path, sizeof(path), "%s.%03d.png", p, frames / every); renderer_->CaptureScreenshot(path); } } else { if (frames == at) renderer_->CaptureScreenshot(p); - if (frames == at + 4 && std::getenv("RX_EDITOR_SHOT_QUIT")) + if (frames == at + 4 && ::getenv("RX_EDITOR_SHOT_QUIT")) host_->RequestQuit(); } } - if (const char *q = std::getenv("RX_EDITOR_QUIT_FRAME")) { - if (frames >= std::atoi(q)) + if (const char *q = ::getenv("RX_EDITOR_QUIT_FRAME")) { + if (frames >= ::atoi(q)) host_->RequestQuit(); } } diff --git a/apps/editor/editor_app.h b/apps/editor/editor_app.h index 65f8e9a4..61c590c0 100644 --- a/apps/editor/editor_app.h +++ b/apps/editor/editor_app.h @@ -1,15 +1,7 @@ #ifndef RX_EDITOR_APP_H_ #define RX_EDITOR_APP_H_ -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include "anim/body_dynamics.h" #include "anim/locomotion.h" @@ -20,6 +12,14 @@ #include "core/input.h" #include "core/math.h" +#include "base/containers/map.h" +#include "base/containers/pair.h" +#include "base/containers/span.h" +#include "base/containers/vector.h" +#include "base/memory/move.h" +#include "base/optional.h" +#include "base/containers/unordered_map.h" +#include "base/strings/xstring.h" #include "edit/hierarchy.h" #include "edit/reflect.h" #include "world/world_bake.h" @@ -49,16 +49,16 @@ enum class WalkPreviewMode { kAuto, kHipSway, kMarch }; // A content-browser entry discovered by scanning the mounted asset dir. struct AssetEntry { - std::string path; // built-in URI or source filesystem path - std::string name; // basename - std::string kind; // mesh/terrain/texture/material/audio/scene/model + base::String path; // built-in URI or source filesystem path + base::String name; // basename + base::String kind; // mesh/terrain/texture/material/audio/scene/model }; // A CPU copy of an uploaded mesh, kept for ray-vs-triangle picking (the // renderer does not read geometry back). struct MeshRecord { asset::Mesh mesh; - std::string name; + base::String name; }; struct ImportedSkin { @@ -87,9 +87,9 @@ struct ImportedInstance { }; struct ImportedModel { - std::string source_path; - std::vector skins; - std::vector instances; + base::String source_path; + base::Vector skins; + base::Vector instances; ecs::Entity turntable_entity; Vec3 turntable_center; f32 preview_time = 0; @@ -126,26 +126,30 @@ struct TerrainTileVisual { asset::AssetId mesh; }; +inline base::Pair TerrainTileMapKey(terrain::TerrainTileKey key) { + return {key.x, key.z}; +} + struct TerrainStroke { bool active = false; terrain::TerrainChange change; Vec3 last_dab{}; f32 flatten_target = 0; - std::string label; + base::String label; }; struct PlacementBrush { bool armed = false; bool dragging = false; asset::AssetId mesh; - std::string name; + base::String name; f32 spacing = 1.5f; Vec3 last_position{}; }; class Editor final : public app::Application { public: - explicit Editor(std::string open_path) : open_path_(std::move(open_path)) {} + explicit Editor(base::String open_path) : open_path_(base::move(open_path)) {} bool OnInitialize(app::Services &services) override; void OnUpdate(f32 dt) override; @@ -156,12 +160,12 @@ class Editor final : public app::Application { private: // scene / assets (editor_app.cc) void SetupDefaultScene(); - asset::AssetId UploadPrimitive(const std::string &name, + asset::AssetId UploadPrimitive(const base::String &name, const asset::Mesh &mesh); - ecs::Entity SpawnMesh(const std::string &mesh_name, asset::AssetId mesh, - const Vec3 &pos, const std::string &label); + ecs::Entity SpawnMesh(const base::String &mesh_name, asset::AssetId mesh, + const Vec3 &pos, const base::String &label); void ScanAssets(); - bool LoadModelDocument(const std::string &path); + bool LoadModelDocument(const base::String &path); void UpdateImportedModels(f32 dt); u32 ConfigureImportedBody(ImportedSkin *skin); @@ -185,35 +189,35 @@ class Editor final : public app::Application { void SetupDefaultTerrain(); void ClearTerrainVisuals(); void RebuildTerrainVisuals(); - void RebuildTerrainTiles(std::span keys, + void RebuildTerrainTiles(base::Span keys, bool live); ecs::Entity SpawnTerrainTile(terrain::TerrainTileKey key, asset::AssetId mesh); bool IsTerrainVisual(ecs::Entity entity) const; - std::pair ViewportCameraRay(f32 mx, f32 my) const; + base::Pair ViewportCameraRay(f32 mx, f32 my) const; void UpdateModeInteraction(bool lmb_down, bool lmb_edge); void FinishTerrainStroke(); void FinishPlacementDrag(); void SetEditorMode(EditorMode mode); - void AppendInteractionPreview(std::vector *lines) const; - void LoadTerrainAsset(const std::string &path); + void AppendInteractionPreview(base::Vector *lines) const; + void LoadTerrainAsset(const base::String &path); void ArmPlacement(const AssetEntry &asset); asset::AssetId ResolvePlacementMesh(const AssetEntry &asset); void RecordTerrainChange(terrain::TerrainChange change, - const std::string &label); - void OnTerrainCommandReplayed(std::span keys); - void SyncTerrainRayTracing(std::span keys); + const base::String &label); + void OnTerrainCommandReplayed(base::Span keys); + void SyncTerrainRayTracing(base::Span keys); // file ops (editor_app.cc) void NewScene(); - void DoSave(const std::string &path); + void DoSave(const base::String &path); // Bake World: save first, then cook what was saved into .rxp. The cook // reads the file rather than this live world - the editor's world holds // transients the author never wrote (terrain tile visuals, preview models), // and edit::SaveScene already knows which of those to leave out. void DoBakeWorld(); - void DoLoad(const std::string &path); - void OpenDocument(const std::string &path); + void DoLoad(const base::String &path); + void OpenDocument(const base::String &path); void OpenFileDialog(); void RunAutopilot(); // RX_EDITOR_AUTOPILOT smoke driver @@ -223,26 +227,26 @@ class Editor final : public app::Application { void UiFeedInput(f32 dt); void UiPerFrameText(); void UiRebuild(); - std::string UiBuildDoc(); - std::string BuildHierarchy(); - std::string BuildInspector(); - std::string BuildContent(); - std::string BuildDialog(); - std::string BuildModeToolbar(); - std::string BuildInspectorTabs(); - std::string BuildTerrainInspector(); - std::string BuildPlacementInspector(); + base::String UiBuildDoc(); + base::String BuildHierarchy(); + base::String BuildInspector(); + base::String BuildContent(); + base::String BuildDialog(); + base::String BuildModeToolbar(); + base::String BuildInspectorTabs(); + base::String BuildTerrainInspector(); + base::String BuildPlacementInspector(); void UiHotReloadCheck(f32 dt); void UpdateGizmoWidgets(); void OnUiClick(ugui::wid w, ugui::MouseButton btn); - void OnUiTextSubmit(const std::string &widget, const std::string &value); - bool RouteClick(const std::string &name, ugui::MouseButton btn); + void OnUiTextSubmit(const base::String &widget, const base::String &value); + bool RouteClick(const base::String &name, ugui::MouseButton btn); void MarkDirty() { ui_dirty_ = true; } void SetDocDirty(bool d) { doc_dirty_ = d; } // helpers const MeshRecord *FindMesh(u64 hash) const; - std::string EntityLabel(ecs::Entity e) const; + base::String EntityLabel(ecs::Entity e) const; app::Services *services_ = nullptr; app::Host *host_ = nullptr; @@ -254,51 +258,53 @@ class Editor final : public app::Application { asset::Vfs *vfs_ = nullptr; bool headless_ = false; - std::string open_path_; // scene/gltf passed on argv + base::String open_path_; // scene/gltf passed on argv // editor state FlyCamera camera_; edit::Selection selection_; edit::UndoStack undo_; - std::optional assets_; // constructed once vfs is known + base::Optional assets_; // constructed once vfs is known GizmoMode gizmo_mode_ = GizmoMode::kTranslate; EditorMode editor_mode_ = EditorMode::kSelect; Scrub scrub_; GizmoDrag gizmo_drag_; - std::string scene_path_ = "untitled.rxscene"; + base::String scene_path_ = "untitled.rxscene"; // Held once, so the Bake World action and the inspector's per-entity verdict // cannot disagree about the partition. The cell size in particular is not // scene data, and a label computed against a different one than the archive // was cooked with would be quietly wrong. world::WorldBakeOptions world_bake_options_; - std::string terrain_path_ = "untitled.rxterrain"; - std::string asset_root_ = "assets"; + base::String terrain_path_ = "untitled.rxterrain"; + base::String asset_root_ = "assets"; bool doc_dirty_ = false; // scene has unsaved changes bool terrain_dirty_ = false; bool terrain_command_replayed_ = false; - std::string status_message_; + base::String status_message_; bool playing_ = false; WalkPreviewMode walk_preview_mode_ = WalkPreviewMode::kAuto; bool material_tab_ = false; bool add_menu_open_ = false; bool dialog_open_ = false; - std::vector dialog_files_; + base::Vector dialog_files_; - std::string search_filter_; - std::string content_filter_; + base::String search_filter_; + base::String content_filter_; - std::unordered_map meshes_; + base::UnorderedMap meshes_; asset::AssetId cube_mesh_, sphere_mesh_, plane_mesh_, terrain_material_; - std::vector assets_list_; - std::unordered_map placement_meshes_; - std::vector imported_models_; + base::Vector assets_list_; + base::UnorderedMap placement_meshes_; + base::Vector imported_models_; // Full entity handle -> (model index, instance index). Including the // generation prevents a reused ECS slot from inheriting stale skin state. - std::unordered_map> imported_entities_; + base::UnorderedMap> imported_entities_; terrain::Terrain terrain_; - std::map terrain_tiles_; + // Ordered by (x, z): save respawns tiles in key order, which fixes their + // entity ids. Keyed by a Pair because TerrainTileKey has no ordering. + base::Map, TerrainTileVisual> terrain_tiles_; terrain::TerrainBrushMode terrain_brush_mode_ = terrain::TerrainBrushMode::kRaise; f32 terrain_brush_radius_ = 2.0f; @@ -307,25 +313,25 @@ class Editor final : public app::Application { u32 terrain_brush_layer_ = 0; TerrainStroke terrain_stroke_; PlacementBrush placement_; - std::optional terrain_cursor_hit_; - std::optional placement_preview_; + base::Optional terrain_cursor_hit_; + base::Optional placement_preview_; // per-entity tint override (0 = none), for material-tint editing. - std::unordered_map tints_; + base::UnorderedMap tints_; // Display names live in scene::Name components (the ECS column-relocation - // bug that corrupted std::string components is fixed on feature/editor-core). - void SetName(ecs::Entity e, const std::string &name); - std::string GetName(ecs::Entity e) const; + // bug that corrupted base::String components is fixed on feature/editor-core). + void SetName(ecs::Entity e, const base::String &name); + base::String GetName(ecs::Entity e) const; // Engine GPU picking: pick_id -> entity map rebuilt each gather, plus the // in-flight request (results arrive 1-2 frames later). - std::unordered_map pick_map_; + base::UnorderedMap pick_map_; bool pick_pending_ = false; // Debug-line storage for the frame (FrameView holds spans into these). - std::vector grid_lines_; - std::vector gizmo_lines_; + base::Vector grid_lines_; + base::Vector gizmo_lines_; // input edge tracking bool prev_lmb_ = false, prev_rmb_ = false; @@ -342,7 +348,7 @@ class Editor final : public app::Application { const ugui::DrawData *draw_data_ = nullptr; bool ui_ready_ = false; bool ui_dirty_ = true; // widget tree needs a rebuild - std::string ui_dir_; + base::String ui_dir_; int64_t ui_mtime_ = 0; f32 reload_timer_ = 0; }; diff --git a/apps/editor/editor_terrain.cc b/apps/editor/editor_terrain.cc index 70a5393b..d568f3d9 100644 --- a/apps/editor/editor_terrain.cc +++ b/apps/editor/editor_terrain.cc @@ -1,21 +1,28 @@ #include "editor_app.h" -#include -#include -#include -#include -#include -#include -#include +#include +#include #include "asset/gltf_loader.h" +#include "base/algorithm.h" +#include "base/containers/array.h" +#include "base/containers/pair.h" +#include "base/containers/span.h" +#include "base/containers/vector.h" +#include "base/functional/function.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/optional.h" +#include "base/strings/xstring.h" +#include "core/format.h" +#include "core/file_system.h" #include "core/log.h" +#include "core/scalar.h" #include "edit/reflect.h" #include "render/core/renderer.h" #include "scene/components.h" namespace rx::editor { -namespace fs = std::filesystem; namespace { @@ -30,19 +37,19 @@ const char* BrushLabel(terrain::TerrainBrushMode mode) { return "Edit Terrain"; } -u32 PackRgba(const std::array& color) { +u32 PackRgba(const base::Array& color) { return (static_cast(color[0]) << 24) | (static_cast(color[1]) << 16) | (static_cast(color[2]) << 8) | color[3]; } class TerrainCommand final : public edit::Command { public: - using Refresh = std::function)>; + using Refresh = base::Function)>; - TerrainCommand(terrain::Terrain* target, terrain::TerrainChange change, std::string label, + TerrainCommand(terrain::Terrain* target, terrain::TerrainChange change, base::String label, Refresh refresh) - : target_(target), change_(std::move(change)), label_(std::move(label)), - refresh_(std::move(refresh)) {} + : target_(target), change_(base::move(change)), label_(base::move(label)), + refresh_(base::move(refresh)) {} void Apply(ecs::World&) override { // A failed apply means the terrain no longer matches the recorded @@ -65,13 +72,13 @@ class TerrainCommand final : public edit::Command { private: void RefreshTiles() { - refresh_(std::span(change_.dirty_tiles.data(), + refresh_(base::Span(change_.dirty_tiles.data(), change_.dirty_tiles.size())); } terrain::Terrain* target_; terrain::TerrainChange change_; - std::string label_; + base::String label_; Refresh refresh_; }; @@ -84,11 +91,11 @@ void Editor::SetupDefaultTerrain() { desc.tile_quads = 16; desc.sample_spacing = 0.5f; - auto add_layer = [&](const char* name, std::array color) { + auto add_layer = [&](const char* name, base::Array color) { terrain::TerrainLayer layer; layer.name = name; layer.debug_rgba = color; - desc.layers.push_back(std::move(layer)); + desc.layers.push_back(base::move(layer)); }; add_layer("Meadow", {82, 122, 67, 255}); add_layer("Earth", {126, 87, 55, 255}); @@ -96,15 +103,15 @@ void Editor::SetupDefaultTerrain() { add_layer("Sand", {184, 157, 103, 255}); ClearTerrainVisuals(); - terrain_ = terrain::Terrain(std::move(desc)); + terrain_ = terrain::Terrain(base::move(desc)); // The previous terrain may have had more layers; a stale out-of-range // selection makes every paint dab silently no-op. terrain_brush_layer_ = 0; const u32 side = terrain_.samples_per_side(); const u32 quads = terrain_.desc().tile_quads; const f32 spacing = terrain_.desc().sample_spacing; - std::vector heights(static_cast(side) * side); - std::vector weights(heights.size()); + base::Vector heights(static_cast(side) * side); + base::Vector weights(heights.size()); for (i32 tile_z = -2; tile_z < 2; ++tile_z) { for (i32 tile_x = -2; tile_x < 2; ++tile_x) { @@ -114,14 +121,14 @@ void Editor::SetupDefaultTerrain() { const i32 grid_z = tile_z * static_cast(quads) + static_cast(z); const f32 world_x = grid_x * spacing; const f32 world_z = grid_z * spacing; - const f32 broad = 0.46f * std::sin(world_x * 0.22f) * std::cos(world_z * 0.18f); - const f32 detail = 0.17f * std::sin((world_x + world_z) * 0.48f) + - 0.10f * std::cos((world_x - world_z) * 0.61f); - const f32 knoll = 0.30f * std::exp(-(world_x * world_x + world_z * world_z) / 95.0f); + const f32 broad = 0.46f * ::sin(world_x * 0.22f) * ::cos(world_z * 0.18f); + const f32 detail = 0.17f * ::sin((world_x + world_z) * 0.48f) + + 0.10f * ::cos((world_x - world_z) * 0.61f); + const f32 knoll = 0.30f * ::exp(-(world_x * world_x + world_z * world_z) / 95.0f); const size_t sample = static_cast(z) * side + x; heights[sample] = broad + detail + knoll; - const f32 patch = std::sin(world_x * 0.31f) * std::cos(world_z * 0.27f); + const f32 patch = ::sin(world_x * 0.31f) * ::cos(world_z * 0.27f); if (heights[sample] > 0.62f) { weights[sample].rgba = {40, 25, 190, 0}; } else if (heights[sample] < -0.25f) { @@ -173,7 +180,7 @@ void Editor::ClearTerrainVisuals() { void Editor::RebuildTerrainVisuals() { ClearTerrainVisuals(); for (const terrain::TerrainTile& tile : terrain_.tiles()) { - std::optional mesh = terrain_.BuildTileMesh(tile.key, terrain_material_); + base::Optional mesh = terrain_.BuildTileMesh(tile.key, terrain_material_); if (!mesh) { RX_WARN("editor: failed to build terrain tile {},{}", tile.key.x, tile.key.z); continue; @@ -184,22 +191,23 @@ void Editor::RebuildTerrainVisuals() { tile.key.z); } const asset::AssetId mesh_id = mesh->id; - const std::string name = "Terrain [" + std::to_string(tile.key.x) + "," + - std::to_string(tile.key.z) + "]"; - meshes_[mesh_id.hash] = MeshRecord{std::move(*mesh), name}; + const base::String name = "Terrain [" + rx::ToString(tile.key.x) + "," + + rx::ToString(tile.key.z) + "]"; + meshes_[mesh_id.hash] = MeshRecord{base::move(*mesh), name}; ecs::Entity entity = SpawnTerrainTile(tile.key, mesh_id); - terrain_tiles_.emplace(tile.key, TerrainTileVisual{tile.key, entity, mesh_id}); + terrain_tiles_.emplace(TerrainTileMapKey(tile.key), + TerrainTileVisual{tile.key, entity, mesh_id}); } } -void Editor::RebuildTerrainTiles(std::span keys, bool live) { +void Editor::RebuildTerrainTiles(base::Span keys, bool live) { for (terrain::TerrainTileKey key : keys) { - auto visual = terrain_tiles_.find(key); + auto visual = terrain_tiles_.find(TerrainTileMapKey(key)); if (visual == terrain_tiles_.end()) { RX_WARN("editor: no visual mapping for dirty terrain tile {},{}", key.x, key.z); continue; } - std::optional mesh = terrain_.BuildTileMesh(key, terrain_material_); + base::Optional mesh = terrain_.BuildTileMesh(key, terrain_material_); if (!mesh) continue; if (renderer_) { const bool updated = live ? renderer_->UpdateDynamicMesh(*mesh) @@ -208,9 +216,9 @@ void Editor::RebuildTerrainTiles(std::span keys, RX_WARN("editor: failed to {} terrain tile {},{}", live ? "update" : "upload", key.x, key.z); } - const std::string name = "Terrain [" + std::to_string(key.x) + "," + - std::to_string(key.z) + "]"; - meshes_[mesh->id.hash] = MeshRecord{std::move(*mesh), name}; + const base::String name = "Terrain [" + rx::ToString(key.x) + "," + + rx::ToString(key.z) + "]"; + meshes_[mesh->id.hash] = MeshRecord{base::move(*mesh), name}; } } @@ -222,12 +230,12 @@ bool Editor::IsTerrainVisual(ecs::Entity entity) const { return false; } -std::pair Editor::ViewportCameraRay(f32 mx, f32 my) const { +base::Pair Editor::ViewportCameraRay(f32 mx, f32 my) const { const f32 width = static_cast(window_->width()); const f32 height = static_cast(window_->height()); const f32 ndc_x = 2.0f * mx / width - 1.0f; const f32 ndc_y = 1.0f - 2.0f * my / height; - const f32 tan_half_fov = std::tan(1.0472f * 0.5f); + const f32 tan_half_fov = ::tan(1.0472f * 0.5f); const Vec3 forward = camera_.forward(); const Vec3 right = Normalize(Cross(forward, {0, 1, 0})); const Vec3 up = Cross(right, forward); @@ -236,14 +244,14 @@ std::pair Editor::ViewportCameraRay(f32 mx, f32 my) const { return {camera_.position(), direction}; } -void Editor::RecordTerrainChange(terrain::TerrainChange change, const std::string& label) { +void Editor::RecordTerrainChange(terrain::TerrainChange change, const base::String& label) { if (change.empty()) return; - undo_.RecordApplied(std::make_unique( - &terrain_, std::move(change), label, - [this](std::span keys) { OnTerrainCommandReplayed(keys); })); + undo_.RecordApplied(base::MakeUnique( + &terrain_, base::move(change), label, + [this](base::Span keys) { OnTerrainCommandReplayed(keys); })); } -void Editor::OnTerrainCommandReplayed(std::span keys) { +void Editor::OnTerrainCommandReplayed(base::Span keys) { terrain_command_replayed_ = true; terrain_dirty_ = true; RebuildTerrainTiles(keys, true); @@ -252,10 +260,10 @@ void Editor::OnTerrainCommandReplayed(std::span k } void Editor::SyncTerrainRayTracing( - std::span keys) { + base::Span keys) { if (!renderer_) return; for (terrain::TerrainTileKey key : keys) { - const auto visual = terrain_tiles_.find(key); + const auto visual = terrain_tiles_.find(TerrainTileMapKey(key)); if (visual == terrain_tiles_.end()) continue; const MeshRecord* record = FindMesh(visual->second.mesh.hash); if (record) renderer_->SyncDynamicMeshRayTracing(record->mesh); @@ -266,7 +274,7 @@ void Editor::FinishTerrainStroke() { if (!terrain_stroke_.active) return; if (!terrain_stroke_.change.empty()) { SyncTerrainRayTracing(terrain_stroke_.change.dirty_tiles); - RecordTerrainChange(std::move(terrain_stroke_.change), terrain_stroke_.label); + RecordTerrainChange(base::move(terrain_stroke_.change), terrain_stroke_.label); } terrain_stroke_ = {}; } @@ -354,7 +362,7 @@ void Editor::UpdateModeInteraction(bool lmb_down, bool lmb_edge) { for (terrain::TerrainTileKey key : dab.dirty_tiles) dirty_tiles.push_back(key); if (terrain_stroke_.change.empty()) { - terrain_stroke_.change = std::move(dab); + terrain_stroke_.change = base::move(dab); } else if (!terrain::MergeTerrainChanges(&terrain_stroke_.change, dab)) { RX_WARN("editor: could not merge terrain brush dab into stroke"); } @@ -362,8 +370,14 @@ void Editor::UpdateModeInteraction(bool lmb_down, bool lmb_edge) { MarkDirty(); }; auto upload_dirty_tiles = [&] { - std::sort(dirty_tiles.begin(), dirty_tiles.end()); - dirty_tiles.erase(std::unique(dirty_tiles.begin(), dirty_tiles.end()), + // Duplicates compare equal in every field, so after Unique any correct + // sort leaves the same sequence. + base::Sort(dirty_tiles.data(), dirty_tiles.data() + dirty_tiles.size(), + [](terrain::TerrainTileKey a, terrain::TerrainTileKey b) { + return TerrainTileMapKey(a) < TerrainTileMapKey(b); + }); + dirty_tiles.erase(base::Unique(dirty_tiles.data(), + dirty_tiles.data() + dirty_tiles.size()), dirty_tiles.end()); RebuildTerrainTiles(dirty_tiles, true); }; @@ -377,10 +391,10 @@ void Editor::UpdateModeInteraction(bool lmb_down, bool lmb_edge) { } const Vec3 delta = current - terrain_stroke_.last_dab; const f32 distance = Length(delta); - const f32 spacing = std::max(terrain_.desc().sample_spacing * 0.5f, + const f32 spacing = rx::Max(terrain_.desc().sample_spacing * 0.5f, terrain_brush_radius_ * 0.20f); if (distance < spacing) return; - const int steps = std::min(64, static_cast(distance / spacing)); + const int steps = rx::Min(64, static_cast(distance / spacing)); const Vec3 start = terrain_stroke_.last_dab; for (int i = 1; i <= steps; ++i) apply_dab(start + delta * (static_cast(i) * spacing / distance)); @@ -399,9 +413,9 @@ void Editor::UpdateModeInteraction(bool lmb_down, bool lmb_edge) { f32 vertical_offset = 0; if (const MeshRecord* record = FindMesh(placement_.mesh.hash); record && !record->mesh.lods.empty() && !record->mesh.lods[0].vertices.empty()) { - f32 min_y = std::numeric_limits::max(); + f32 min_y = FLT_MAX; for (const asset::Vertex& vertex : record->mesh.lods[0].vertices) - min_y = std::min(min_y, vertex.position[1]); + min_y = rx::Min(min_y, vertex.position[1]); vertical_offset = -min_y; } if (over_viewport) { @@ -411,7 +425,7 @@ void Editor::UpdateModeInteraction(bool lmb_down, bool lmb_edge) { if (terrain_cursor_hit_) { position = terrain_cursor_hit_->position; } else { - const f32 plane_t = std::fabs(direction.y) > 1e-5f ? -origin.y / direction.y : -1.0f; + const f32 plane_t = ::fabs(direction.y) > 1e-5f ? -origin.y / direction.y : -1.0f; position = plane_t > 0 ? origin + direction * plane_t : origin + direction * 5.0f; } position.y += vertical_offset; @@ -429,8 +443,8 @@ void Editor::UpdateModeInteraction(bool lmb_down, bool lmb_edge) { const edit::ComponentDesc* renderable = edit::FindComponentByName("Renderable"); const edit::ComponentDesc* name = edit::FindComponentByName("Name"); if (!transform || !renderable || !name) return; - std::vector>>> + base::Vector>>> initial; initial.push_back( {transform, {{&transform->props[0], edit::PropValue::Vec3(position.x, position.y, @@ -438,13 +452,13 @@ void Editor::UpdateModeInteraction(bool lmb_down, bool lmb_edge) { initial.push_back( {renderable, {{&renderable->props[0], edit::PropValue::AssetIdV(placement_.mesh.hash)}}}); initial.push_back({name, {{&name->props[0], edit::PropValue::String(placement_.name)}}}); - undo_.Push(*world_, edit::MakeCreateEntity(std::move(initial), nullptr)); + undo_.Push(*world_, edit::MakeCreateEntity(base::move(initial), nullptr)); doc_dirty_ = true; MarkDirty(); }; if (lmb_edge) { - const std::string label = "Place " + placement_.name; + const base::String label = "Place " + placement_.name; undo_.BeginGroup(label.c_str()); placement_.dragging = true; placement_.last_position = *placement_preview_; @@ -456,10 +470,10 @@ void Editor::UpdateModeInteraction(bool lmb_down, bool lmb_edge) { const f32 distance = Length(delta); if (distance < placement_.spacing) return; const Vec3 start = placement_.last_position; - const int steps = std::min(64, static_cast(distance / placement_.spacing)); + const int steps = rx::Min(64, static_cast(distance / placement_.spacing)); for (int i = 1; i <= steps; ++i) { Vec3 position = start + delta * (static_cast(i) * placement_.spacing / distance); - if (std::optional height = terrain_.SampleHeight(position.x, position.z)) + if (base::Optional height = terrain_.SampleHeight(position.x, position.z)) position.y = *height + vertical_offset; place(position); } @@ -467,7 +481,7 @@ void Editor::UpdateModeInteraction(bool lmb_down, bool lmb_edge) { start + delta * (static_cast(steps) * placement_.spacing / distance); } -void Editor::AppendInteractionPreview(std::vector* lines) const { +void Editor::AppendInteractionPreview(base::Vector* lines) const { if (!lines) return; Vec3 center; f32 radius = 0; @@ -502,24 +516,24 @@ void Editor::AppendInteractionPreview(std::vector* lines) con Vec3 previous; for (int i = 0; i <= kSegments; ++i) { const f32 angle = static_cast(i) * 6.2831853f / kSegments; - Vec3 point{center.x + std::cos(angle) * radius, center.y + 0.035f, - center.z + std::sin(angle) * radius}; + Vec3 point{center.x + ::cos(angle) * radius, center.y + 0.035f, + center.z + ::sin(angle) * radius}; if (editor_mode_ == EditorMode::kTerrain) { - if (std::optional y = terrain_.SampleHeight(point.x, point.z)) point.y = *y + 0.035f; + if (base::Optional y = terrain_.SampleHeight(point.x, point.z)) point.y = *y + 0.035f; } if (i > 0) lines->push_back({previous, point, color}); previous = point; } - const f32 cross = std::max(0.18f, radius * 0.22f); + const f32 cross = rx::Max(0.18f, radius * 0.22f); lines->push_back({{center.x - cross, center.y + 0.045f, center.z}, {center.x + cross, center.y + 0.045f, center.z}, color}); lines->push_back({{center.x, center.y + 0.045f, center.z - cross}, {center.x, center.y + 0.045f, center.z + cross}, color}); } -void Editor::LoadTerrainAsset(const std::string& path) { +void Editor::LoadTerrainAsset(const base::String& path) { terrain::Terrain loaded; - std::string error; + base::String error; if (!terrain::LoadTerrain(path, &loaded, &error)) { status_message_ = "Terrain load failed: " + error; RX_WARN("editor: {}", status_message_); @@ -529,14 +543,14 @@ void Editor::LoadTerrainAsset(const std::string& path) { FinishTerrainStroke(); FinishPlacementDrag(); ClearTerrainVisuals(); - terrain_ = std::move(loaded); - terrain_path_ = fs::path(scene_path_).replace_extension(".rxterrain").string(); + terrain_ = base::move(loaded); + terrain_path_ = fs::ReplaceExtension(scene_path_, ".rxterrain"); terrain_dirty_ = true; // activating an asset imports it into this scene terrain_brush_layer_ = 0; undo_.Clear(); RebuildTerrainVisuals(); SetEditorMode(EditorMode::kTerrain); - status_message_ = "Activated " + fs::path(path).filename().string(); + status_message_ = "Activated " + base::String(fs::Filename(path)); RX_INFO("editor: loaded terrain {}", path); MarkDirty(); } @@ -545,14 +559,13 @@ asset::AssetId Editor::ResolvePlacementMesh(const AssetEntry& entry) { if (entry.name == "cube.mesh") return cube_mesh_; if (entry.name == "sphere.mesh") return sphere_mesh_; if (entry.name == "plane.mesh") return plane_mesh_; - if (auto cached = placement_meshes_.find(entry.path); cached != placement_meshes_.end()) - return cached->second; + if (const asset::AssetId* cached = placement_meshes_.find(entry.path)) return *cached; - const std::string extension = fs::path(entry.path).extension().string(); + const base::String extension(fs::Extension(entry.path)); if (extension == ".gltf" || extension == ".glb") { asset::ImportedScene scene; if (!asset::LoadGltfScene(entry.path, &scene) || scene.meshes.empty()) return {}; - const std::string source_path = asset::NormalizePath(entry.path); + const base::String source_path = asset::NormalizePath(entry.path); scene.meshes[0].id = asset::MakeAssetId(source_path); asset::RecordAssetPath(scene.meshes[0].id, source_path); if (renderer_) { @@ -569,9 +582,7 @@ asset::AssetId Editor::ResolvePlacementMesh(const AssetEntry& entry) { } if (assets_) { - std::error_code error; - std::string asset_path = fs::relative(entry.path, asset_root_, error).generic_string(); - if (error) asset_path = entry.path; + const base::String asset_path = fs::GenericString(fs::Relative(entry.path, asset_root_)); if (const asset::Mesh* mesh = assets_->LoadMesh(asset_path)) { asset::AssetId id = UploadPrimitive(entry.name, *mesh); placement_meshes_[entry.path] = id; diff --git a/apps/editor/editor_ui.cc b/apps/editor/editor_ui.cc index 16449af7..f51dd8e3 100644 --- a/apps/editor/editor_ui.cc +++ b/apps/editor/editor_ui.cc @@ -1,15 +1,11 @@ // The editor's ugui layer: draw-data-mode UIContext driven by the recreation- // style GuiRenderBackend through FrameView::hud_draw, plus the C++ that // generates the .ugui document from editor state and routes widget events. -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include @@ -19,20 +15,27 @@ #include #include +#include "core/file_system.h" #include "core/log.h" #include "edit/hierarchy.h" #include "editor_app.h" #include "anim/morph.h" +#include "base/algorithm.h" +#include "base/containers/pair.h" +#include "base/containers/vector.h" +#include "base/functional/function.h" +#include "base/memory/move.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/scalar.h" #include "render/rhi/vulkan_interop.h" #include "scene/components.h" namespace rx::editor { -namespace fs = std::filesystem; - namespace { const char *FindFont() { - static std::string resolved; + static base::String resolved; if (FILE *p = popen("fc-match -f '%{file}' sans 2>/dev/null", "r")) { char buf[1024]; size_t n = fread(buf, 1, sizeof(buf) - 1, p); @@ -40,7 +43,7 @@ const char *FindFont() { if (n > 0) { buf[n] = '\0'; resolved = buf; - if (fs::exists(resolved)) + if (fs::Exists(resolved)) return resolved.c_str(); } } @@ -51,12 +54,12 @@ const char *FindFont() { "/run/current-system/sw/share/X11/fonts/DejaVuSans.ttf", }; for (auto *c : candidates) - if (fs::exists(c)) + if (fs::Exists(c)) return c; return nullptr; } -std::string F(const char *fmt, ...) { +base::String F(const char *fmt, ...) { va_list ap; va_start(ap, fmt); va_list measure; @@ -67,14 +70,14 @@ std::string F(const char *fmt, ...) { va_end(ap); return {}; } - std::string result(static_cast(size), '\0'); + base::String result(static_cast(size), '\0'); vsnprintf(result.data(), result.size() + 1, fmt, ap); va_end(ap); return result; } -std::string EscapeUguiString(std::string_view value) { - std::string escaped; +base::String EscapeUguiString(base::StringRef value) { + base::String escaped; escaped.reserve(value.size()); for (char c : value) { switch (c) { @@ -104,13 +107,13 @@ std::string EscapeUguiString(std::string_view value) { void QuatToEuler(const Quat &q, f32 out_deg[3]) { f32 sinr = 2 * (q.w * q.x + q.y * q.z); f32 cosr = 1 - 2 * (q.x * q.x + q.y * q.y); - f32 x = std::atan2(sinr, cosr); + f32 x = ::atan2(sinr, cosr); f32 sinp = 2 * (q.w * q.y - q.z * q.x); f32 y = - std::fabs(sinp) >= 1 ? std::copysign(1.5707963f, sinp) : std::asin(sinp); + ::fabs(sinp) >= 1 ? ::copysign(1.5707963f, sinp) : ::asin(sinp); f32 siny = 2 * (q.w * q.z + q.x * q.y); f32 cosy = 1 - 2 * (q.y * q.y + q.z * q.z); - f32 z = std::atan2(siny, cosy); + f32 z = ::atan2(siny, cosy); const f32 r2d = 57.29578f; out_deg[0] = x * r2d; out_deg[1] = y * r2d; @@ -118,9 +121,9 @@ void QuatToEuler(const Quat &q, f32 out_deg[3]) { } Quat EulerToQuat(const f32 deg[3]) { const f32 d2r = 0.0174533f; - f32 cx = std::cos(deg[0] * d2r * 0.5f), sx = std::sin(deg[0] * d2r * 0.5f); - f32 cy = std::cos(deg[1] * d2r * 0.5f), sy = std::sin(deg[1] * d2r * 0.5f); - f32 cz = std::cos(deg[2] * d2r * 0.5f), sz = std::sin(deg[2] * d2r * 0.5f); + f32 cx = ::cos(deg[0] * d2r * 0.5f), sx = ::sin(deg[0] * d2r * 0.5f); + f32 cy = ::cos(deg[1] * d2r * 0.5f), sy = ::sin(deg[1] * d2r * 0.5f); + f32 cz = ::cos(deg[2] * d2r * 0.5f), sz = ::sin(deg[2] * d2r * 0.5f); return Normalize( Quat{sx * cy * cz - cx * sy * sz, cx * sy * cz + sx * cy * sz, cx * cy * sz - sx * sy * cz, cx * cy * cz + sx * sy * sz}); @@ -128,8 +131,8 @@ Quat EulerToQuat(const f32 deg[3]) { // Engine reflection registers lowercase prop names ("position"); display them // capitalized in the inspector. -std::string DisplayName(const char *n) { - std::string s = n ? n : ""; +base::String DisplayName(const char *n) { + base::String s = n ? n : ""; if (!s.empty() && s[0] >= 'a' && s[0] <= 'z') s[0] = (char)(s[0] - 'a' + 'A'); return s; @@ -144,7 +147,7 @@ const char *AxisColor(int i) { : i == 2 ? "#5a8dee" : "#c9a25a"; } -std::string KindColor(const std::string &k) { +base::String KindColor(const base::String &k) { if (k == "mesh") return "#2f7f6a"; if (k == "terrain") @@ -204,7 +207,7 @@ bool Editor::UiInit() { #ifdef RX_EDITOR_UI_DIR ui_dir_ = RX_EDITOR_UI_DIR; #endif - if (const char *env = std::getenv("RX_EDITOR_UI_DIR")) + if (const char *env = ::getenv("RX_EDITOR_UI_DIR")) ui_dir_ = env; ui_ready_ = true; @@ -289,7 +292,7 @@ void Editor::UiFeedInput(f32) { // Per-frame text + gizmo widget updates void Editor::UiPerFrameText() { - auto set = [&](const char *name, const std::string &v) { + auto set = [&](const char *name, const base::String &v) { ugui::wid w = ui_.FindWidget(name); if (w.valid()) ugui::SetText(w, v); @@ -335,7 +338,7 @@ void Editor::UpdateGizmoWidgets() { scene::Transform wt = e ? edit::WorldTransform(*world_, e) : scene::Transform{}; Vec3 origin{wt.position[0], wt.position[1], wt.position[2]}; - f32 len = e ? std::max(0.5f, Length(origin - camera_.position()) * 0.18f) : 0; + f32 len = e ? rx::Max(0.5f, Length(origin - camera_.position()) * 0.18f) : 0; for (int a = 0; a < 3; ++a) { ugui::wid w = ui_.FindWidget(F("gizmo_%c", "xyz"[a]).c_str()); if (!w.valid()) @@ -365,21 +368,21 @@ void Editor::UpdateGizmoWidgets() { void Editor::UiRebuild() { if (!ui_ready_) return; - std::string doc = UiBuildDoc(); + base::String doc = UiBuildDoc(); ui_.LoadUiString(doc.c_str(), "editor"); ui_.InvalidateWidgetCache(); // Wire text-input handlers (they survive until the next rebuild). if (ugui::wid s = ui_.FindWidget("hier_search"); s.valid()) { ugui::SetTextInputValue(s, search_filter_); - ugui::SetTextInputChange(s, [this](const ugui::String &v) { + ugui::SetTextInputChange(s, [this](const base::String &v) { search_filter_ = v; MarkDirty(); }); } if (ugui::wid c = ui_.FindWidget("content_filter"); c.valid()) { ugui::SetTextInputValue(c, content_filter_); - ugui::SetTextInputChange(c, [this](const ugui::String &v) { + ugui::SetTextInputChange(c, [this](const base::String &v) { content_filter_ = v; MarkDirty(); }); @@ -388,34 +391,34 @@ void Editor::UiRebuild() { if (ecs::Entity e = selection_.primary(); e && world_->IsAlive(e)) ugui::SetTextInputValue(n, EntityLabel(e)); ugui::SetTextInputSubmit( - n, [this](const ugui::String &v) { OnUiTextSubmit("insp_name", v); }); + n, [this](const base::String &v) { OnUiTextSubmit("insp_name", v); }); } ui_.input().RefreshHover(ui_.root()); UiPerFrameText(); ui_dirty_ = false; } -std::string Editor::UiBuildDoc() { - std::string tmpl; - std::string path = - (ui_dir_.empty() ? std::string("apps/editor/ui") : ui_dir_) + +base::String Editor::UiBuildDoc() { + base::String tmpl; + base::String path = + (ui_dir_.empty() ? base::String("apps/editor/ui") : ui_dir_) + "/editor.ugui"; - if (FILE *f = std::fopen(path.c_str(), "rb")) { + if (FILE *f = ::fopen(path.c_str(), "rb")) { char buf[4096]; size_t n; - while ((n = std::fread(buf, 1, sizeof(buf), f)) > 0) + while ((n = ::fread(buf, 1, sizeof(buf), f)) > 0) tmpl.append(buf, n); - std::fclose(f); - ui_mtime_ = (int64_t)fs::last_write_time(path).time_since_epoch().count(); + ::fclose(f); + ui_mtime_ = fs::LastWriteTime(path).value_or(0); } else { RX_WARN("editor: cannot read {}", path); return "panel root { background: #101113; text t { text: \"missing " "editor.ugui\"; color:#f00; } }"; } - auto replace = [&](const char *marker, const std::string &with) { + auto replace = [&](const char *marker, const base::String &with) { size_t p = tmpl.find(marker); - if (p != std::string::npos) - tmpl.replace(p, std::strlen(marker), with); + if (p != base::String::npos) + tmpl = tmpl.substr(0, p) + with + tmpl.substr(p + ::strlen(marker)); }; replace("//@@HIERARCHY@@", BuildHierarchy()); replace("//@@MODES@@", BuildModeToolbar()); @@ -434,17 +437,17 @@ std::string Editor::UiBuildDoc() { return tmpl; } -std::string Editor::BuildHierarchy() { - std::string out; +base::String Editor::BuildHierarchy() { + base::String out; // Recursive emit respecting Parent; filter by search. - std::function emit = [&](ecs::Entity e, int depth) { + base::Function emit = [&](ecs::Entity e, int depth) { if (IsTerrainVisual(e)) return; - std::string label = EntityLabel(e); + base::String label = EntityLabel(e); bool match = search_filter_.empty() || - label.find(search_filter_) != std::string::npos; + label.find(search_filter_) != base::String::npos; if (match) { - const std::string escaped_label = EscapeUguiString(label); + const base::String escaped_label = EscapeUguiString(label); bool sel = selection_.Contains(e); const char *icon = world_->Has(e) ? "#e0b06a" : "#c78ad6"; @@ -479,7 +482,7 @@ std::string Editor::BuildHierarchy() { return out; } -std::string Editor::BuildInspector() { +base::String Editor::BuildInspector() { if (editor_mode_ == EditorMode::kTerrain) return BuildTerrainInspector(); if (editor_mode_ == EditorMode::kPlace) @@ -489,8 +492,8 @@ std::string Editor::BuildInspector() { return "text nosel { text: \"No selection\"; font-size: 13; color: " "#6a6d73; padding: 16; }\n"; - std::string out; - const std::string escaped_entity_label = EscapeUguiString(EntityLabel(e)); + base::String out; + const base::String escaped_entity_label = EscapeUguiString(EntityLabel(e)); out += "panel name_row { layout: row; align: center; padding: 10 12; gap: 8; " "border-color:#101113; border-width:0 0 1 0;\n"; out += " panel ni { width: 8; height: 8; corner-radius: 4; background: " @@ -512,7 +515,7 @@ std::string Editor::BuildInspector() { int subs = 0; if (rec && !rec->mesh.lods.empty()) subs = (int)rec->mesh.lods[0].submeshes.size(); - const std::string escaped_mesh_name = + const base::String escaped_mesh_name = EscapeUguiString(rec ? rec->name : "(none)"); out += F("panel matb { layout: column; padding: 10 12; gap: 8;\n" " panel mrow { layout: row; gap: 8; align:center; text ml { " @@ -524,7 +527,7 @@ std::string Editor::BuildInspector() { "panel sv { class: field; text svv { class: field_val; text: " "\"%d\"; } } }\n", escaped_mesh_name.c_str(), subs); - uint32_t tint = tints_.count(e.index) ? tints_[e.index] : 0xffffff; + uint32_t tint = tints_.contains(e.index) ? tints_[e.index] : 0xffffff; out += F(" panel trow { layout: row; gap: 8; align:center; text tl { " "class: row_label; text: \"Tint\"; }\n" " panel tsw { width: 26; height: 26; corner-radius: 5; " @@ -571,7 +574,7 @@ std::string Editor::BuildInspector() { const f32 to_max_x = verdict.cell_maximum.x - t->position[0]; const f32 to_min_z = t->position[2] - verdict.cell_minimum.z; const f32 to_max_z = verdict.cell_maximum.z - t->position[2]; - const f32 nearest = std::min(std::min(to_min_x, to_max_x), std::min(to_min_z, to_max_z)); + const f32 nearest = rx::Min(rx::Min(to_min_x, to_max_x), rx::Min(to_min_z, to_max_z)); out += F(" text bakecell { text: \"cell %016llx [%.0f, %.0f]..[%.0f, %.0f] @ %g m\"; " "font-size: 11; color: #a8abb2; }\n", static_cast(verdict.cell), @@ -588,7 +591,7 @@ std::string Editor::BuildInspector() { static_cast(nearest), nearest < 1.0f ? "#e0b06a" : "#6a6d73"); } if (!verdict.dropped.empty()) { - std::string dropped; + base::String dropped; for (size_t i = 0; i < verdict.dropped.size(); ++i) { dropped += (i != 0 ? ", " : "") + verdict.dropped[i]; } @@ -601,13 +604,13 @@ std::string Editor::BuildInspector() { // Inspector tab: one section per component. for (const edit::ComponentDesc *comp : edit::ComponentsOn(*world_, e)) { - if (std::string(comp->name) == "Guid") + if (base::String(comp->name) == "Guid") continue; // internal out += "panel ch { class: comp_header;\n"; out += F(" text ct { class: comp_title; text: \"%s\"; }\n", comp->name); out += " panel csp { flex-grow: 1; }\n"; - if (std::string(comp->name) != "Transform" && - std::string(comp->name) != "Name") + if (base::String(comp->name) != "Transform" && + base::String(comp->name) != "Name") out += F(" button insp_rm_%u { text: \"x\"; font-size: 13; color: " "#7a7d83; cursor: pointer; :hover { color:#e0655f; } }\n", comp->id); @@ -627,7 +630,7 @@ std::string Editor::BuildInspector() { : (v.type == edit::PropType::kVec2) ? 2 : 1; if (v.type == edit::PropType::kString) { - const std::string escaped_value = EscapeUguiString(v.s); + const base::String escaped_value = EscapeUguiString(v.s); out += F(" panel fv { class: field; text vv { class: field_val; " "text: \"%s\"; } }\n", escaped_value.c_str()); @@ -639,7 +642,7 @@ std::string Editor::BuildInspector() { } else if (v.type == edit::PropType::kU64 || v.type == edit::PropType::kAssetId || v.type == edit::PropType::kEntity) { - std::string txt; + base::String txt; if (v.type == edit::PropType::kEntity) txt = F("entity %u", v.e.index); else @@ -680,10 +683,10 @@ std::string Editor::BuildInspector() { for (const edit::ComponentDesc *comp : edit::AllComponents()) { if (world_->HasRaw(e, comp->id)) continue; - if (std::string(comp->name) == "Guid") + if (base::String(comp->name) == "Guid") continue; - if (std::string(comp->name) == "Name") - continue; // ECS std::string unsafe; edited via side table + if (base::String(comp->name) == "Name") + continue; // ECS base::String unsafe; edited via side table out += F(" button insp_add_%u { text: \"%s\"; font-size: 12; " "color:#c9ccd2; padding: 7 10; corner-radius:4; cursor: " "pointer; :hover { background:#2c2f34; } }\n", @@ -695,13 +698,13 @@ std::string Editor::BuildInspector() { return out; } -std::string Editor::BuildModeToolbar() { +base::String Editor::BuildModeToolbar() { auto mode_button = [&](const char *id, const char *label, bool active, int width) { return F("button %s { class: %s; width: %d; text: \"%s\"; }\n", id, active ? "tool_btn_on" : "tool_btn", width, label); }; - std::string out; + base::String out; out += mode_button("mode_select", "Select", editor_mode_ == EditorMode::kSelect, 56); out += mode_button("mode_terrain", "Terrain", @@ -736,7 +739,7 @@ std::string Editor::BuildModeToolbar() { return out; } -std::string Editor::BuildInspectorTabs() { +base::String Editor::BuildInspectorTabs() { if (editor_mode_ == EditorMode::kTerrain) { return "text terrain_tab { text: \"TERRAIN WORKSPACE\"; class: " "header_label; color: #d8e2ca; " @@ -747,7 +750,7 @@ std::string Editor::BuildInspectorTabs() { "color: #e7cf9d; " "padding: 0 16; }\n"; } - std::string out; + base::String out; out += F("button tab_inspector { text: \"INSPECTOR\"; class: header_label; " "padding: 0 16; " "height: 32; layout: row; align: center; cursor: pointer; color: " @@ -765,7 +768,7 @@ std::string Editor::BuildInspectorTabs() { return out; } -std::string Editor::BuildTerrainInspector() { +base::String Editor::BuildTerrainInspector() { const char *tool_names[] = {"Raise", "Lower", "Smooth", "Flatten", "Paint"}; const terrain::TerrainBrushMode tool_modes[] = { terrain::TerrainBrushMode::kRaise, terrain::TerrainBrushMode::kLower, @@ -773,7 +776,7 @@ std::string Editor::BuildTerrainInspector() { terrain::TerrainBrushMode::kPaintLayer}; const char *tool_ids[] = {"terrain_raise", "terrain_lower", "terrain_smooth", "terrain_flatten", "terrain_paint"}; - std::string out; + base::String out; out += "panel terrain_asset { layout: column; padding: 12; gap: 6; " "background: #1c211d; " "border-color: #344238; border-width: 0 0 1 0;\n"; @@ -783,7 +786,7 @@ std::string Editor::BuildTerrainInspector() { "TERRAIN\"; " "font-size: 11; font-weight: bold; letter-spacing: 1; color: #cbd8c0; " "} }\n"; - const std::string escaped_path = EscapeUguiString(terrain_path_); + const base::String escaped_path = EscapeUguiString(terrain_path_); out += F(" text ta_path { text: \"%s\"; font-size: 11; color: #8e9b8a; }\n", escaped_path.c_str()); out += F(" text ta_state { text: \"%s\"; font-size: 11; color: %s; }\n", @@ -830,7 +833,7 @@ std::string Editor::BuildTerrainInspector() { for (u32 i = 0; i < terrain_.desc().layers.size(); ++i) { const terrain::TerrainLayer &layer = terrain_.desc().layers[i]; const auto &c = layer.debug_rgba; - const std::string escaped_name = EscapeUguiString(layer.name); + const base::String escaped_name = EscapeUguiString(layer.name); out += F(" button terrain_layer_%u { layout: row; align: center; gap: 10; " "height: 34; " "padding: 0 8; background: %s; corner-radius: 6; border-color: " @@ -859,9 +862,9 @@ std::string Editor::BuildTerrainInspector() { return out; } -std::string Editor::BuildPlacementInspector() { - std::string out; - const std::string escaped_name = EscapeUguiString(placement_.name); +base::String Editor::BuildPlacementInspector() { + base::String out; + const base::String escaped_name = EscapeUguiString(placement_.name); out += "panel place_asset { layout: column; padding: 14; gap: 7; background: " "#211f19; " "border-color: #4a402d; border-width: 0 0 1 0;\n"; @@ -898,12 +901,12 @@ std::string Editor::BuildPlacementInspector() { return out; } -std::string Editor::BuildContent() { - std::string out; +base::String Editor::BuildContent() { + base::String out; size_t idx = 0; for (const AssetEntry &a : assets_list_) { if (!content_filter_.empty() && - a.name.find(content_filter_) == std::string::npos) { + a.name.find(content_filter_) == base::String::npos) { ++idx; continue; } @@ -918,7 +921,7 @@ std::string Editor::BuildContent() { "color: %s; } }\n", idx, KindColor(a.kind).c_str(), a.kind.c_str(), KindColor(a.kind).c_str()); - const std::string escaped_name = EscapeUguiString(a.name); + const base::String escaped_name = EscapeUguiString(a.name); out += F(" text cn { text: \"%s\"; font-size: 11; color: #b7bac0; }\n", escaped_name.c_str()); out += "}\n"; @@ -930,10 +933,10 @@ std::string Editor::BuildContent() { return out; } -std::string Editor::BuildDialog() { +base::String Editor::BuildDialog() { if (!dialog_open_) return ""; - std::string out = "panel dialog_scrim { position: absolute; left: 0; top: 0; " + base::String out = "panel dialog_scrim { position: absolute; left: 0; top: 0; " "width: 100vw; height: 100vh; " "layout: column; justify: center; align: center; " "background: #000000aa;\n"; @@ -948,7 +951,7 @@ std::string Editor::BuildDialog() { out += " text de { text: \"No .rxscene files found\"; font-size: 12; " "color:#6a6d73; padding: 8; }\n"; for (size_t i = 0; i < dialog_files_.size(); ++i) { - const std::string escaped_path = EscapeUguiString(dialog_files_[i]); + const base::String escaped_path = EscapeUguiString(dialog_files_[i]); out += F(" button dlg_%zu { text: \"%s\"; font-size: 13; " "color:#c9ccd2; padding: 8 10; corner-radius: 5; " "cursor: pointer; :hover { background:#2f4368; color:#fff; } }\n", @@ -969,14 +972,13 @@ void Editor::UiHotReloadCheck(f32 dt) { if (reload_timer_ < 0.3f) return; reload_timer_ = 0; - std::string path = - (ui_dir_.empty() ? std::string("apps/editor/ui") : ui_dir_) + + base::String path = + (ui_dir_.empty() ? base::String("apps/editor/ui") : ui_dir_) + "/editor.ugui"; - std::error_code ec; - auto t = fs::last_write_time(path, ec); - if (ec) + const base::Optional t = fs::LastWriteTime(path); + if (!t) return; - int64_t m = (int64_t)t.time_since_epoch().count(); + int64_t m = *t; if (m != ui_mtime_ && ui_mtime_ != 0) MarkDirty(); } @@ -984,11 +986,11 @@ void Editor::UiHotReloadCheck(f32 dt) { // Event routing namespace { // Climb up to `max` ancestors, returning the first non-empty widget name. -std::string NamedAncestor(ugui::UIContext &ui, ugui::wid w, int max = 5) { +base::String NamedAncestor(ugui::UIContext &ui, ugui::wid w, int max = 5) { for (int i = 0; i < max && w.valid(); ++i) { if (ugui::WidgetNode *n = ui.world().Get(w)) { if (!n->name.empty()) - return std::string(n->name.c_str()); + return base::String(n->name.c_str()); } ugui::Hierarchy *h = ui.world().Get(w); w = h ? h->parent : ugui::wid{}; @@ -998,16 +1000,16 @@ std::string NamedAncestor(ugui::UIContext &ui, ugui::wid w, int max = 5) { } // namespace void Editor::OnUiClick(ugui::wid w, ugui::MouseButton btn) { - std::string name = NamedAncestor(ui_, w); + base::String name = NamedAncestor(ui_, w); if (name.empty()) return; RouteClick(name, btn); } -bool Editor::RouteClick(const std::string &name, ugui::MouseButton) { +bool Editor::RouteClick(const base::String &name, ugui::MouseButton) { auto starts = [&](const char *p) { return name.rfind(p, 0) == 0; }; auto tail_u = [&](const char *p) -> unsigned { - return (unsigned)std::strtoul(name.c_str() + std::strlen(p), nullptr, 10); + return (unsigned)::strtoul(name.c_str() + ::strlen(p), nullptr, 10); }; if (name == "menu_new") { @@ -1130,32 +1132,32 @@ bool Editor::RouteClick(const std::string &name, ugui::MouseButton) { return true; } if (name == "terrain_radius_minus") { - terrain_brush_radius_ = std::max(0.25f, terrain_brush_radius_ - 0.25f); + terrain_brush_radius_ = rx::Max(0.25f, terrain_brush_radius_ - 0.25f); MarkDirty(); return true; } if (name == "terrain_radius_plus") { - terrain_brush_radius_ = std::min(24.0f, terrain_brush_radius_ + 0.25f); + terrain_brush_radius_ = rx::Min(24.0f, terrain_brush_radius_ + 0.25f); MarkDirty(); return true; } if (name == "terrain_strength_minus") { - terrain_brush_strength_ = std::max(0.01f, terrain_brush_strength_ - 0.05f); + terrain_brush_strength_ = rx::Max(0.01f, terrain_brush_strength_ - 0.05f); MarkDirty(); return true; } if (name == "terrain_strength_plus") { - terrain_brush_strength_ = std::min(2.0f, terrain_brush_strength_ + 0.05f); + terrain_brush_strength_ = rx::Min(2.0f, terrain_brush_strength_ + 0.05f); MarkDirty(); return true; } if (name == "terrain_falloff_minus") { - terrain_brush_falloff_ = std::max(0.0f, terrain_brush_falloff_ - 0.25f); + terrain_brush_falloff_ = rx::Max(0.0f, terrain_brush_falloff_ - 0.25f); MarkDirty(); return true; } if (name == "terrain_falloff_plus") { - terrain_brush_falloff_ = std::min(8.0f, terrain_brush_falloff_ + 0.25f); + terrain_brush_falloff_ = rx::Min(8.0f, terrain_brush_falloff_ + 0.25f); MarkDirty(); return true; } @@ -1164,18 +1166,18 @@ bool Editor::RouteClick(const std::string &name, ugui::MouseButton) { terrain_.desc().layers.empty() ? 0 : static_cast(terrain_.desc().layers.size() - 1); - terrain_brush_layer_ = std::min(tail_u("terrain_layer_"), last_layer); + terrain_brush_layer_ = rx::Min(tail_u("terrain_layer_"), last_layer); terrain_brush_mode_ = terrain::TerrainBrushMode::kPaintLayer; MarkDirty(); return true; } if (name == "place_spacing_minus") { - placement_.spacing = std::max(0.25f, placement_.spacing - 0.25f); + placement_.spacing = rx::Max(0.25f, placement_.spacing - 0.25f); MarkDirty(); return true; } if (name == "place_spacing_plus") { - placement_.spacing = std::min(20.0f, placement_.spacing + 0.25f); + placement_.spacing = rx::Min(20.0f, placement_.spacing + 0.25f); MarkDirty(); return true; } @@ -1184,14 +1186,14 @@ bool Editor::RouteClick(const std::string &name, ugui::MouseButton) { ecs::Entity out; const edit::ComponentDesc *xf = edit::FindComponentByName("Transform"); const edit::ComponentDesc *nm = edit::FindComponentByName("Name"); - std::vector>>> + base::Vector>>> initial; initial.push_back({xf, {}}); initial.push_back( {nm, {{&nm->props[0], edit::PropValue::String("Empty")}}}); - undo_.Push(*world_, edit::MakeCreateEntity(std::move(initial), &out)); + undo_.Push(*world_, edit::MakeCreateEntity(base::move(initial), &out)); selection_.Set(out); doc_dirty_ = true; MarkDirty(); @@ -1209,7 +1211,7 @@ bool Editor::RouteClick(const std::string &name, ugui::MouseButton) { if (starts("hier_")) { // hier__ unsigned index = 0, gen = 0; - if (std::sscanf(name.c_str() + 5, "%u_%u", &index, &gen) == 2) { + if (::sscanf(name.c_str() + 5, "%u_%u", &index, &gen) == 2) { ecs::Entity e{index, gen}; if (world_->IsAlive(e)) { selection_.Set(e); @@ -1245,9 +1247,9 @@ bool Editor::RouteClick(const std::string &name, ugui::MouseButton) { return true; } // toggle bool field (field___0 on a bool) - if (starts("field_") && name.find("_tint_") == std::string::npos) { + if (starts("field_") && name.find("_tint_") == base::String::npos) { unsigned cid = 0, pi = 0, ax = 0; - if (std::sscanf(name.c_str() + 6, "%u_%u_%u", &cid, &pi, &ax) == 3) { + if (::sscanf(name.c_str() + 6, "%u_%u_%u", &cid, &pi, &ax) == 3) { const edit::ComponentDesc *c = edit::FindComponent(cid); ecs::Entity e = selection_.primary(); if (c && e && pi < c->prop_count && @@ -1294,11 +1296,11 @@ bool Editor::RouteClick(const std::string &name, ugui::MouseButton) { return false; } -void Editor::OnUiTextSubmit(const std::string &widget, - const std::string &value) { +void Editor::OnUiTextSubmit(const base::String &widget, + const base::String &value) { if (widget == "insp_name") { if (ecs::Entity e = selection_.primary()) { - SetName(e, value); // side table (ECS std::string is unsafe today) + SetName(e, value); // side table (ECS base::String is unsafe today) doc_dirty_ = true; MarkDirty(); } @@ -1328,13 +1330,14 @@ void Editor::OnBuildView(f32 dt, render::FrameView &view) { d.prev_transform = d.transform; uint32_t tint = terrain_visual ? 0u - : (tints_.count(e.index) ? tints_[e.index] : 0u); + : (tints_.contains(e.index) ? tints_[e.index] : 0u); if (!terrain_visual && e == primary) tint = 0xffa64d; // selection highlight d.tint = tint; - auto imported = imported_entities_.find(ImportedEntityKey(e)); - if (imported != imported_entities_.end()) { - const auto [model_index, instance_index] = imported->second; + const base::Pair *imported = + imported_entities_.find(ImportedEntityKey(e)); + if (imported) { + const auto [model_index, instance_index] = *imported; if (model_index < imported_models_.size() && instance_index < imported_models_[model_index].instances.size()) { ImportedModel &model = imported_models_[model_index]; @@ -1350,8 +1353,8 @@ void Editor::OnBuildView(f32 dt, render::FrameView &view) { view.bone_matrices.push_back(matrix); instance.morph_weights.resize(record->mesh.morph_targets.size()); - std::fill(instance.morph_weights.begin(), - instance.morph_weights.end(), 0.0f); + base::Fill(instance.morph_weights.begin(), + instance.morph_weights.end(), 0.0f); anim::ApplyBodyMorphWeights(record->mesh, skin.morphs, &instance.morph_weights); d.morph_offset = static_cast(view.morph_weights.size()); @@ -1390,7 +1393,7 @@ void Editor::OnBuildView(f32 dt, render::FrameView &view) { gizmo_mode_ == GizmoMode::kTranslate) { scene::Transform wt = edit::WorldTransform(*world_, primary); Vec3 o{wt.position[0], wt.position[1], wt.position[2]}; - f32 len = std::max(0.5f, Length(o - camera_.position()) * 0.18f); + f32 len = rx::Max(0.5f, Length(o - camera_.position()) * 0.18f); gizmo_lines_.push_back({o, {o.x + len, o.y, o.z}, 0xe0655fff}); // X red gizmo_lines_.push_back({o, {o.x, o.y + len, o.z}, 0x7fb96aff}); // Y green gizmo_lines_.push_back({o, {o.x, o.y, o.z + len}, 0x5a8deeff}); // Z blue @@ -1430,12 +1433,12 @@ void Editor::OnBuildView(f32 dt, render::FrameView &view) { // scrub helpers implemented here (need ugui hovered name + reflect) bool Editor::TryStartScrub(f32 mx) { - std::string name = NamedAncestor(ui_, ui_.input().hovered_widget()); + base::String name = NamedAncestor(ui_, ui_.input().hovered_widget()); if (name.rfind("field_", 0) != 0) return false; unsigned cid = 0, pi = 0, ax = 0; - if (name.find("_tint_") != std::string::npos) { - if (std::sscanf(name.c_str() + std::strlen("field_tint_"), "%u_%u", &pi, + if (name.find("_tint_") != base::String::npos) { + if (::sscanf(name.c_str() + ::strlen("field_tint_"), "%u_%u", &pi, &ax) == 2) { // tint scrub: live edit of tints_ (not undo-tracked) scrub_ = {}; @@ -1444,7 +1447,7 @@ bool Editor::TryStartScrub(f32 mx) { scrub_.comp = nullptr; // signals tint scrub_.axis = (int)ax; scrub_.start_mouse = mx; - uint32_t t = tints_.count(scrub_.entity.index) + uint32_t t = tints_.contains(scrub_.entity.index) ? tints_[scrub_.entity.index] : 0xffffff; scrub_.base_value = (f32)((t >> (16 - ax * 8)) & 0xFF); @@ -1453,7 +1456,7 @@ bool Editor::TryStartScrub(f32 mx) { } return false; } - if (std::sscanf(name.c_str() + 6, "%u_%u_%u", &cid, &pi, &ax) != 3) + if (::sscanf(name.c_str() + 6, "%u_%u_%u", &cid, &pi, &ax) != 3) return false; const edit::ComponentDesc *c = edit::FindComponent(cid); ecs::Entity e = selection_.primary(); @@ -1495,8 +1498,8 @@ void Editor::UpdateScrub() { if (!scrub_.comp) { // tint live edit ecs::Entity e = scrub_.entity; - int iv = (int)std::clamp(nv, 0.0f, 255.0f); - uint32_t t = tints_.count(e.index) ? tints_[e.index] : 0xffffff; + int iv = (int)rx::Clamp(nv, 0.0f, 255.0f); + uint32_t t = tints_.contains(e.index) ? tints_[e.index] : 0xffffff; int shift = 16 - scrub_.axis * 8; t = (t & ~(0xFFu << shift)) | ((uint32_t)iv << shift); tints_[e.index] = t; @@ -1517,7 +1520,7 @@ void Editor::UpdateScrub() { edit::PropValue::Quat(q.x, q.y, q.z, q.w))); } else { if (pd.min != pd.max) - nv = std::clamp(nv, pd.min, pd.max); + nv = rx::Clamp(nv, pd.min, pd.max); edit::PropValue out = v; out.f[scrub_.axis] = nv; undo_.Push(*world_, edit::MakeSetProp(*world_, scrub_.entity, *scrub_.comp, diff --git a/apps/editor/fly_camera.cc b/apps/editor/fly_camera.cc index bc7523dd..19e6a611 100644 --- a/apps/editor/fly_camera.cc +++ b/apps/editor/fly_camera.cc @@ -1,12 +1,12 @@ +#include "core/scalar.h" #include "fly_camera.h" -#include -#include +#include namespace rx { Vec3 FlyCamera::forward() const { - return {std::cos(pitch_) * std::sin(yaw_), std::sin(pitch_), -std::cos(pitch_) * std::cos(yaw_)}; + return {::cos(pitch_) * ::sin(yaw_), ::sin(pitch_), -::cos(pitch_) * ::cos(yaw_)}; } void FlyCamera::Update(const InputState& input, const ActionState& actions, bool allow_mouse, @@ -17,8 +17,8 @@ void FlyCamera::Update(const InputState& input, const ActionState& actions, bool yaw_ += input.mouse_dx * sensitivity; pitch_ -= input.mouse_dy * sensitivity; if (input.wheel != 0) { - speed *= std::pow(1.2f, input.wheel); - speed = std::clamp(speed, 0.1f, 200.0f); + speed *= ::pow(1.2f, input.wheel); + speed = rx::Clamp(speed, 0.1f, 200.0f); } } // Right-stick look (rate based), no button needed. @@ -26,7 +26,7 @@ void FlyCamera::Update(const InputState& input, const ActionState& actions, bool yaw_ += actions.axis(Axis::kLookX) * pad_sensitivity * dt; pitch_ -= actions.axis(Axis::kLookY) * pad_sensitivity * dt * (invert_y ? -1.0f : 1.0f); } - pitch_ = std::clamp(pitch_, -1.55f, 1.55f); + pitch_ = rx::Clamp(pitch_, -1.55f, 1.55f); if (!allow_keyboard) return; @@ -39,7 +39,7 @@ void FlyCamera::Update(const InputState& input, const ActionState& actions, bool if (actions.down(Action::kCamUp) || actions.down(Action::kJump)) move += Vec3{0, 1, 0}; if (actions.down(Action::kCamDown) || actions.down(Action::kSneak)) move += Vec3{0, -1, 0}; - f32 length = std::sqrt(Dot(move, move)); + f32 length = ::sqrt(Dot(move, move)); if (length > 0) { f32 boost = actions.down(Action::kSprint) ? 4.0f : 1.0f; position_ += move * (speed * boost * dt / length); diff --git a/apps/editor/main.cc b/apps/editor/main.cc index d6b4954e..c4fcc329 100644 --- a/apps/editor/main.cc +++ b/apps/editor/main.cc @@ -1,17 +1,17 @@ // rx editor entry point. Boots app::Host with the editor Application. An // optional .rxscene, .gltf/.glb, or .blend path may be passed to open on start; // otherwise a small built-in default scene keeps the editor from being empty. -#include #include #include "app/host.h" +#include "base/strings/xstring.h" #include "editor_app.h" int main(int argc, char** argv) { - std::string open_path; + base::String open_path; for (int i = 1; i < argc; ++i) { - std::string a = argv[i]; + base::String a = argv[i]; if (!a.empty() && a[0] != '-') open_path = a; } diff --git a/cmake/rxConfig.cmake.in b/cmake/rxConfig.cmake.in index e8d6ea7d..74771160 100644 --- a/cmake/rxConfig.cmake.in +++ b/cmake/rxConfig.cmake.in @@ -38,7 +38,7 @@ if(NOT TARGET equilibrium::base) IMPORTED_LOCATION "${_rx_deps_lib}/libeq_base.a" INTERFACE_INCLUDE_DIRECTORIES "${_rx_deps_inc}/equilibrium;${_rx_deps_inc}/equilibrium/base" - INTERFACE_COMPILE_DEFINITIONS "OS_LINUX;OS_POSIX;BASE_USE_STD_ATOMIC=1" + INTERFACE_COMPILE_DEFINITIONS "OS_LINUX;OS_POSIX;BASE_NO_STD_FORMAT" INTERFACE_LINK_LIBRARIES "Threads::Threads") endif() @@ -56,12 +56,15 @@ if(@RX_INSTALL_UI@ AND NOT TARGET ultragui) add_library(yogacore STATIC IMPORTED) set_target_properties(yogacore PROPERTIES IMPORTED_LOCATION "${_rx_deps_lib}/libyogacore.a") + # Built with ULTRAGUI_USE_BASE (engine/ui/CMakeLists.txt): its headers pick + # base containers only when the define is seen, so it travels with them. add_library(ultragui STATIC IMPORTED) set_target_properties(ultragui PROPERTIES IMPORTED_LOCATION "${_rx_deps_lib}/libultragui.a" INTERFACE_INCLUDE_DIRECTORIES "${_rx_deps_inc}" + INTERFACE_COMPILE_DEFINITIONS "ULTRAGUI_USE_BASE" INTERFACE_LINK_LIBRARIES - "yogacore;PkgConfig::FREETYPE;PkgConfig::HARFBUZZ") + "equilibrium::base;yogacore;PkgConfig::FREETYPE;PkgConfig::HARFBUZZ") endif() # Vulkan headers / volk / VMA (PUBLIC via rhi/vulkan_interop.h) @@ -85,7 +88,14 @@ if(NOT TARGET GPUOpen::VulkanMemoryAllocator) endif() # PRIVATE static archives (link-only for the consumer) -_rx_import_static(kinema::kinema libkinema.a) +# rx builds kinema in its equilibrium configuration (KINEMA_USE_BASE): its +# containers are base's, so the archive links against libeq_base. +if(NOT TARGET kinema::kinema) + _rx_import_static(kinema::kinema libkinema.a) + set_target_properties(kinema::kinema PROPERTIES + INTERFACE_COMPILE_DEFINITIONS KINEMA_USE_BASE + INTERFACE_LINK_LIBRARIES equilibrium::base) +endif() _rx_import_static(rx::stb_impl librx_stb_impl.a) if(@RX_INSTALL_JOLT@) _rx_import_static(Jolt libJolt.a) diff --git a/engine/anim/anim_graph.cc b/engine/anim/anim_graph.cc index 15b90513..4b1c25f1 100644 --- a/engine/anim/anim_graph.cc +++ b/engine/anim/anim_graph.cc @@ -1,9 +1,13 @@ #include "anim/anim_graph.h" -#include +#include #include "anim/anim_internal.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/string_ref.h" #include "core/math.h" +#include "core/scalar.h" namespace rx::anim { @@ -96,7 +100,7 @@ kinema::OwnedClip BakeGait(const kinema::Skeleton& sk, u32 frames, f32 rate, con for (u32 fr = 0; fr < frames; ++fr) { const f32 phase = static_cast(fr) / static_cast(frames - 1); // 0..1, wraps const f32 theta = phase * kTwoPi; - const f32 leg = std::sin(theta); + const f32 leg = ::sin(theta); for (u32 bone = 0; bone < bones; ++bone) { kinema::Quat rot = sk.bind_rotation[bone]; @@ -104,20 +108,20 @@ kinema::OwnedClip BakeGait(const kinema::Skeleton& sk, u32 frames, f32 rate, con if (is(j.l_thigh)) rot = Compose(rot, x, amp.thigh * leg); else if (is(j.r_thigh)) rot = Compose(rot, x, -amp.thigh * leg); else if (is(j.l_calf)) - rot = Compose(rot, x, -amp.knee * Clamp01(-std::sin(theta - 0.6f))); + rot = Compose(rot, x, -amp.knee * Clamp01(-::sin(theta - 0.6f))); else if (is(j.r_calf)) - rot = Compose(rot, x, -amp.knee * Clamp01(-std::sin(theta + kPi - 0.6f))); + rot = Compose(rot, x, -amp.knee * Clamp01(-::sin(theta + kPi - 0.6f))); else if (is(j.l_arm)) rot = Compose(rot, x, -amp.arm * leg); else if (is(j.r_arm)) rot = Compose(rot, x, amp.arm * leg); - else if (is(j.spine1)) rot = Compose(rot, z, amp.spine * std::cos(theta)); + else if (is(j.spine1)) rot = Compose(rot, z, amp.spine * ::cos(theta)); else if (is(j.spine)) rot = Compose(rot, x, amp.lean); // constant forward lean b.SetSample(fr, bone, sk.bind_translation[bone], rot, sk.bind_scale[bone]); } if (curve >= 0) { // Two intensity pulses per cycle, peaking at the footfalls. - f32 pL = std::exp(-std::pow((phase - 0.25f) * 6.0f, 2.0f)); - f32 pR = std::exp(-std::pow((phase - 0.75f) * 6.0f, 2.0f)); - b.SetCurveSample(fr, static_cast(curve), std::max(pL, pR)); + f32 pL = ::exp(-::pow((phase - 0.25f) * 6.0f, 2.0f)); + f32 pR = ::exp(-::pow((phase - 0.75f) * 6.0f, 2.0f)); + b.SetCurveSample(fr, static_cast(curve), rx::Max(pL, pR)); } } return kinema::OwnedClip(b.Build()); @@ -126,7 +130,7 @@ kinema::OwnedClip BakeGait(const kinema::Skeleton& sk, u32 frames, f32 rate, con } // namespace AnimGraph BuildBipedLocomotionGraph(const asset::Skeleton& skeleton) { - auto state = std::make_shared(); + auto state = base::MakeUnique(); state->skeleton = detail::BuildKinemaSkeleton(skeleton); const kinema::Skeleton& sk = state->skeleton; @@ -139,7 +143,7 @@ AnimGraph BuildBipedLocomotionGraph(const asset::Skeleton& skeleton) { // a 1 s walk cycle and a punchier 0.6 s run cycle. Walk and run carry matched // footfall markers so the sync group and blend space stay foot-aligned. auto push = [&](kinema::OwnedClip c) { - state->clips.push_back(std::make_unique(std::move(c))); + state->clips.push_back(base::MakeUnique(base::move(c))); return static_cast(state->clips.size()) - 1; }; state->idle_clip = push(BakeGait(sk, 61, 30.0f, GaitAmp{0.03f, 0.04f, 0.05f, 0.05f, 0.0f}, @@ -153,7 +157,7 @@ AnimGraph BuildBipedLocomotionGraph(const asset::Skeleton& skeleton) { // 1D walk<->run blend space keyed on the "speed" parameter (idle is a separate // state, so the space is exactly the two synced gaits). - state->locomotion_space = std::make_unique(kinema::BlendSpace::Dim::k1D); + state->locomotion_space = base::MakeUnique(kinema::BlendSpace::Dim::k1D); state->locomotion_space->Add(state->clip(state->walk_clip), state->walk_speed); state->locomotion_space->Add(state->clip(state->run_clip), state->run_speed); state->locomotion_space->Finalize(); @@ -169,7 +173,7 @@ AnimGraph BuildBipedLocomotionGraph(const asset::Skeleton& skeleton) { loco.kind = kinema::PoseOp::Kind::kBlendSpace; loco.dst = 0; loco.a = 1; // scratch register - loco.space = state->locomotion_space.get(); + loco.space = state->locomotion_space.Get_UseOnlyIfYouKnowWhatYouareDoing(); loco.coord_param = static_cast(state->speed_param); loco.time_param = static_cast(state->phase_param); const f32 walk_dur = state->clip(state->walk_clip)->duration(); @@ -189,12 +193,19 @@ AnimGraph BuildBipedLocomotionGraph(const asset::Skeleton& skeleton) { smb.AddTransition(state->loco_state, state->idle_state, stop, &halting, 1); state->machine = smb.Build(); - return AnimGraph(std::move(state)); + return AnimGraph(base::move(state)); } +// Out of line: GraphState is only complete in this translation unit. +AnimGraph::AnimGraph() = default; +AnimGraph::AnimGraph(base::UniquePointer state) : state_(base::move(state)) {} +AnimGraph::AnimGraph(AnimGraph&&) noexcept = default; +AnimGraph& AnimGraph::operator=(AnimGraph&&) noexcept = default; +AnimGraph::~AnimGraph() = default; + u32 AnimGraph::bone_count() const { return state_ ? state_->skeleton.count() : 0; } -int AnimGraph::ParamIndex(std::string_view name) const { +int AnimGraph::ParamIndex(base::StringRef name) const { if (!state_) return -1; for (size_t i = 0; i < state_->param_names.size(); ++i) { if (state_->param_names[i] == name) return static_cast(i); diff --git a/engine/anim/anim_graph.h b/engine/anim/anim_graph.h index 521e8fbb..16c9d984 100644 --- a/engine/anim/anim_graph.h +++ b/engine/anim/anim_graph.h @@ -1,10 +1,10 @@ #ifndef RX_ANIM_ANIM_GRAPH_H_ #define RX_ANIM_ANIM_GRAPH_H_ -#include -#include - #include "asset/skeleton.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/string_ref.h" #include "core/types.h" namespace rx::anim { @@ -16,31 +16,34 @@ struct GraphState; // The shared, immutable animation archetype: a skeleton binding plus a compiled // state graph (states, transitions, blend spaces and the clips they sample), // built once and shared by every character that plays it. Carries no per-frame -// state - that lives in RigPlayer. Copies are cheap (a shared_ptr to the -// compiled payload), so a whole crowd of RigPlayers shares one AnimGraph. +// state - that lives in RigPlayer. Move-only: a whole crowd of RigPlayers binds +// to one AnimGraph by reference, and it must outlive them. // // The graph is authored kinema-side (kinema stays a private detail of // engine/anim, so it never appears in this header); BuildBipedLocomotionGraph is // the first such archetype factory. class AnimGraph { public: - AnimGraph() = default; - explicit AnimGraph(std::shared_ptr state) : state_(std::move(state)) {} + AnimGraph(); + explicit AnimGraph(base::UniquePointer state); + AnimGraph(AnimGraph&&) noexcept; + AnimGraph& operator=(AnimGraph&&) noexcept; + ~AnimGraph(); - bool valid() const { return static_cast(state_); } + bool valid() const { return !state_.empty(); } u32 bone_count() const; // Parameter id for a named live input (e.g. "speed"), or -1 if the archetype // has no such parameter. RigPlayer::SetParam takes this id. - int ParamIndex(std::string_view name) const; + int ParamIndex(base::StringRef name) const; // Internal handle to the compiled payload. Returns a detail type, so it is // only usable in a translation unit that includes anim_internal.h (RigPlayer / // FootPlacement); ordinary callers cannot reach kinema through it. - const detail::GraphState* state() const { return state_.get(); } + const detail::GraphState* state() const { return state_ ? &*state_ : nullptr; } private: - std::shared_ptr state_; + base::UniquePointer state_; }; // Author an idle / walk / run locomotion archetype for a biped built on the diff --git a/engine/anim/anim_internal.h b/engine/anim/anim_internal.h index ce55fdc6..469139ab 100644 --- a/engine/anim/anim_internal.h +++ b/engine/anim/anim_internal.h @@ -8,15 +8,15 @@ // public rx header includes . cmake/install.cmake excludes // *_internal.h from the package for this reason. -#include -#include -#include -#include +#include #include #include "anim/pose.h" #include "asset/skeleton.h" +#include "base/containers/vector.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" #include "core/math.h" namespace rx::anim::detail { @@ -49,10 +49,10 @@ kinema::Skeleton BuildKinemaSkeleton(const asset::Skeleton& skeleton); // AnimGraph and shared by every character of the archetype. struct GraphState { kinema::Skeleton skeleton; - std::vector> clips; // stable Clip addresses - std::unique_ptr locomotion_space; + base::Vector> clips; // stable Clip addresses + base::UniquePointer locomotion_space; kinema::StateMachine machine; - std::vector param_names; // index == parameter id + base::Vector param_names; // index == parameter id // Locomotion archetype roles (indices into `clips`, -1 if absent). int idle_clip = -1, walk_clip = -1, run_clip = -1; diff --git a/engine/anim/body_dynamics.cc b/engine/anim/body_dynamics.cc index 6c6f9d51..4e17517b 100644 --- a/engine/anim/body_dynamics.cc +++ b/engine/anim/body_dynamics.cc @@ -1,10 +1,11 @@ #include "anim/body_dynamics.h" -#include -#include -#include +#include #include "asset/asset_id.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/scalar.h" namespace rx::anim { namespace { @@ -23,9 +24,9 @@ Vec3 ClampMagnitude(Vec3 value, f32 maximum) { void ClampAxes(Vec3 *value, Vec3 *velocity, const Vec3 &limit) { auto clamp = [](f32 *x, f32 *v, f32 extent) { - extent = std::max(extent, 0.0f); + extent = rx::Max(extent, 0.0f); const f32 before = *x; - *x = std::clamp(*x, -extent, extent); + *x = rx::Clamp(*x, -extent, extent); if (*x != before && ((*x > 0 && *v > 0) || (*x < 0 && *v < 0))) *v = 0; }; @@ -39,33 +40,33 @@ void ClampAxes(Vec3 *value, Vec3 *velocity, const Vec3 &limit) { // and over-damped authored material responses. void Spring(f32 *x, f32 *velocity, f32 force, f32 frequency, f32 damping, f32 dt) { - const f32 w = 2.0f * kPi * std::max(frequency, 0.01f); + const f32 w = 2.0f * kPi * rx::Max(frequency, 0.01f); const f32 target = force / (w * w); const f32 y = *x - target; const f32 v = *velocity; - const f32 z = std::max(damping, 0.0f); + const f32 z = rx::Max(damping, 0.0f); if (z < 1.0f - 1e-4f) { - const f32 wd = w * std::sqrt(1.0f - z * z); - const f32 e = std::exp(-z * w * dt); - const f32 c = std::cos(wd * dt); - const f32 s = std::sin(wd * dt); + const f32 wd = w * ::sqrt(1.0f - z * z); + const f32 e = ::exp(-z * w * dt); + const f32 c = ::cos(wd * dt); + const f32 s = ::sin(wd * dt); const f32 a = (v + z * w * y) / wd; *x = target + e * (y * c + a * s); *velocity = e * (v * c - ((z * w * v + w * w * y) / wd) * s); } else if (z <= 1.0f + 1e-4f) { - const f32 e = std::exp(-w * dt); + const f32 e = ::exp(-w * dt); const f32 a = v + w * y; *x = target + (y + a * dt) * e; *velocity = (v - w * a * dt) * e; } else { - const f32 root = std::sqrt(z * z - 1.0f); + const f32 root = ::sqrt(z * z - 1.0f); const f32 r1 = -w * (z - root); const f32 r2 = -w * (z + root); const f32 c1 = (v - r2 * y) / (r1 - r2); const f32 c2 = y - c1; - const f32 e1 = std::exp(r1 * dt); - const f32 e2 = std::exp(r2 * dt); + const f32 e1 = ::exp(r1 * dt); + const f32 e2 = ::exp(r2 * dt); *x = target + c1 * e1 + c2 * e2; *velocity = r1 * c1 * e1 + r2 * c2 * e2; } @@ -79,7 +80,7 @@ void Spring(Vec3 *x, Vec3 *velocity, const Vec3 &force, f32 frequency, } Vec3 ExpSmooth(const Vec3 &previous, const Vec3 &value, f32 halflife, f32 dt) { - const f32 t = 1.0f - std::exp(-0.69314718f * dt / std::max(halflife, 1e-4f)); + const f32 t = 1.0f - ::exp(-0.69314718f * dt / rx::Max(halflife, 1e-4f)); return Lerp(previous, value, t); } @@ -90,11 +91,11 @@ Vec3 QuaternionVelocity(Quat previous, Quat current, f32 dt) { if (delta.w < 0) delta = {-delta.x, -delta.y, -delta.z, -delta.w}; const f32 sin_half = - std::sqrt(delta.x * delta.x + delta.y * delta.y + delta.z * delta.z); + ::sqrt(delta.x * delta.x + delta.y * delta.y + delta.z * delta.z); if (sin_half < 1e-6f || dt <= 0) return {}; const f32 angle = - 2.0f * std::atan2(sin_half, std::clamp(delta.w, -1.0f, 1.0f)); + 2.0f * ::atan2(sin_half, rx::Clamp(delta.w, -1.0f, 1.0f)); return Vec3{delta.x, delta.y, delta.z} * (angle / (sin_half * dt)); } @@ -129,7 +130,7 @@ void AppendMorphSignals(const BodyRegionConfig &config, BodyMorphWeight weight; weight.target = asset::MakeAssetId(binding.target).hash; weight.weight = - std::clamp(binding.bias + binding.gain * Signal(sample, binding.signal), + rx::Clamp(binding.bias + binding.gain * Signal(sample, binding.signal), binding.min_weight, binding.max_weight); if (weight.target == 0 || weight.weight == 0) continue; @@ -149,13 +150,13 @@ void AppendMorphSignals(const BodyRegionConfig &config, } // namespace BodyRegionConfig MakeBodyRegionPreset(BodyRegionKind kind, - std::string_view name, - std::string_view driver_bone, - std::string_view driven_bone) { + base::StringRef name, + base::StringRef driver_bone, + base::StringRef driven_bone) { BodyRegionConfig out; - out.name = std::string(name); - out.driver_bone = std::string(driver_bone); - out.driven_bone = std::string(driven_bone); + out.name = base::String(name); + out.driver_bone = base::String(driver_bone); + out.driven_bone = base::String(driven_bone); switch (kind) { case BodyRegionKind::kChest: out.frequency_hz = 3.1f; @@ -282,15 +283,15 @@ void BodyDynamics::Update(const asset::Skeleton &skeleton, states_.size() != regions_.size()) { Resolve(skeleton); } - const f32 model_units_per_metre = std::max( - std::isfinite(frame.model_units_per_metre) ? frame.model_units_per_metre + const f32 model_units_per_metre = rx::Max( + ::isfinite(frame.model_units_per_metre) ? frame.model_units_per_metre : 1.0f, 1e-6f); - if (std::fabs(model_units_per_metre - model_units_per_metre_) > 1e-5f) { + if (::fabs(model_units_per_metre - model_units_per_metre_) > 1e-5f) { Reset(); model_units_per_metre_ = model_units_per_metre; } - if (frame.teleport || !std::isfinite(dt) || dt <= 0 || dt > 0.25f) + if (frame.teleport || !::isfinite(dt) || dt <= 0 || dt > 0.25f) Reset(); ComputeModelMatrices(skeleton, *pose, &model_); @@ -380,13 +381,13 @@ void BodyDynamics::Update(const asset::Skeleton &skeleton, ClampAxes(&state.rotation, &state.angular_velocity, config.max_rotation); const f32 impact_from_acceleration = - std::max(Length(acceleration_local) - config.impact_threshold, 0.0f) / - std::max(max_acceleration_ - config.impact_threshold, 1.0f); + rx::Max(Length(acceleration_local) - config.impact_threshold, 0.0f) / + rx::Max(max_acceleration_ - config.impact_threshold, 1.0f); const f32 impact_from_impulse = Length(frame.linear_impulse) / 2.0f; - state.impact = std::max( + state.impact = rx::Max( state.impact * - std::exp(-0.69314718f * dt / std::max(config.impact_decay, 1e-4f)), - std::clamp(std::max(impact_from_acceleration, impact_from_impulse), + ::exp(-0.69314718f * dt / rx::Max(config.impact_decay, 1e-4f)), + rx::Clamp(rx::Max(impact_from_acceleration, impact_from_impulse), 0.0f, 1.0f)); // Translate from the driver's local axes into model space, then into the @@ -434,19 +435,19 @@ BodyRegionSample BodyDynamics::sample(u32 index) const { const Vec3 axis = Normalize(config.deformation_axis); const f32 axial = Dot(state.translation, axis); const f32 normalizer = - std::max(Length(Multiply(axis, config.max_translation)), 1e-5f); - out.stretch = std::clamp(axial / normalizer, 0.0f, 1.0f); - out.compression = std::clamp(-axial / normalizer, 0.0f, 1.0f); + rx::Max(Length(Multiply(axis, config.max_translation)), 1e-5f); + out.stretch = rx::Clamp(axial / normalizer, 0.0f, 1.0f); + out.compression = rx::Clamp(-axial / normalizer, 0.0f, 1.0f); const Vec3 lateral = state.translation - axis * axial; - out.shear = std::clamp(Length(lateral) / - std::max(Length(config.max_translation - + out.shear = rx::Clamp(Length(lateral) / + rx::Max(Length(config.max_translation - Multiply(Multiply(axis, axis), config.max_translation)), 1e-5f), 0.0f, 1.0f); - out.speed = std::clamp( + out.speed = rx::Clamp( Length(state.velocity) / - std::max(Length(config.max_translation) * config.frequency_hz, 1e-5f), + rx::Max(Length(config.max_translation) * config.frequency_hz, 1e-5f), 0.0f, 1.0f); return out; } @@ -462,7 +463,7 @@ void ApplyBodyMorphWeights(const asset::Mesh &mesh, const i32 target = mesh.FindMorphTarget(body_weight.target); if (target < 0) continue; - (*dense)[target] = std::clamp((*dense)[target] + body_weight.weight, + (*dense)[target] = rx::Clamp((*dense)[target] + body_weight.weight, min_weight, max_weight); } } diff --git a/engine/anim/body_dynamics.h b/engine/anim/body_dynamics.h index 0e0fa520..e4ae0f72 100644 --- a/engine/anim/body_dynamics.h +++ b/engine/anim/body_dynamics.h @@ -1,14 +1,14 @@ #ifndef RX_ANIM_BODY_DYNAMICS_H_ #define RX_ANIM_BODY_DYNAMICS_H_ -#include -#include #include #include "anim/pose.h" #include "asset/mesh.h" #include "asset/skeleton.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/math.h" #include "core/types.h" @@ -26,7 +26,7 @@ enum class BodyDeformationSignal : u8 { }; struct BodyMorphBinding { - std::string target; + base::String target; BodyDeformationSignal signal = BodyDeformationSignal::kStretch; f32 gain = 1; f32 bias = 0; @@ -39,9 +39,9 @@ struct BodyMorphBinding { // rotation. Usually the driven bone is a dedicated tissue/helper bone; it can // equal the driver for simpler rigs. All vectors use the driver's local frame. struct BodyRegionConfig { - std::string name; - std::string driver_bone; - std::string driven_bone; + base::String name; + base::String driver_bone; + base::String driven_bone; // Physical response. Frequency controls firmness and damping_ratio controls // decay (1 = critical, <1 = a decaying jiggle). These values describe a @@ -88,8 +88,8 @@ enum class BodyRegionKind : u8 { }; RX_ANIM_EXPORT BodyRegionConfig MakeBodyRegionPreset( - BodyRegionKind kind, std::string_view name, std::string_view driver_bone, - std::string_view driven_bone); + BodyRegionKind kind, base::StringRef name, base::StringRef driver_bone, + base::StringRef driven_bone); // Motion not visible in the skeleton pose (for example, entity locomotion) // must be supplied here in skeleton/model axes. Animation-induced acceleration diff --git a/engine/anim/expression.cc b/engine/anim/expression.cc index cb6a3a73..e5512404 100644 --- a/engine/anim/expression.cc +++ b/engine/anim/expression.cc @@ -1,9 +1,14 @@ #include "anim/expression.h" -#include -#include +#include +#include #include "asset/asset_id.h" +#include "base/algorithm.h" +#include "base/memory/move.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/scalar.h" namespace rx::anim { namespace { @@ -12,10 +17,10 @@ namespace { // dt). `halflife` is the time the remaining offset takes to roughly halve; no // overshoot from rest and only vanishing overshoot from a carried velocity. void Damp(f32* x, f32* v, f32 goal, f32 halflife, f32 dt) { - const f32 y = (2.0f * 0.69314718f) / std::max(halflife, 1e-4f); + const f32 y = (2.0f * 0.69314718f) / rx::Max(halflife, 1e-4f); const f32 j0 = *x - goal; const f32 j1 = *v + j0 * y; - const f32 e = std::exp(-y * dt); + const f32 e = ::exp(-y * dt); *x = goal + (j0 + j1 * dt) * e; *v = (*v - j1 * y * dt) * e; } @@ -32,7 +37,7 @@ f32 ToUnit(u64 value) { return static_cast(value >> 40) * (1.0f / 16777216. // Stateless value noise in [-1, 1]: random knots at integer positions of `s`, // smoothstep between them - smooth drift, never jitter. f32 SmoothNoise(u64 seed, u32 lane, f32 s) { - const f32 floor = std::floor(s); + const f32 floor = ::floor(s); const i64 knot = static_cast(floor); auto knot_value = [&](i64 k) { u64 state = seed ^ (static_cast(lane) * 0xD6E8FEB86659FD93ull) ^ @@ -46,7 +51,7 @@ f32 SmoothNoise(u64 seed, u32 lane, f32 s) { } f32 SmoothStep01(f32 x) { - x = std::clamp(x, 0.0f, 1.0f); + x = rx::Clamp(x, 0.0f, 1.0f); return x * x * (3.0f - 2.0f * x); } @@ -55,8 +60,8 @@ f32 SmoothStep01(f32 x) { constexpr f32 kDefaultTransition = 0.4f; // Life-layer channel names: blinks, then the brow micro-motion lanes. -constexpr std::string_view kBlinkTargets[] = {"eyeBlinkLeft", "eyeBlinkRight"}; -constexpr std::string_view kMicroTargets[] = {"browInnerUp", "browOuterUpLeft", +const base::StringRef kBlinkTargets[] = {"eyeBlinkLeft", "eyeBlinkRight"}; +const base::StringRef kMicroTargets[] = {"browInnerUp", "browOuterUpLeft", "browOuterUpRight"}; } // namespace @@ -70,9 +75,9 @@ ExpressionController::ExpressionController() { {"cheek", 0.10f, 0.03f}, {"viseme", 0.12f, 0.04f}, {"mouth", 0.12f, 0.05f}, {"jaw", 0.14f, 0.06f}, {"", 0.10f, 0.02f}, }; - SetRegions(defaults, static_cast(std::size(defaults))); - for (std::string_view name : kBlinkTargets) channels_[EnsureChannel(name)].blink = true; - for (u32 lane = 0; lane < std::size(kMicroTargets); ++lane) { + SetRegions(defaults, static_cast((sizeof(defaults) / sizeof(defaults[0])))); + for (base::StringRef name : kBlinkTargets) channels_[EnsureChannel(name)].blink = true; + for (u32 lane = 0; lane < (sizeof(kMicroTargets) / sizeof(kMicroTargets[0])); ++lane) { channels_[EnsureChannel(kMicroTargets[lane])].micro = static_cast(lane); } set_seed(0); @@ -83,13 +88,13 @@ void ExpressionController::SetRegions(const Region* regions, u32 count) { for (u32 i = 0; i < count; ++i) regions_.push_back(regions[i]); } -u32 ExpressionController::EnsureChannel(std::string_view name) { +u32 ExpressionController::EnsureChannel(base::StringRef name) { const u64 target = asset::MakeAssetId(name).hash; for (u32 i = 0; i < channels_.size(); ++i) { if (channels_[i].target == target) return i; } Channel channel; - channel.name = std::string(name); + channel.name = base::String(name); channel.target = target; // First matching prefix wins; an empty prefix matches everything. for (const Region& region : regions_) { @@ -98,11 +103,11 @@ u32 ExpressionController::EnsureChannel(std::string_view name) { channel.base_delay = region.delay; break; } - channels_.push_back(std::move(channel)); + channels_.push_back(base::move(channel)); return static_cast(channels_.size() - 1); } -void ExpressionController::AddPose(std::string_view name, const PoseEntry* entries, u32 count) { +void ExpressionController::AddPose(base::StringRef name, const PoseEntry* entries, u32 count) { Pose pose; pose.name_hash = asset::MakeAssetId(name).hash; for (u32 i = 0; i < count; ++i) { @@ -111,10 +116,10 @@ void ExpressionController::AddPose(std::string_view name, const PoseEntry* entri } for (Pose& existing : poses_) { if (existing.name_hash != pose.name_hash) continue; - existing = std::move(pose); + existing = base::move(pose); return; } - poses_.push_back(std::move(pose)); + poses_.push_back(base::move(pose)); } void ExpressionController::AddDefaultPoses() { @@ -158,7 +163,7 @@ void ExpressionController::AddDefaultPoses() { {"cheekSquintLeft", 0.3f}}); } -bool ExpressionController::SetExpression(std::string_view name, f32 transition_time) { +bool ExpressionController::SetExpression(base::StringRef name, f32 transition_time) { return SetExpression(asset::MakeAssetId(name).hash, transition_time); } @@ -172,7 +177,7 @@ bool ExpressionController::SetExpression(u64 pose_hash, f32 transition_time) { const f32 scale = transition_time > 0 ? transition_time / kDefaultTransition : 1.0f; scratch_.resize(channels_.size()); - std::fill(scratch_.begin(), scratch_.end(), 0.0f); + base::Fill(scratch_.begin(), scratch_.end(), 0.0f); for (u32 i = 0; i < pose->channel.size(); ++i) scratch_[pose->channel[i]] = pose->weight[i]; for (u32 i = 0; i < channels_.size(); ++i) { Channel& channel = channels_[i]; @@ -249,7 +254,7 @@ void ExpressionController::UpdateBlink(f32 dt) { } void ExpressionController::Update(f32 dt) { - dt = std::max(dt, 0.0f); + dt = rx::Max(dt, 0.0f); life_time_ += dt; if (life_.enabled) { UpdateBlink(dt); @@ -268,13 +273,13 @@ void ExpressionController::Update(f32 dt) { f32 out = channel.value; if (life_.enabled && channel.micro >= 0) { // Micro-motion fades as the expression takes the channel over. - const f32 headroom = 1.0f - std::min(std::abs(channel.value), 1.0f); + const f32 headroom = 1.0f - rx::Min(::abs(channel.value), 1.0f); out += life_.micro_amplitude * headroom * SmoothNoise(seed_, static_cast(channel.micro), life_time_ * life_.micro_hz); } // max() lets a pose that already holds the eyes closed absorb the blink. - if (life_.enabled && channel.blink) out = std::max(out, blink_env_); - channel.out = std::clamp(out, 0.0f, 1.0f); + if (life_.enabled && channel.blink) out = rx::Max(out, blink_env_); + channel.out = rx::Clamp(out, 0.0f, 1.0f); } } diff --git a/engine/anim/expression.h b/engine/anim/expression.h index 797e2562..0efe6bd2 100644 --- a/engine/anim/expression.h +++ b/engine/anim/expression.h @@ -2,11 +2,11 @@ #define RX_ANIM_EXPRESSION_H_ #include -#include -#include #include +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/types.h" @@ -35,7 +35,7 @@ class RX_ANIM_EXPORT ExpressionController { // One channel of a pose: a morph target by source name (e.g. "jawOpen"), // hashed with MakeAssetId to match asset::MorphTarget::name_hash. struct PoseEntry { - std::string_view target; + base::StringRef target; f32 weight = 0; }; @@ -45,7 +45,7 @@ class RX_ANIM_EXPORT ExpressionController { // for that long after SetExpression, staggering the region behind faster // ones. struct Region { - std::string prefix; + base::String prefix; f32 halflife = 0.1f; // seconds f32 delay = 0; // seconds }; @@ -70,8 +70,8 @@ class RX_ANIM_EXPORT ExpressionController { // targets no earlier pose mentioned. AddDefaultPoses ships the stock set // (neutral, smile, angry, surprised, eyes_closed, pucker, smirk), built for // ARKit-style target names. - void AddPose(std::string_view name, const PoseEntry* entries, u32 count); - void AddPose(std::string_view name, std::initializer_list entries) { + void AddPose(base::StringRef name, const PoseEntry* entries, u32 count); + void AddPose(base::StringRef name, std::initializer_list entries) { AddPose(name, entries.begin(), static_cast(entries.size())); } void AddDefaultPoses(); @@ -85,7 +85,7 @@ class RX_ANIM_EXPORT ExpressionController { // zero. `transition_time` scales the per-region response and delay (an // approximate settle time; <= 0 keeps the defaults, which settle in about // 0.4 s). Returns false when no such pose was added. - bool SetExpression(std::string_view name, f32 transition_time = 0); + bool SetExpression(base::StringRef name, f32 transition_time = 0); bool SetExpression(u64 pose_hash, f32 transition_time = 0); u64 expression() const { return active_pose_; } @@ -106,7 +106,7 @@ class RX_ANIM_EXPORT ExpressionController { private: struct Channel { - std::string name; + base::String name; u64 target = 0; f32 value = 0; f32 velocity = 0; @@ -127,7 +127,7 @@ class RX_ANIM_EXPORT ExpressionController { }; enum class BlinkPhase : u8 { kWait, kClose, kHold, kOpen }; - u32 EnsureChannel(std::string_view name); + u32 EnsureChannel(base::StringRef name); void UpdateBlink(f32 dt); f32 NextRand01(); diff --git a/engine/anim/foot_ik.cc b/engine/anim/foot_ik.cc index 273ae8db..2d59e971 100644 --- a/engine/anim/foot_ik.cc +++ b/engine/anim/foot_ik.cc @@ -1,7 +1,7 @@ #include "anim/foot_ik.h" +#include "core/scalar.h" -#include -#include +#include namespace rx::anim { namespace { @@ -59,7 +59,7 @@ void SolveFootIk(const asset::Skeleton& skeleton, const GroundQuery& ground, con Vec3 knee_pos = Translation((*bone_model)[p.knee]); Vec3 ankle_pos = Translation((*bone_model)[p.ankle]); f32 leg = Length(knee_pos - hip_pos) + Length(ankle_pos - knee_pos); - leg_span = std::max(leg_span, leg); + leg_span = rx::Max(leg_span, leg); Vec3 hit, normal; // Start the down-ray well above the foot, scaled to the leg so it works in // metres (test rig) or game units alike. @@ -69,7 +69,7 @@ void SolveFootIk(const asset::Skeleton& skeleton, const GroundQuery& ground, con p.target = hit + up * ankle_height; p.lift = Dot(p.target - ankle_pos, up); // Only planted feet pull the pelvis down (a lifting swing foot must not). - if (p.weight >= 0.5f) lowest_lift = std::min(lowest_lift, p.lift); + if (p.weight >= 0.5f) lowest_lift = rx::Min(lowest_lift, p.lift); any = true; } if (!any) return; @@ -79,7 +79,7 @@ void SolveFootIk(const asset::Skeleton& skeleton, const GroundQuery& ground, con i32 pelvis = skeleton.Find("NPC Pelvis [Pelv]"); if (pelvis < 0) pelvis = skeleton.Find("NPC Root [Root]"); if (pelvis >= 0 && lowest_lift < 0) { - f32 drop = std::max(lowest_lift, -leg_span * 0.5f); // clamp so we never fold up + f32 drop = rx::Max(lowest_lift, -leg_span * 0.5f); // clamp so we never fold up pose->translation[pelvis] += up * drop; ComputeModelMatrices(skeleton, *pose, bone_model); } @@ -96,7 +96,7 @@ void SolveFootIk(const asset::Skeleton& skeleton, const GroundQuery& ground, con if (l_thigh < 1e-4f || l_calf < 1e-4f) continue; Vec3 to_target = p.target - a; - f32 reach = Clamp(Length(to_target), std::fabs(l_thigh - l_calf) + 1e-3f, + f32 reach = Clamp(Length(to_target), ::fabs(l_thigh - l_calf) + 1e-3f, l_thigh + l_calf - 1e-3f); Vec3 dir = Normalize(to_target); Vec3 effective_target = a + dir * reach; @@ -106,7 +106,7 @@ void SolveFootIk(const asset::Skeleton& skeleton, const GroundQuery& ground, con f32 cos_hip = Clamp((l_thigh * l_thigh + reach * reach - l_calf * l_calf) / (2.0f * l_thigh * reach), -1.0f, 1.0f); - f32 hip_angle = std::acos(cos_hip); + f32 hip_angle = ::acos(cos_hip); Vec3 plane_n = Cross(dir, forward); if (Length(plane_n) < 1e-4f) plane_n = Cross(dir, up); plane_n = Normalize(plane_n); diff --git a/engine/anim/foot_ik.h b/engine/anim/foot_ik.h index d621d690..80dd89ab 100644 --- a/engine/anim/foot_ik.h +++ b/engine/anim/foot_ik.h @@ -1,10 +1,10 @@ #ifndef RX_ANIM_FOOT_IK_H_ #define RX_ANIM_FOOT_IK_H_ -#include #include "anim/pose.h" #include "asset/skeleton.h" +#include "base/functional/function.h" #include "core/math.h" namespace rx::anim { @@ -12,7 +12,7 @@ namespace rx::anim { // Raycast straight down from a model-space origin; on a hit fill the model-space // ground point and surface normal. The engine wraps the physics world (which // works in engine/world space) and converts to/from the actor model space. -using GroundQuery = std::function; +using GroundQuery = base::Function; // SOTA-ish foot IK for a biped: drops the pelvis so the lower foot can reach, // then analytic two-bone IK plants each foot on the ground and pitches it to diff --git a/engine/anim/foot_placement.cc b/engine/anim/foot_placement.cc index b997602b..2bd01503 100644 --- a/engine/anim/foot_placement.cc +++ b/engine/anim/foot_placement.cc @@ -1,8 +1,9 @@ #include "anim/foot_placement.h" -#include #include "anim/anim_internal.h" +#include "base/containers/vector.h" +#include "base/memory/unique_pointer.h" namespace rx::anim { @@ -15,16 +16,16 @@ struct FootPlacement::Impl { bool ok = false; f32 ankle_height = 0.08f; // Model-space scratch (kinema fills it via LocalToModel); sized once at Bind. - std::vector mt; - std::vector mr; - std::vector ms; + base::Vector mt; + base::Vector mr; + base::Vector ms; kinema::PoseView model() { return kinema::PoseView{mt.data(), mr.data(), ms.data(), static_cast(mt.size())}; } }; -FootPlacement::FootPlacement() : impl_(std::make_unique()) {} +FootPlacement::FootPlacement() : impl_(base::MakeUnique()) {} FootPlacement::~FootPlacement() = default; FootPlacement::FootPlacement(FootPlacement&&) noexcept = default; FootPlacement& FootPlacement::operator=(FootPlacement&&) noexcept = default; diff --git a/engine/anim/foot_placement.h b/engine/anim/foot_placement.h index 131e4e4f..02d9b5e3 100644 --- a/engine/anim/foot_placement.h +++ b/engine/anim/foot_placement.h @@ -1,11 +1,11 @@ #ifndef RX_ANIM_FOOT_PLACEMENT_H_ #define RX_ANIM_FOOT_PLACEMENT_H_ -#include -#include #include "anim/anim_graph.h" #include "anim/pose.h" +#include "base/functional/function.h" +#include "base/memory/unique_pointer.h" #include "core/math.h" namespace rx::anim { @@ -15,7 +15,7 @@ namespace rx::anim { // app wraps its physics world and converts to/from the actor model space - // engine/anim links no physics, exactly as kinema itself keeps raycasts out of // its solvers. -using GroundProbe = std::function; +using GroundProbe = base::Function; // Grounds a kinema-driven pose: probes under each foot, drops the pelvis to the // lower foot and runs analytic two-bone leg IK onto the contacts @@ -44,7 +44,7 @@ class FootPlacement { private: struct Impl; - std::unique_ptr impl_; + base::UniquePointer impl_; }; } // namespace rx::anim diff --git a/engine/anim/locomotion.cc b/engine/anim/locomotion.cc index fbcd67f3..a9a7636f 100644 --- a/engine/anim/locomotion.cc +++ b/engine/anim/locomotion.cc @@ -1,9 +1,9 @@ #include "anim/locomotion.h" +#include "base/strings/string_ref.h" +#include "core/scalar.h" -#include -#include +#include #include -#include namespace rx::anim { namespace { @@ -17,8 +17,8 @@ f32 Clamp01(f32 v) { return v < 0 ? 0 : (v > 1 ? 1 : v); } f32 Mix(f32 a, f32 b, f32 weight) { return a + (b - a) * weight; } i32 FindFirst(const asset::Skeleton &skeleton, - std::initializer_list names) { - for (std::string_view name : names) { + std::initializer_list names) { + for (base::StringRef name : names) { const i32 bone = skeleton.Find(name); if (bone >= 0) return bone; @@ -111,8 +111,8 @@ f32 AdvancePhase(f32 phase, f32 speed, f32 dt) { f32 AdvancePhase(f32 phase, f32 speed, f32 dt, const WalkStyle &style) { f32 cycles_per_second = speed > 0.01f ? speed / kStride : 0.5f; // idle ticks slowly - phase += cycles_per_second * std::max(style.cadence_scale, 0.0f) * dt; - phase -= std::floor(phase); + phase += cycles_per_second * rx::Max(style.cadence_scale, 0.0f) * dt; + phase -= ::floor(phase); return phase; } @@ -129,7 +129,7 @@ void Locomotion::Apply(const asset::Skeleton &skeleton, f32 speed, if (!is_biped) { // Quadrupeds/creatures: a slow breathing bob on the spine, no stepping. - f32 breathe = std::sin(theta) * 0.03f; + f32 breathe = ::sin(theta) * 0.03f; const i32 spine = FindFirst(skeleton, {"NPC Spine [Spn0]", "spine", "spine-1", "chest"}); LayerRotation(pose, spine, {1, 0, 0}, breathe); @@ -156,7 +156,7 @@ void Locomotion::Apply(const asset::Skeleton &skeleton, f32 speed, const i32 right_forearm = FindFirst( skeleton, {"NPC R Forearm [RLar]", "forearm.bend.R", "forearm.R"}); - f32 leg = std::sin(theta); // left leg leads, right is opposite phase + f32 leg = ::sin(theta); // left leg leads, right is opposite phase f32 thigh_amp = (0.06f + 0.45f * walk + 0.35f * run) * style.stride_scale; f32 knee_amp = (0.10f + 0.7f * walk + 0.6f * run) * style.knee_lift_scale; f32 arm_amp = (0.05f + 0.35f * walk + 0.45f * run) * style.arm_swing_scale; @@ -167,9 +167,9 @@ void Locomotion::Apply(const asset::Skeleton &skeleton, f32 speed, LayerRotation(pose, left_thigh, x, ts * thigh_amp * leg); LayerRotation(pose, right_thigh, x, -ts * thigh_amp * leg); LayerRotation(pose, left_shin, x, - -knee_amp * Clamp01(-std::sin(theta - 0.6f))); + -knee_amp * Clamp01(-::sin(theta - 0.6f))); LayerRotation(pose, right_shin, x, - -knee_amp * Clamp01(-std::sin(theta + kPi - 0.6f))); + -knee_amp * Clamp01(-::sin(theta + kPi - 0.6f))); // Arms counter-swing the legs. f32 as = arm_axis_sign; @@ -184,15 +184,15 @@ void Locomotion::Apply(const asset::Skeleton &skeleton, f32 speed, f32 leg_length = 1.0f; if (left_shin >= 0) leg_length = - std::max(Length(skeleton.bones[left_shin].bind_translation), 1.0e-4f); - const f32 sway = std::cos(theta) * walk; - const f32 twist = std::sin(theta) * walk; + rx::Max(Length(skeleton.bones[left_shin].bind_translation), 1.0e-4f); + const f32 sway = ::cos(theta) * walk; + const f32 twist = ::sin(theta) * walk; LayerRotation(pose, hip, {0, 0, 1}, style.hip_roll * sway); LayerRotation(pose, hip, {0, 1, 0}, style.hip_yaw * twist); if (hip >= 0 && hip < static_cast(pose->translation.size())) { pose->translation[hip].x += style.hip_shift * leg_length * sway; pose->translation[hip].y += style.vertical_bob * leg_length * walk * - (0.5f - 0.5f * std::cos(theta * 2.0f)); + (0.5f - 0.5f * ::cos(theta * 2.0f)); } LayerRotation(pose, spine, {0, 0, 1}, -style.torso_counter_roll * sway); LayerRotation(pose, spine, {0, 1, 0}, -style.torso_counter_yaw * twist); diff --git a/engine/anim/morph.cc b/engine/anim/morph.cc index 842f084e..208b4384 100644 --- a/engine/anim/morph.cc +++ b/engine/anim/morph.cc @@ -1,6 +1,6 @@ #include "anim/morph.h" - -#include +#include "base/algorithm.h" +#include "core/scalar.h" namespace rx::anim { @@ -10,7 +10,7 @@ void SampleMorphWeights(const asset::MorphAnimation& animation, f32 time, const u32 targets = keys > 0 ? static_cast(animation.weights.size()) / keys : 0; out->resize(targets); if (targets == 0) return; - std::fill(out->begin(), out->end(), 0.0f); + base::Fill(out->begin(), out->end(), 0.0f); if (animation.weights.size() < static_cast(keys) * targets) return; // Last key at or before `time`; clamped so times outside the track hold the @@ -19,11 +19,11 @@ void SampleMorphWeights(const asset::MorphAnimation& animation, f32 time, while (key + 1 < keys && animation.times[key + 1] <= time) ++key; const f32* row = &animation.weights[static_cast(key) * targets]; if (animation.step || key + 1 >= keys || time <= animation.times[key]) { - std::copy(row, row + targets, out->data()); + base::Copy(row, row + targets, out->data()); return; } f32 span = animation.times[key + 1] - animation.times[key]; - f32 t = span > 1e-8f ? std::min((time - animation.times[key]) / span, 1.0f) : 0.0f; + f32 t = span > 1e-8f ? rx::Min((time - animation.times[key]) / span, 1.0f) : 0.0f; const f32* next = row + targets; for (u32 i = 0; i < targets; ++i) (*out)[i] = row[i] + (next[i] - row[i]) * t; } diff --git a/engine/anim/pose.cc b/engine/anim/pose.cc index caad4c4e..99b83f9f 100644 --- a/engine/anim/pose.cc +++ b/engine/anim/pose.cc @@ -1,4 +1,5 @@ #include "anim/pose.h" +#include "base/strings/xstring.h" namespace rx::anim { @@ -18,7 +19,7 @@ base::Vector BuildBoneRemap(const asset::Skeleton& skeleton, const asset::SkinBinding& skin) { base::Vector remap; remap.reserve(skin.bones.size()); - for (const std::string& name : skin.bones) remap.push_back(skeleton.Find(name)); + for (const base::String& name : skin.bones) remap.push_back(skeleton.Find(name)); return remap; } diff --git a/engine/anim/rig_player.cc b/engine/anim/rig_player.cc index d05d8d70..10c8d8a9 100644 --- a/engine/anim/rig_player.cc +++ b/engine/anim/rig_player.cc @@ -1,8 +1,10 @@ #include "anim/rig_player.h" -#include #include "anim/anim_internal.h" +#include "base/containers/vector.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/string_ref.h" namespace rx::anim { @@ -13,11 +15,11 @@ struct RigPlayer::Impl { kinema::StateMachineInstance sm; kinema::PoseArena arena; kinema::SyncGroup sync; - std::vector params; + base::Vector params; f32 prev_phase = 0.0f; // last frame's normalized locomotion phase [0,1) }; -RigPlayer::RigPlayer() : impl_(std::make_unique()) {} +RigPlayer::RigPlayer() : impl_(base::MakeUnique()) {} RigPlayer::~RigPlayer() = default; RigPlayer::RigPlayer(RigPlayer&&) noexcept = default; RigPlayer& RigPlayer::operator=(RigPlayer&&) noexcept = default; @@ -92,14 +94,14 @@ Vec3 RigPlayer::Update(f32 dt, SkeletonPose* out, const EventSink& on_event) { const f32 t0 = d.prev_phase * dur; const f32 t1 = phase * dur; c->EventsInRange(t0, t1, [&](const kinema::ClipEvent& e) { - on_event(Event{e.name_hash, e.name ? std::string_view(e.name) : std::string_view{}, + on_event(Event{e.name_hash, e.name ? base::StringRef(e.name) : base::StringRef{}, Event::Phase::kPoint}); }); c->RangedEventsInRange(t0, t1, [&](const kinema::ClipRangedEvent& r, kinema::RangePhase p) { Event::Phase ph = p == kinema::RangePhase::kEnter ? Event::Phase::kEnter : p == kinema::RangePhase::kActive ? Event::Phase::kActive : Event::Phase::kExit; - on_event(Event{r.name_hash, r.name ? std::string_view(r.name) : std::string_view{}, ph}); + on_event(Event{r.name_hash, r.name ? base::StringRef(r.name) : base::StringRef{}, ph}); }); } } diff --git a/engine/anim/rig_player.h b/engine/anim/rig_player.h index 0a9e1bd6..fe84ee9e 100644 --- a/engine/anim/rig_player.h +++ b/engine/anim/rig_player.h @@ -1,12 +1,12 @@ #ifndef RX_ANIM_RIG_PLAYER_H_ #define RX_ANIM_RIG_PLAYER_H_ -#include -#include -#include #include "anim/anim_graph.h" #include "anim/pose.h" +#include "base/functional/function.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/string_ref.h" #include "core/math.h" namespace rx::anim { @@ -44,10 +44,10 @@ class RigPlayer { // and is valid for the duration of the callback only. struct Event { u64 name_hash = 0; - std::string_view name; + base::StringRef name; enum class Phase : u8 { kPoint, kEnter, kActive, kExit } phase = Phase::kPoint; }; - using EventSink = std::function; + using EventSink = base::Function; // Advance the graph by dt, write this frame's local pose into `out` (sized to // the skeleton) and return the model-space root-translation delta for this @@ -62,7 +62,7 @@ class RigPlayer { private: struct Impl; - std::unique_ptr impl_; + base::UniquePointer impl_; }; } // namespace rx::anim diff --git a/engine/app/application.h b/engine/app/application.h index 3ac37e6a..3cf2b039 100644 --- a/engine/app/application.h +++ b/engine/app/application.h @@ -1,9 +1,9 @@ #ifndef RX_ENGINE_APP_APPLICATION_H_ #define RX_ENGINE_APP_APPLICATION_H_ -#include #include "app/services.h" +#include "base/functional/function.h" #include "render/core/presets.h" namespace rx::app { @@ -48,7 +48,7 @@ struct AppConfig { // hand; anything an app tuned earlier is lost in that replacement. This runs // after all of it, so an app-level profile (recreation's platform .ini) // cannot be clobbered by the preset it sits on top of. - std::function tune_settings; + base::Function tune_settings; }; // The game's side of the host contract. Host::RunFrame drives these in order: diff --git a/engine/app/host.cc b/engine/app/host.cc index 6764c4fd..c08697db 100644 --- a/engine/app/host.cc +++ b/engine/app/host.cc @@ -1,13 +1,16 @@ #include "app/host.h" -#include -#include -#include -#include +#include #include #include "asset/engine_archives.h" +#include "base/algorithm.h" +#include "base/atomic.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/threading/thread.h" #include "core/feature_registry.h" #include "core/log.h" #include "core/math.h" @@ -47,7 +50,7 @@ base::Option SplashSeconds{"splash.seconds", ui::Splash::kDefaultSeconds, } // namespace bool Host::Initialize(const AppConfig& config, Application& app, - std::unique_ptr window) { + base::UniquePointer window) { config_ = config; app_ = &app; InitFeatures(); // apply RX_FEATURES overrides before any flag read @@ -55,7 +58,7 @@ bool Host::Initialize(const AppConfig& config, Application& app, // Memory plan first, so the pools are pre-reserved and the budgets are in // place before any subsystem starts allocating in earnest. mem::ApplyMemoryConfig(mem::LoadMemoryConfig()); - jobs_ = std::make_unique(); + jobs_ = base::MakeUnique(); ConfigureClock(20.0f); // An app that asked for lockstep (AppConfig::fixed_delta, i.e. a capture run) // gets it unless the caller spoke about the clock themselves: RX_FIXED_DT set @@ -77,7 +80,7 @@ bool Host::Initialize(const AppConfig& config, Application& app, if (config_.height > 0) desc.height = config_.height; if (!config_.headless) { desc.touch_emits_mouse = TouchMouse; - window_ = window ? std::move(window) : Window::Create(desc); + window_ = window ? base::move(window) : Window::Create(desc); if (!renderer_.Initialize(config_.renderer, *window_)) return false; ApplyRenderPreset(); } else if (config_.offscreen) { @@ -89,7 +92,7 @@ bool Host::Initialize(const AppConfig& config, Application& app, // Audio comes up before content loads (it reads sound bytes lazily through // the Vfs). Headless runs and mute (RX_AUDIO_MUTE) open no device and run // silent; the rest of the engine is unaffected either way. - audio_ = std::make_unique(); + audio_ = base::MakeUnique(); audio_->Initialize(&vfs_); if (physics_.Initialize()) { @@ -106,7 +109,7 @@ bool Host::Initialize(const AppConfig& config, Application& app, transform->position[0] = position.x; transform->position[1] = position.y; transform->position[2] = position.z; - std::memcpy(transform->rotation, rotation, sizeof(rotation)); + base::MemCopy(transform->rotation, rotation, sizeof(rotation)); } } }); @@ -115,15 +118,15 @@ bool Host::Initialize(const AppConfig& config, Application& app, // Hand the application every service it may wire against. Addresses are // host members, stable until Shutdown. services_.host = this; - services_.window = window_.get(); - services_.jobs = jobs_.get(); + services_.window = window_.Get_UseOnlyIfYouKnowWhatYouareDoing(); + services_.jobs = jobs_.Get_UseOnlyIfYouKnowWhatYouareDoing(); services_.clock = &clock_; services_.world = &world_; services_.scheduler = &scheduler_; services_.renderer = &renderer_; services_.physics = &physics_; services_.vfs = &vfs_; - services_.audio = audio_.get(); + services_.audio = audio_.Get_UseOnlyIfYouKnowWhatYouareDoing(); services_.input_map = &input_map_; services_.actions = &actions_; services_.physics_bindings = &physics_bindings_; @@ -134,9 +137,9 @@ bool Host::Initialize(const AppConfig& config, Application& app, // the plate parks (ui::Splash::Initialize) is the one the application is // about to build its own UI into. if (WantsSplash()) { - auto splash = std::make_unique(); + auto splash = base::MakeUnique(); if (splash->Initialize(*window_, renderer_, vfs_, SplashSeconds.get())) - splash_ = std::move(splash); + splash_ = base::move(splash); } return app_->OnInitialize(services_); @@ -280,7 +283,7 @@ void Host::ConfigureClock(f32 base_timescale) { } bool Host::RunFrame() { - if (quit_.load(std::memory_order_relaxed)) return false; + if (quit_.load(base::memory_order_relaxed)) return false; mem::MainFrameArena().Reset(); if (FixedDt.get() > 0.0f) timer_.set_fixed_delta(static_cast(FixedDt.get())); if (window_ && !window_->PumpEvents()) return false; @@ -337,7 +340,7 @@ bool Host::RunFrame() { // is the one stall the splash costs, and it lands on the frame the // application becomes visible, before anything is animating. renderer_.WaitIdle(); - splash_.reset(); + splash_.Reset(); } } // Move the audio listener to this frame's viewpoint, so positional @@ -352,9 +355,9 @@ bool Host::RunFrame() { } else { // No vsync to pace the loop; yield between fixed steps instead of // spinning a core. - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + base::SleepForMilliseconds(1); } - return !quit_.load(std::memory_order_relaxed); + return !quit_.load(base::memory_order_relaxed); } namespace { @@ -419,7 +422,7 @@ void Host::GatherEntityDraws(render::FrameView& view) { emit(entity, world_matrix(entity), renderable.mesh); }); } - std::swap(prev_transforms_, transforms_scratch_); + base::Swap(prev_transforms_, transforms_scratch_); } int Host::Run() { @@ -447,7 +450,7 @@ void Host::Shutdown() { if (rendering()) renderer_.WaitIdle(); // A run that quit inside the first seconds still owns a plate; drop it while // the device it uploaded through is alive. - splash_.reset(); + splash_.Reset(); // Destroy app-provided frame callbacks while the renderer and application // resources they may own are still alive. renderer_.ClearFrameCallbacks(); diff --git a/engine/app/host.h b/engine/app/host.h index e1575c8f..b2ce1687 100644 --- a/engine/app/host.h +++ b/engine/app/host.h @@ -1,14 +1,14 @@ #ifndef RX_ENGINE_APP_HOST_H_ #define RX_ENGINE_APP_HOST_H_ -#include -#include #include #include #include "app/application.h" #include "app/services.h" +#include "base/atomic.h" +#include "base/memory/unique_pointer.h" #include "core/export.h" #include "core/frame_timer.h" #include "ui/splash.h" @@ -32,7 +32,7 @@ class RX_APP_EXPORT Host { // brought up (the destructor calls Shutdown), so callers need not Shutdown // after a failure. `app` must outlive the host. bool Initialize(const AppConfig& config, Application& app, - std::unique_ptr window = nullptr); + base::UniquePointer window = nullptr); int Run(); // One iteration of the main loop. Returns false when the host wants to // stop. Platforms that own the loop drive this directly instead of the @@ -45,7 +45,7 @@ class RX_APP_EXPORT Host { void OnSurfaceCreated(); // Safe to call from a signal handler; Run() returns after the current frame. - void RequestQuit() { quit_.store(true, std::memory_order_relaxed); } + void RequestQuit() { quit_.store(true, base::memory_order_relaxed); } Services& services() { return services_; } @@ -75,8 +75,8 @@ class RX_APP_EXPORT Host { AppConfig config_; Application* app_ = nullptr; - std::unique_ptr window_; - std::unique_ptr jobs_; + base::UniquePointer window_; + base::UniquePointer jobs_; FrameTimer timer_; // The clock driving the day/night cycle; applications derive sun/sky from // it (or ignore it entirely). @@ -86,11 +86,11 @@ class RX_APP_EXPORT Host { ecs::Scheduler scheduler_; asset::Vfs vfs_; - std::unique_ptr audio_; + base::UniquePointer audio_; render::Renderer renderer_; // Alive only until the plate is spent, then dropped mid-run (RunFrame). - std::unique_ptr splash_; + base::UniquePointer splash_; physics::PhysicsWorld physics_; base::Vector physics_bindings_; base::Vector hair_bindings_; @@ -109,7 +109,7 @@ class RX_APP_EXPORT Host { Services services_; - std::atomic quit_ = false; + base::Atomic quit_ = false; bool shut_down_ = false; }; diff --git a/engine/asset/asset_database.cc b/engine/asset/asset_database.cc index 4fdea445..c6b0f217 100644 --- a/engine/asset/asset_database.cc +++ b/engine/asset/asset_database.cc @@ -1,33 +1,37 @@ #include "asset/asset_database.h" -#include +#include "base/memory/move.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "base/threading/lock_guard.h" +#include "base/threading/mutex.h" #include "core/log.h" namespace rx::asset { namespace { -base::String ExtensionOf(std::string_view normalized_path) { +base::String ExtensionOf(base::StringRef normalized_path) { size_t dot = normalized_path.rfind('.'); - if (dot == std::string_view::npos) + if (dot == base::StringRef::npos) return {}; - std::string_view extension = normalized_path.substr(dot); + base::StringRef extension = normalized_path.substr(dot); return base::String(extension.data(), extension.size()); } template const Asset * -LoadWith(std::mutex &mutex, Vfs &vfs, std::string_view path, +LoadWith(base::Mutex &mutex, Vfs &vfs, base::StringRef path, const base::UnorderedMap &converters, base::UnorderedMap> &cache) { - std::string normalized = NormalizePath(path); + base::String normalized = NormalizePath(path); AssetId id = MakeAssetId(normalized); // Record the id -> path mapping so tooling (scene serialization) can write a // relocatable path for this asset even without a database handle. Done before // the cache check so it survives repeated lookups and cached failures. RecordAssetPath(id, normalized); { - std::scoped_lock lock(mutex); + base::LockGuard lock(mutex); if (auto *cached = cache.find(id.hash)) return cached->Get_UseOnlyIfYouKnowWhatYouareDoing(); } @@ -59,9 +63,9 @@ LoadWith(std::mutex &mutex, Vfs &vfs, std::string_view path, // so `asset` still holds ours when the key was taken; the `asset` test keeps // this honest for a container that moves first (it would just mean the // existing entry always wins, never a use-after-move). - std::scoped_lock lock(mutex); - auto result = cache.emplace(id.hash, std::move(asset)); - if (!result.second && asset && !*result.first) *result.first = std::move(asset); + base::LockGuard lock(mutex); + auto result = cache.emplace(id.hash, base::move(asset)); + if (!result.second && asset && !*result.first) *result.first = base::move(asset); return result.first->Get_UseOnlyIfYouKnowWhatYouareDoing(); } @@ -77,33 +81,33 @@ FindIn(const base::UnorderedMap> &cache, void AssetDatabase::RegisterMeshConverter(base::String extension, MeshConverter converter) { - mesh_converters_.emplace(extension, std::move(converter)); + mesh_converters_.emplace(extension, base::move(converter)); } void AssetDatabase::RegisterTextureConverter(base::String extension, TextureConverter converter) { - texture_converters_.emplace(extension, std::move(converter)); + texture_converters_.emplace(extension, base::move(converter)); } void AssetDatabase::RegisterMaterialConverter(base::String extension, MaterialConverter converter) { - material_converters_.emplace(extension, std::move(converter)); + material_converters_.emplace(extension, base::move(converter)); } -const Mesh *AssetDatabase::LoadMesh(std::string_view path) { +const Mesh *AssetDatabase::LoadMesh(base::StringRef path) { return LoadWith(mutex_, vfs_, path, mesh_converters_, meshes_); } -const Texture *AssetDatabase::LoadTexture(std::string_view path) { +const Texture *AssetDatabase::LoadTexture(base::StringRef path) { return LoadWith(mutex_, vfs_, path, texture_converters_, textures_); } -const Material *AssetDatabase::LoadMaterial(std::string_view path) { +const Material *AssetDatabase::LoadMaterial(base::StringRef path) { return LoadWith(mutex_, vfs_, path, material_converters_, materials_); } void AssetDatabase::AddMaterial(const Material &material) { - std::scoped_lock lock(mutex_); + base::LockGuard lock(mutex_); if (auto *existing = materials_.find(material.id.hash)) { if (!*existing) *existing = base::MakeUnique(material); @@ -114,66 +118,66 @@ void AssetDatabase::AddMaterial(const Material &material) { const Mesh *AssetDatabase::AddMesh(Mesh mesh) { u64 hash = mesh.id.hash; - std::scoped_lock lock(mutex_); + base::LockGuard lock(mutex_); if (auto *existing = meshes_.find(hash)) { if (!*existing) - *existing = base::MakeUnique(std::move(mesh)); + *existing = base::MakeUnique(base::move(mesh)); return existing->Get_UseOnlyIfYouKnowWhatYouareDoing(); } - return meshes_.emplace(hash, base::MakeUnique(std::move(mesh))) + return meshes_.emplace(hash, base::MakeUnique(base::move(mesh))) .first->Get_UseOnlyIfYouKnowWhatYouareDoing(); } const Mesh *AssetDatabase::ReplaceMesh(Mesh mesh) { const u64 hash = mesh.id.hash; - std::scoped_lock lock(mutex_); + base::LockGuard lock(mutex_); if (auto *existing = meshes_.find(hash)) { Mesh *value = existing->Get_UseOnlyIfYouKnowWhatYouareDoing(); if (value) { - *value = std::move(mesh); + *value = base::move(mesh); return value; } meshes_.erase(hash); } - return meshes_.emplace(hash, base::MakeUnique(std::move(mesh))) + return meshes_.emplace(hash, base::MakeUnique(base::move(mesh))) .first->Get_UseOnlyIfYouKnowWhatYouareDoing(); } bool AssetDatabase::RemoveMesh(AssetId id) { - std::scoped_lock lock(mutex_); + base::LockGuard lock(mutex_); return meshes_.erase(id.hash) != 0; } const Texture *AssetDatabase::AddTexture(Texture texture) { u64 hash = texture.id.hash; - std::scoped_lock lock(mutex_); + base::LockGuard lock(mutex_); if (auto *existing = textures_.find(hash)) { if (!*existing) - *existing = base::MakeUnique(std::move(texture)); + *existing = base::MakeUnique(base::move(texture)); return existing->Get_UseOnlyIfYouKnowWhatYouareDoing(); } - return textures_.emplace(hash, base::MakeUnique(std::move(texture))) + return textures_.emplace(hash, base::MakeUnique(base::move(texture))) .first->Get_UseOnlyIfYouKnowWhatYouareDoing(); } const Material *AssetDatabase::FindMaterial(AssetId id) const { - std::scoped_lock lock(mutex_); + base::LockGuard lock(mutex_); return FindIn(materials_, id); } Material *AssetDatabase::FindMaterialMutable(AssetId id) { - std::scoped_lock lock(mutex_); + base::LockGuard lock(mutex_); auto *cached = materials_.find(id.hash); return cached ? cached->Get_UseOnlyIfYouKnowWhatYouareDoing() : nullptr; } const Texture *AssetDatabase::FindTexture(AssetId id) const { - std::scoped_lock lock(mutex_); + base::LockGuard lock(mutex_); return FindIn(textures_, id); } const Mesh *AssetDatabase::FindMesh(AssetId id) const { - std::scoped_lock lock(mutex_); + base::LockGuard lock(mutex_); return FindIn(meshes_, id); } diff --git a/engine/asset/asset_database.h b/engine/asset/asset_database.h index 85ab4663..35169b57 100644 --- a/engine/asset/asset_database.h +++ b/engine/asset/asset_database.h @@ -1,8 +1,6 @@ #ifndef RX_ASSET_ASSET_DATABASE_H_ #define RX_ASSET_ASSET_DATABASE_H_ -#include -#include #include #include @@ -13,6 +11,9 @@ #include "asset/mesh.h" #include "asset/texture.h" #include "asset/vfs.h" +#include "base/functional/function.h" +#include "base/strings/string_ref.h" +#include "base/threading/mutex.h" #include "core/export.h" namespace rx::asset { @@ -23,12 +24,12 @@ namespace rx::asset { // touching this module. The normalized source path rides along for converters // that key behavior off naming conventions (e.g. _n.dds normal maps stay // linear). -using MeshConverter = std::function( - ByteSpan, AssetId, std::string_view path)>; -using TextureConverter = std::function( - ByteSpan, AssetId, std::string_view path)>; -using MaterialConverter = std::function( - ByteSpan, AssetId, std::string_view path)>; +using MeshConverter = base::Function( + ByteSpan, AssetId, base::StringRef path)>; +using TextureConverter = base::Function( + ByteSpan, AssetId, base::StringRef path)>; +using MaterialConverter = base::Function( + ByteSpan, AssetId, base::StringRef path)>; class RX_ASSET_EXPORT AssetDatabase { public: @@ -47,9 +48,9 @@ class RX_ASSET_EXPORT AssetDatabase { // background prefetch must not stall the main thread for a whole convert). // Concurrent loads of the same key may both convert; the first insert wins, // except that a successful convert supersedes a concurrently cached failure. - const Mesh *LoadMesh(std::string_view path); - const Texture *LoadTexture(std::string_view path); - const Material *LoadMaterial(std::string_view path); + const Mesh *LoadMesh(base::StringRef path); + const Texture *LoadTexture(base::StringRef path); + const Material *LoadMaterial(base::StringRef path); // Side channel for converters that synthesize assets while converting // another (NIF shader properties become materials) and for procedurally @@ -81,7 +82,7 @@ class RX_ASSET_EXPORT AssetDatabase { // heap objects behind UniquePointers, stable across rehash. Invariant: // background prefetch threads only ADD (LoadMesh/LoadTexture); Remove* and // Replace* stay main-thread-only and must not race a convert on the same key. - mutable std::mutex mutex_; + mutable base::Mutex mutex_; base::UnorderedMap> meshes_; base::UnorderedMap> textures_; base::UnorderedMap> materials_; diff --git a/engine/asset/asset_id.h b/engine/asset/asset_id.h index 8b057887..5634a63f 100644 --- a/engine/asset/asset_id.h +++ b/engine/asset/asset_id.h @@ -1,10 +1,10 @@ #ifndef RX_ASSET_ASSET_ID_H_ #define RX_ASSET_ASSET_ID_H_ -#include -#include -#include +#include "base/optional.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/types.h" @@ -30,19 +30,19 @@ struct AssetId { explicit operator bool() const { return hash != 0; } }; -RX_ASSET_EXPORT AssetId MakeAssetId(std::string_view normalized_path); +RX_ASSET_EXPORT AssetId MakeAssetId(base::StringRef normalized_path); // Lowercases and converts backslashes to forward slashes (e.g. Bethesda-style // backslash asset paths normalize the same as forward-slash ones). -RX_ASSET_EXPORT std::string NormalizePath(std::string_view path); +RX_ASSET_EXPORT base::String NormalizePath(base::StringRef path); // Process-global id -> source-path table for tooling (scene serialization needs // to write a human-readable, relocatable path for a Renderable's AssetId rather // than a raw hash). The AssetDatabase records every path it is asked to load, // so a saver with no database handle can still resolve ids it produced. The // path stored is already normalized; recording is idempotent per id. -RX_ASSET_EXPORT void RecordAssetPath(AssetId id, std::string_view normalized_path); -RX_ASSET_EXPORT std::optional LookupAssetPath(AssetId id); +RX_ASSET_EXPORT void RecordAssetPath(AssetId id, base::StringRef normalized_path); +RX_ASSET_EXPORT base::Optional LookupAssetPath(AssetId id); } // namespace rx::asset diff --git a/engine/asset/bc_encode.cc b/engine/asset/bc_encode.cc index 0de88506..a8f2f560 100644 --- a/engine/asset/bc_encode.cc +++ b/engine/asset/bc_encode.cc @@ -1,8 +1,11 @@ #include "asset/bc_encode.h" +#include "base/algorithm.h" +#include "base/memory/mem_ops.h" +#include "core/scalar.h" -#include -#include -#include +#include +#include +#include namespace rx::asset { namespace { @@ -48,8 +51,8 @@ void FitLine(const f32* points, f32* e0, f32* e1) { } for (u32 i = 1; i < kBlockTexels; ++i) { for (int c = 0; c < N; ++c) { - lo[c] = std::min(lo[c], points[i * N + c]); - hi[c] = std::max(hi[c], points[i * N + c]); + lo[c] = rx::Min(lo[c], points[i * N + c]); + hi[c] = rx::Max(hi[c], points[i * N + c]); } } f32 len = 0; @@ -61,7 +64,7 @@ void FitLine(const f32* points, f32* e0, f32* e1) { // Single-colour block: any axis works, the projection collapses anyway. for (int c = 0; c < N; ++c) axis[c] = c == 0 ? 1.0f : 0.0f; } else { - const f32 inv = 1.0f / std::sqrt(len); + const f32 inv = 1.0f / ::sqrt(len); for (int c = 0; c < N; ++c) axis[c] *= inv; } } @@ -73,7 +76,7 @@ void FitLine(const f32* points, f32* e0, f32* e1) { f32 len = 0; for (int c = 0; c < N; ++c) len += next[c] * next[c]; if (len < 1e-12f) break; - const f32 inv = 1.0f / std::sqrt(len); + const f32 inv = 1.0f / ::sqrt(len); for (int c = 0; c < N; ++c) axis[c] = next[c] * inv; } @@ -82,12 +85,12 @@ void FitLine(const f32* points, f32* e0, f32* e1) { for (u32 i = 0; i < kBlockTexels; ++i) { f32 t = 0; for (int c = 0; c < N; ++c) t += (points[i * N + c] - mean[c]) * axis[c]; - tmin = std::min(tmin, t); - tmax = std::max(tmax, t); + tmin = rx::Min(tmin, t); + tmax = rx::Max(tmax, t); } for (int c = 0; c < N; ++c) { - e0[c] = std::clamp(mean[c] + axis[c] * tmin, 0.0f, 255.0f); - e1[c] = std::clamp(mean[c] + axis[c] * tmax, 0.0f, 255.0f); + e0[c] = rx::Clamp(mean[c] + axis[c] * tmin, 0.0f, 255.0f); + e1[c] = rx::Clamp(mean[c] + axis[c] * tmax, 0.0f, 255.0f); } } @@ -114,20 +117,20 @@ void RefitEndpoints(const f32* points, const f32* weights, f32* e0, f32* e1) { } } const f32 det = aa * bb - ab * ab; - if (std::abs(det) < 1e-6f) return; + if (::abs(det) < 1e-6f) return; const f32 inv = 1.0f / det; for (int c = 0; c < N; ++c) { - e0[c] = std::clamp((bb * ax[c] - ab * bx[c]) * inv, 0.0f, 255.0f); - e1[c] = std::clamp((aa * bx[c] - ab * ax[c]) * inv, 0.0f, 255.0f); + e0[c] = rx::Clamp((bb * ax[c] - ab * bx[c]) * inv, 0.0f, 255.0f); + e1[c] = rx::Clamp((aa * bx[c] - ab * ax[c]) * inv, 0.0f, 255.0f); } } // BC1 colour block u16 Quantize565(const f32* c) { - const int r = std::clamp(static_cast(c[0] * (31.0f / 255.0f) + 0.5f), 0, 31); - const int g = std::clamp(static_cast(c[1] * (63.0f / 255.0f) + 0.5f), 0, 63); - const int b = std::clamp(static_cast(c[2] * (31.0f / 255.0f) + 0.5f), 0, 31); + const int r = rx::Clamp(static_cast(c[0] * (31.0f / 255.0f) + 0.5f), 0, 31); + const int g = rx::Clamp(static_cast(c[1] * (63.0f / 255.0f) + 0.5f), 0, 63); + const int b = rx::Clamp(static_cast(c[2] * (31.0f / 255.0f) + 0.5f), 0, 31); return static_cast((r << 11) | (g << 5) | b); } @@ -153,8 +156,8 @@ f32 FitColor(const f32* points, f32* f0, f32* f1, ColorFit* fit) { u16 q0 = Quantize565(f0); u16 q1 = Quantize565(f1); if (q0 < q1) { - std::swap(q0, q1); - for (int c = 0; c < 3; ++c) std::swap(f0[c], f1[c]); + base::Swap(q0, q1); + for (int c = 0; c < 3; ++c) base::Swap(f0[c], f1[c]); } f32 palette[4][3]; Unquantize565(q0, palette[0]); @@ -221,8 +224,8 @@ void EncodeColorBlock(const u8* rgba, u8* out) { if (error >= best_error) break; best_error = error; best = candidate; - std::memcpy(e0, n0, sizeof(e0)); - std::memcpy(e1, n1, sizeof(e1)); + base::MemCopy(e0, n0, sizeof(e0)); + base::MemCopy(e1, n1, sizeof(e1)); } PackColorBlock(best, out); } @@ -238,11 +241,11 @@ struct AlphaFit { f32 FitAlpha(const f32* values, f32* f0, f32* f1, AlphaFit* fit) { // The 8-value mode is selected by r0 > r1; r0 is the endpoint index 0 lands // on, so the pair is ordered high-first. - int q0 = std::clamp(static_cast(*f0 + 0.5f), 0, 255); - int q1 = std::clamp(static_cast(*f1 + 0.5f), 0, 255); + int q0 = rx::Clamp(static_cast(*f0 + 0.5f), 0, 255); + int q1 = rx::Clamp(static_cast(*f1 + 0.5f), 0, 255); if (q0 < q1) { - std::swap(q0, q1); - std::swap(*f0, *f1); + base::Swap(q0, q1); + base::Swap(*f0, *f1); } f32 palette[8]; palette[0] = static_cast(q0); @@ -285,8 +288,8 @@ void EncodeChannelBlock(const f32* values, u8* out) { f32 lo = values[0]; f32 hi = values[0]; for (u32 i = 1; i < kBlockTexels; ++i) { - lo = std::min(lo, values[i]); - hi = std::max(hi, values[i]); + lo = rx::Min(lo, values[i]); + hi = rx::Max(hi, values[i]); } f32 e0 = hi; f32 e1 = lo; @@ -314,7 +317,7 @@ void EncodeChannelBlock(const f32* values, u8* out) { // The endpoint's low bit is the shared p-bit, so a component quantizes to the // nearest 8-bit value of the requested parity. u8 QuantizeWithParity(f32 v, u32 p) { - const int q = std::clamp(static_cast((v - static_cast(p)) * 0.5f + 0.5f), 0, 127); + const int q = rx::Clamp(static_cast((v - static_cast(p)) * 0.5f + 0.5f), 0, 127); return static_cast((q << 1) | p); } @@ -343,10 +346,10 @@ f32 AssignBc7Indices(const f32* points, const u8* e0, const u8* e1, u8* index) { f32 t = 0; for (int c = 0; c < 4; ++c) t += (points[i * 4 + c] - static_cast(e0[c])) * dir[c]; t /= dir_len2; - guess = static_cast(std::clamp(static_cast(t * 15.0f + 0.5f), 0, 15)); + guess = static_cast(rx::Clamp(static_cast(t * 15.0f + 0.5f), 0, 15)); } const u32 first = guess > 0 ? guess - 1 : 0; - const u32 last = std::min(15u, guess + 1); + const u32 last = rx::Min(15u, guess + 1); f32 best = 1e30f; u32 best_index = guess; for (u32 k = first; k <= last; ++k) { @@ -446,9 +449,9 @@ void EncodeBc7Block(const u8* rgba, u8* out) { best_error = error; best_p0 = p0; best_p1 = p1; - std::memcpy(best0, q0, sizeof(best0)); - std::memcpy(best1, q1, sizeof(best1)); - std::memcpy(best_index, index, sizeof(best_index)); + base::MemCopy(best0, q0, sizeof(best0)); + base::MemCopy(best1, q1, sizeof(best1)); + base::MemCopy(best_index, index, sizeof(best_index)); } } @@ -456,12 +459,12 @@ void EncodeBc7Block(const u8* rgba, u8* out) { // spends on the mode), so a block whose first texel sits past the middle of // the ramp has to be stored with its endpoints the other way round. if (best_index[0] > 7) { - for (int c = 0; c < 4; ++c) std::swap(best0[c], best1[c]); - std::swap(best_p0, best_p1); + for (int c = 0; c < 4; ++c) base::Swap(best0[c], best1[c]); + base::Swap(best_p0, best_p1); for (u32 i = 0; i < kBlockTexels; ++i) best_index[i] = static_cast(15 - best_index[i]); } - std::memset(out, 0, 16); + base::MemSet(out, 0, 16); BitWriter writer{out}; writer.Put(1u << 6, 7); // mode 6: six zeros then a one for (int c = 0; c < 3; ++c) { diff --git a/engine/asset/blend_import.cc b/engine/asset/blend_import.cc index f52c1448..d33adbf9 100644 --- a/engine/asset/blend_import.cc +++ b/engine/asset/blend_import.cc @@ -1,12 +1,10 @@ #include "asset/blend_import.h" -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #if defined(_WIN32) #include @@ -17,56 +15,49 @@ extern char **environ; #endif #include "asset/asset_id.h" +#include "base/containers/vector.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" +#include "core/format.h" #include "core/log.h" namespace rx::asset { -namespace fs = std::filesystem; namespace { -std::string CacheRoot(const BlendImportOptions &options) { +base::String CacheRoot(const BlendImportOptions &options) { if (!options.cache_directory.empty()) return options.cache_directory; - if (const char *xdg = std::getenv("XDG_CACHE_HOME")) - return (fs::path(xdg) / "rx/blend").string(); + if (const char *xdg = ::getenv("XDG_CACHE_HOME")) + return fs::Join(xdg, "rx/blend"); #if defined(_WIN32) - if (const char *local = std::getenv("LOCALAPPDATA")) - return (fs::path(local) / "rx/blend").string(); + if (const char *local = ::getenv("LOCALAPPDATA")) + return fs::Join(local, "rx/blend"); #else - if (const char *home = std::getenv("HOME")) - return (fs::path(home) / ".cache/rx/blend").string(); + if (const char *home = ::getenv("HOME")) + return fs::Join(home, ".cache/rx/blend"); #endif - return (fs::temp_directory_path() / "rx/blend").string(); + return fs::Join(fs::TempDirectory(), "rx/blend"); } -std::string CacheKey(const fs::path &source, const fs::path &script) { - std::error_code error; - const auto source_size = fs::file_size(source, error); - if (error) +base::String CacheKey(base::StringRef source, base::StringRef script) { + const base::Optional source_size = fs::FileSize(source); + const base::Optional source_time = fs::LastWriteTime(source); + const base::Optional script_time = fs::LastWriteTime(script); + if (!source_size || !source_time || !script_time) return {}; - const auto source_time = - fs::last_write_time(source, error).time_since_epoch().count(); - if (error) - return {}; - const auto script_time = - fs::last_write_time(script, error).time_since_epoch().count(); - if (error) - return {}; - // file_time_type::rep is not a fixed type across standard libraries (libc++ - // on windows makes it wider than long long), so name the width here. - const std::string identity = - fs::weakly_canonical(source).string() + ":" + std::to_string(source_size) + ":" + - std::to_string(static_cast(source_time)) + ":" + - std::to_string(static_cast(script_time)); + const base::String identity = + fs::WeaklyCanonical(source) + ":" + rx::ToString(*source_size) + ":" + + rx::ToString(*source_time) + ":" + rx::ToString(*script_time); char key[17]; - std::snprintf(key, sizeof(key), "%016llx", + ::snprintf(key, sizeof(key), "%016llx", static_cast(MakeAssetId(identity).hash)); return key; } -int Run(const std::vector &arguments) { - std::vector argv; +int Run(const base::Vector &arguments) { + base::Vector argv; argv.reserve(arguments.size() + 1); - for (const std::string &argument : arguments) + for (const base::String &argument : arguments) argv.push_back(const_cast(argument.c_str())); argv.push_back(nullptr); #if defined(_WIN32) @@ -84,79 +75,79 @@ int Run(const std::vector &arguments) { #endif } +bool NonEmptyFile(base::StringRef path) { + const base::Optional size = fs::FileSize(path); + return size && *size > 0; +} + } // namespace -bool ConvertBlendScene(const std::string &blend_path, +bool ConvertBlendScene(const base::String &blend_path, const BlendImportOptions &options, - BlendImportResult *out, std::string *error) { + BlendImportResult *out, base::String *error) { if (!out) return false; *out = {}; - const fs::path source = fs::absolute(blend_path); - const fs::path script = fs::absolute(options.converter_script); - std::string extension = source.extension().string(); - std::transform(extension.begin(), extension.end(), extension.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - if (extension != ".blend" || !fs::is_regular_file(source)) { + const base::String source = fs::Absolute(blend_path); + const base::String script = fs::Absolute(options.converter_script); + base::String extension(fs::Extension(source)); + for (char &c : extension) + c = static_cast(::tolower(static_cast(c))); + if (extension != ".blend" || !fs::IsRegularFile(source)) { if (error) - *error = "not a readable .blend file: " + source.string(); + *error = "not a readable .blend file: " + source; return false; } - if (options.converter_script.empty() || !fs::is_regular_file(script)) { + if (options.converter_script.empty() || !fs::IsRegularFile(script)) { if (error) - *error = "Blender converter script not found: " + script.string(); + *error = "Blender converter script not found: " + script; return false; } - const std::string key = CacheKey(source, script); + const base::String key = CacheKey(source, script); if (key.empty()) { if (error) *error = "could not stat Blender source or converter"; return false; } - std::error_code fs_error; - const fs::path cache = fs::path(CacheRoot(options)) / key; - fs::create_directories(cache, fs_error); - if (fs_error) { + const base::String cache = fs::Join(CacheRoot(options), key); + fs::CreateDirectories(cache); + if (!fs::IsDirectory(cache)) { if (error) - *error = "could not create Blender cache: " + fs_error.message(); + *error = base::String("could not create Blender cache: ") + ::strerror(errno); return false; } - const fs::path glb = cache / "scene.glb"; - const fs::path manifest = cache / "scene.rxblend"; - if (!options.force && fs::is_regular_file(glb) && - fs::file_size(glb, fs_error) > 0 && fs::is_regular_file(manifest)) { - out->glb_path = glb.string(); - out->manifest_path = manifest.string(); + const base::String glb = fs::Join(cache, "scene.glb"); + const base::String manifest = fs::Join(cache, "scene.rxblend"); + if (!options.force && NonEmptyFile(glb) && fs::IsRegularFile(manifest)) { + out->glb_path = glb; + out->manifest_path = manifest; out->reused_cache = true; return true; } - RX_INFO("blend: converting {} with {}", source.string(), - options.blender_executable); - const std::vector arguments = { - options.blender_executable, - "--background", - source.string(), - "--python", - script.string(), - "--", - "--output", - glb.string(), - "--manifest", - manifest.string(), - }; + RX_INFO("blend: converting {} with {}", source, options.blender_executable); + base::Vector arguments; + arguments.push_back(options.blender_executable); + arguments.push_back("--background"); + arguments.push_back(source); + arguments.push_back("--python"); + arguments.push_back(script); + arguments.push_back("--"); + arguments.push_back("--output"); + arguments.push_back(glb); + arguments.push_back("--manifest"); + arguments.push_back(manifest); const int result = Run(arguments); - if (result != 0 || !fs::is_regular_file(glb) || - fs::file_size(glb, fs_error) == 0) { - fs::remove(glb, fs_error); - fs::remove(manifest, fs_error); + if (result != 0 || !NonEmptyFile(glb)) { + fs::Remove(glb); + fs::Remove(manifest); if (error) *error = - "Blender conversion failed (exit " + std::to_string(result) + ")"; + "Blender conversion failed (exit " + rx::ToString(result) + ")"; return false; } - out->glb_path = glb.string(); - out->manifest_path = manifest.string(); + out->glb_path = glb; + out->manifest_path = manifest; return true; } diff --git a/engine/asset/blend_import.h b/engine/asset/blend_import.h index 5d479dca..1392a19f 100644 --- a/engine/asset/blend_import.h +++ b/engine/asset/blend_import.h @@ -1,26 +1,26 @@ #ifndef RX_ASSET_BLEND_IMPORT_H_ #define RX_ASSET_BLEND_IMPORT_H_ -#include +#include "base/strings/xstring.h" #include "core/export.h" namespace rx::asset { struct BlendImportOptions { - std::string blender_executable = "blender"; + base::String blender_executable = "blender"; // Absolute path to tools/blend_to_glb.py. Kept explicit so installed editors // can ship or replace the converter without baking a source-tree path into // the asset module. - std::string converter_script; + base::String converter_script; // Empty uses $XDG_CACHE_HOME/rx/blend (or the platform temp directory). - std::string cache_directory; + base::String cache_directory; bool force = false; }; struct BlendImportResult { - std::string glb_path; - std::string manifest_path; + base::String glb_path; + base::String manifest_path; bool reused_cache = false; }; @@ -28,10 +28,10 @@ struct BlendImportResult { // Blender in background mode. Arguments are passed without a shell. Cache keys // include source size/mtime and converter mtime, so changing either invalidates // stale output. Returns false when Blender is missing or conversion fails. -RX_ASSET_EXPORT bool ConvertBlendScene(const std::string &blend_path, +RX_ASSET_EXPORT bool ConvertBlendScene(const base::String &blend_path, const BlendImportOptions &options, BlendImportResult *out, - std::string *error = nullptr); + base::String *error = nullptr); } // namespace rx::asset diff --git a/engine/asset/engine_archives.cc b/engine/asset/engine_archives.cc index 26ceb27f..b0183baf 100644 --- a/engine/asset/engine_archives.cc +++ b/engine/asset/engine_archives.cc @@ -1,10 +1,13 @@ #include "asset/engine_archives.h" -#include #include #include "asset/pack.h" +#include "base/memory/move.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" #include "core/log.h" #include "core/paths.h" @@ -24,22 +27,21 @@ constexpr EngineArchive kEngineArchives[] = {{"rx_fonts.rxp", "fonts"}}; } // namespace -std::string FindEngineArchive(std::string_view file_name) { - std::error_code ec; - auto try_dir = [&](const std::filesystem::path& dir) { - const std::filesystem::path candidate = dir / file_name; - return std::filesystem::is_regular_file(candidate, ec) ? candidate.string() : std::string(); +base::String FindEngineArchive(base::StringRef file_name) { + auto try_dir = [&](base::StringRef dir) { + base::String candidate = fs::Join(dir, file_name); + return fs::IsRegularFile(candidate) ? candidate : base::String(); }; if (const char* dir = EngineArchivesDir.get(); dir != nullptr && *dir != '\0') { - if (std::string found = try_dir(dir); !found.empty()) return found; + if (base::String found = try_dir(dir); !found.empty()) return found; } - if (std::string found = try_dir("."); !found.empty()) return found; + if (base::String found = try_dir("."); !found.empty()) return found; // A shipped build carries the archives beside the executable, and is started // from wherever the launcher happens to be. - if (std::string found = try_dir(ExecutableDirectory()); !found.empty()) return found; + if (base::String found = try_dir(ExecutableDirectory()); !found.empty()) return found; #ifdef RX_ENGINE_ARCHIVES_DIR_DEFAULT - if (std::string found = try_dir(RX_ENGINE_ARCHIVES_DIR_DEFAULT); !found.empty()) return found; + if (base::String found = try_dir(RX_ENGINE_ARCHIVES_DIR_DEFAULT); !found.empty()) return found; #endif return {}; } @@ -47,7 +49,7 @@ std::string FindEngineArchive(std::string_view file_name) { size_t MountEngineArchives(Vfs& vfs) { size_t mounted = 0; for (const EngineArchive& archive : kEngineArchives) { - const std::string path = FindEngineArchive(archive.file_name); + const base::String path = FindEngineArchive(archive.file_name); if (path.empty()) { RX_WARN("engine archive {} not found (set RX_ENGINE_ARCHIVES)", archive.file_name); continue; @@ -57,7 +59,7 @@ size_t MountEngineArchives(Vfs& vfs) { RX_WARN("engine archive {} could not be opened", path); continue; } - vfs.Mount(archive.mount_point, std::move(provider)); + vfs.Mount(archive.mount_point, base::move(provider)); ++mounted; RX_INFO("mounted {} at {}://", path, archive.mount_point); } diff --git a/engine/asset/engine_archives.h b/engine/asset/engine_archives.h index bd205bc5..469d9d32 100644 --- a/engine/asset/engine_archives.h +++ b/engine/asset/engine_archives.h @@ -1,10 +1,10 @@ #ifndef RX_ASSET_ENGINE_ARCHIVES_H_ #define RX_ASSET_ENGINE_ARCHIVES_H_ -#include -#include #include "asset/vfs.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/types.h" @@ -20,7 +20,7 @@ namespace rx::asset { // Where MountEngineArchives looks, in order: RX_ENGINE_ARCHIVES if set, the // working directory, the directory the executable sits in, then the build // directory the engine was compiled in. -RX_ASSET_EXPORT std::string FindEngineArchive(std::string_view file_name); +RX_ASSET_EXPORT base::String FindEngineArchive(base::StringRef file_name); // Mounts every engine archive found. Returns how many mounted; 0 just means the // engine runs without its built-in content (the callers all degrade). diff --git a/engine/asset/gltf_loader.cc b/engine/asset/gltf_loader.cc index 21b4b112..7c61b433 100644 --- a/engine/asset/gltf_loader.cc +++ b/engine/asset/gltf_loader.cc @@ -1,16 +1,22 @@ #include "asset/gltf_loader.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include "asset/asset_id.h" #include "asset/texture_compress.h" +#include "base/memory/mem_ops.h" +#include "base/algorithm.h" +#include "base/memory/move.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/format.h" +#include "core/file_system.h" #include "core/log.h" #include "core/memory/memory_tracker.h" +#include "core/scalar.h" #define CGLTF_IMPLEMENTATION #include @@ -22,14 +28,14 @@ namespace rx::asset { namespace { -AssetId ScopedId(const std::string &path, const char *kind, size_t index) { - return MakeAssetId(path + "#" + kind + std::to_string(index)); +AssetId ScopedId(const base::String &path, const char *kind, size_t index) { + return MakeAssetId(path + "#" + kind + rx::ToString(index)); } // Decodes one glTF image to rgba8, from an external file, a GLB buffer view // or a base64 data uri. bool DecodeImage(const cgltf_image *image, - const std::filesystem::path &base_dir, Texture *out) { + base::StringRef base_dir, Texture *out) { stbi_uc *pixels = nullptr; int width = 0, height = 0, channels = 0; @@ -41,14 +47,14 @@ bool DecodeImage(const cgltf_image *image, stbi_load_from_memory(bytes, static_cast(image->buffer_view->size), &width, &height, &channels, 4); } else if (image->uri) { - if (std::strncmp(image->uri, "data:", 5) == 0) { - const char *comma = std::strchr(image->uri, ','); + if (::strncmp(image->uri, "data:", 5) == 0) { + const char *comma = ::strchr(image->uri, ','); if (!comma) return false; void *decoded = nullptr; cgltf_options options{}; // Base64 length to byte count: every 4 chars carry 3 bytes. - cgltf_size size = (std::strlen(comma + 1) / 4) * 3; + cgltf_size size = (::strlen(comma + 1) / 4) * 3; if (cgltf_load_buffer_base64(&options, size, comma + 1, &decoded) != cgltf_result_success) { return false; @@ -59,11 +65,11 @@ bool DecodeImage(const cgltf_image *image, CGLTF_FREE(decoded); } else { char decoded_uri[1024]; - std::strncpy(decoded_uri, image->uri, sizeof(decoded_uri) - 1); + ::strncpy(decoded_uri, image->uri, sizeof(decoded_uri) - 1); decoded_uri[sizeof(decoded_uri) - 1] = 0; cgltf_decode_uri(decoded_uri); - std::filesystem::path file = base_dir / decoded_uri; - pixels = stbi_load(file.string().c_str(), &width, &height, &channels, 4); + const base::String file = fs::Join(base_dir, decoded_uri); + pixels = stbi_load(file.c_str(), &width, &height, &channels, 4); } } if (!pixels) @@ -74,7 +80,7 @@ bool DecodeImage(const cgltf_image *image, out->height = static_cast(height); out->mip_count = 1; out->data.resize(static_cast(width) * height * 4); - std::memcpy(out->data.data(), pixels, out->data.size()); + base::MemCopy(out->data.data(), pixels, out->data.size()); stbi_image_free(pixels); return true; } @@ -105,7 +111,7 @@ void GenerateTangents(MeshLod *lod, u32 vertex_offset, u32 index_offset) { f32 du1 = v1.uv[0] - v0.uv[0], dv1 = v1.uv[1] - v0.uv[1]; f32 du2 = v2.uv[0] - v0.uv[0], dv2 = v2.uv[1] - v0.uv[1]; f32 det = du1 * dv2 - du2 * dv1; - if (std::abs(det) < 1e-12f) + if (::abs(det) < 1e-12f) continue; f32 inv = 1.0f / det; Vec3 tangent = (e1 * dv2 + e2 * -dv1) * inv; @@ -121,7 +127,7 @@ void GenerateTangents(MeshLod *lod, u32 vertex_offset, u32 index_offset) { Vec3 t = tangents[i] - n * Dot(n, tangents[i]); if (Dot(t, t) < 1e-12f) { // Degenerate uvs; any frame orthogonal to n will do. - t = std::abs(n.y) < 0.99f ? Cross(n, {0, 1, 0}) : Cross(n, {1, 0, 0}); + t = ::abs(n.y) < 0.99f ? Cross(n, {0, 1, 0}) : Cross(n, {1, 0, 0}); } t = Normalize(t); vertex.tangent[0] = t.x; @@ -142,25 +148,25 @@ void QuatFromMatrix(const f32 m[16], const Vec3 &scale, f32 out[4]) { } f32 trace = r[0] + r[4] + r[8]; if (trace > 0) { - f32 s = std::sqrt(trace + 1.0f) * 2; + f32 s = ::sqrt(trace + 1.0f) * 2; out[3] = 0.25f * s; out[0] = (r[7] - r[5]) / s; out[1] = (r[2] - r[6]) / s; out[2] = (r[3] - r[1]) / s; } else if (r[0] > r[4] && r[0] > r[8]) { - f32 s = std::sqrt(1.0f + r[0] - r[4] - r[8]) * 2; + f32 s = ::sqrt(1.0f + r[0] - r[4] - r[8]) * 2; out[3] = (r[7] - r[5]) / s; out[0] = 0.25f * s; out[1] = (r[1] + r[3]) / s; out[2] = (r[2] + r[6]) / s; } else if (r[4] > r[8]) { - f32 s = std::sqrt(1.0f + r[4] - r[0] - r[8]) * 2; + f32 s = ::sqrt(1.0f + r[4] - r[0] - r[8]) * 2; out[3] = (r[2] - r[6]) / s; out[0] = (r[1] + r[3]) / s; out[1] = 0.25f * s; out[2] = (r[5] + r[7]) / s; } else { - f32 s = std::sqrt(1.0f + r[8] - r[0] - r[4]) * 2; + f32 s = ::sqrt(1.0f + r[8] - r[0] - r[4]) * 2; out[3] = (r[3] - r[1]) / s; out[0] = (r[2] + r[6]) / s; out[1] = (r[5] + r[7]) / s; @@ -234,8 +240,8 @@ void MergeUvTransform(const cgltf_texture_view &view, const char *slot, // uv' = T * R * S * uv, spelt out from the extension's own sample shader. void ApplyUvTransform(const UvTransform &transform, f32 uv[2]) { - const f32 cos_r = std::cos(transform.rotation); - const f32 sin_r = std::sin(transform.rotation); + const f32 cos_r = ::cos(transform.rotation); + const f32 sin_r = ::sin(transform.rotation); const f32 u = uv[0] * transform.scale[0]; const f32 v = uv[1] * transform.scale[1]; uv[0] = cos_r * u - sin_r * v + transform.offset[0]; @@ -256,7 +262,7 @@ void Decompose(const Mat4 &matrix, Vec3 *translation, Quat *rotation, } // namespace -bool LoadGltfScene(const std::string &path, ImportedScene *out) { +bool LoadGltfScene(const base::String &path, ImportedScene *out) { static const mem::Category kAssetCategory = mem::RegisterCategory("assets"); mem::CategoryScope mem_scope(kAssetCategory); cgltf_options options{}; @@ -271,7 +277,7 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { cgltf_free(data); return false; } - std::filesystem::path base_dir = std::filesystem::path(path).parent_path(); + const base::String base_dir(fs::ParentPath(path)); // Base color and emissive sample as srgb, data maps stay linear. The same // walk records which slot each image is bound as, because that - not the @@ -320,7 +326,7 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { } else if (slots & kSlotColor) { role = TextureRole::kColor; } - CompressTexture(&texture, role, path + "#image" + std::to_string(i)); + CompressTexture(&texture, role, path + "#image" + rx::ToString(i)); } auto texture_id = [&](const cgltf_texture *texture) -> AssetId { @@ -359,7 +365,7 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { } if (src.has_pbr_metallic_roughness) { const auto &pbr = src.pbr_metallic_roughness; - std::memcpy(material.base_color_factor, pbr.base_color_factor, + base::MemCopy(material.base_color_factor, pbr.base_color_factor, sizeof(f32) * 4); material.metallic_factor = pbr.metallic_factor; material.roughness_factor = pbr.roughness_factor; @@ -369,7 +375,7 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { } material.normal = texture_id(src.normal_texture.texture); material.emissive = texture_id(src.emissive_texture.texture); - std::memcpy(material.emissive_factor, src.emissive_factor, sizeof(f32) * 3); + base::MemCopy(material.emissive_factor, src.emissive_factor, sizeof(f32) * 3); material.alpha_cutoff = src.alpha_cutoff; material.alpha_mode = src.alpha_mode == cgltf_alpha_mode_opaque ? AlphaMode::kOpaque @@ -389,7 +395,7 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { if (src.has_transmission) material.transmission = src.transmission.transmission_factor; if (src.has_sheen) { - std::memcpy(material.sheen_color, src.sheen.sheen_color_factor, + base::MemCopy(material.sheen_color, src.sheen.sheen_color_factor, sizeof(f32) * 3); material.sheen_roughness = src.sheen.sheen_roughness_factor; } @@ -398,29 +404,29 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { // holds for typical hanging drapery unwraps. if (src.name) { material.name = src.name; - std::string n = src.name; + base::String n = src.name; for (char &c : n) - c = static_cast(std::tolower(c)); - if (n.find("curtain") != std::string::npos || - n.find("banner") != std::string::npos || - n.find("flag") != std::string::npos || - n.find("cloth") != std::string::npos || - n.find("fabric") != std::string::npos || - n.find("drape") != std::string::npos) { + c = static_cast(::tolower(c)); + if (n.find("curtain") != base::String::npos || + n.find("banner") != base::String::npos || + n.find("flag") != base::String::npos || + n.find("cloth") != base::String::npos || + n.find("fabric") != base::String::npos || + n.find("drape") != base::String::npos) { material.wind = true; } // Water heuristic: glTF has no water extension either, so name-tag water // surfaces onto the dedicated water pipeline (waves, refraction, and the // sims/caustics that key off a water submesh being present). Whole-token // matches only, so "watermelon" and "waterfall" stay ordinary materials. - auto has_token = [&n](std::string_view token) { + auto has_token = [&n](base::StringRef token) { size_t pos = 0; - while ((pos = n.find(token, pos)) != std::string::npos) { + while ((pos = n.find(token, pos)) != base::String::npos) { bool starts = - pos == 0 || !std::isalpha(static_cast(n[pos - 1])); + pos == 0 || !::isalpha(static_cast(n[pos - 1])); size_t end = pos + token.size(); bool ends = end >= n.size() || - !std::isalpha(static_cast(n[end])); + !::isalpha(static_cast(n[end])); if (starts && ends) return true; ++pos; @@ -449,7 +455,7 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { binding.inverse_bind.resize(src.joints_count); base::Vector bind_model(src.joints_count); base::Vector source_parent(src.joints_count); - std::fill(source_parent.begin(), source_parent.end(), -1); + base::Fill(source_parent.begin(), source_parent.end(), -1); for (size_t joint = 0; joint < src.joints_count; ++joint) { Mat4 inverse_bind = Mat4::Identity(); const bool has_inverse_bind = @@ -457,7 +463,7 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { joint < src.inverse_bind_matrices->count; binding.bones.push_back(src.joints[joint]->name ? src.joints[joint]->name - : "joint_" + std::to_string(joint)); + : "joint_" + rx::ToString(joint)); if (has_inverse_bind) { cgltf_accessor_read_float(src.inverse_bind_matrices, joint, inverse_bind.m, 16); @@ -487,7 +493,7 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { // topological bone order. Mesh::skin remains in source palette order and // BuildBoneRemap connects the two by name. base::Vector source_to_bone(src.joints_count); - std::fill(source_to_bone.begin(), source_to_bone.end(), -1); + base::Fill(source_to_bone.begin(), source_to_bone.end(), -1); skeleton.bones.reserve(src.joints_count); while (skeleton.bones.size() < src.joints_count) { bool progressed = false; @@ -502,7 +508,7 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { Bone bone; bone.name = src.joints[joint]->name ? src.joints[joint]->name - : "joint_" + std::to_string(joint); + : "joint_" + rx::ToString(joint); bone.parent = source_parent_index >= 0 ? source_to_bone[static_cast(source_parent_index)] : -1; @@ -514,7 +520,7 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { Decompose(local, &bone.bind_translation, &bone.bind_rotation, &bone.bind_scale); source_to_bone[joint] = static_cast(skeleton.bones.size()); - skeleton.bones.push_back(std::move(bone)); + skeleton.bones.push_back(base::move(bone)); progressed = true; } if (progressed) @@ -529,7 +535,7 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { // first binding for compatibility with mesh-only consumers; ImportedScene users // use Instance::skeleton_index with skin_bindings for the exact binding. base::Vector mesh_skin(data->meshes_count); - std::fill(mesh_skin.begin(), mesh_skin.end(), -1); + base::Fill(mesh_skin.begin(), mesh_skin.end(), -1); for (size_t node_index = 0; node_index < data->nodes_count; ++node_index) { const cgltf_node &node = data->nodes[node_index]; if (!node.mesh || !node.skin) @@ -608,13 +614,13 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { ReadFloats(position, 3, &scratch); for (u32 v = 0; v < vertex_count; ++v) { - std::memcpy(lod.vertices[vertex_offset + v].position, &scratch[v * 3], + base::MemCopy(lod.vertices[vertex_offset + v].position, &scratch[v * 3], sizeof(f32) * 3); } if (normal && normal->count == vertex_count) { ReadFloats(normal, 3, &scratch); for (u32 v = 0; v < vertex_count; ++v) { - std::memcpy(lod.vertices[vertex_offset + v].normal, &scratch[v * 3], + base::MemCopy(lod.vertices[vertex_offset + v].normal, &scratch[v * 3], sizeof(f32) * 3); } } else { @@ -630,7 +636,7 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { ReadFloats(uv, 2, &scratch); for (u32 v = 0; v < vertex_count; ++v) { Vertex &vertex = lod.vertices[vertex_offset + v]; - std::memcpy(vertex.uv, &scratch[v * 2], sizeof(f32) * 2); + base::MemCopy(vertex.uv, &scratch[v * 2], sizeof(f32) * 2); // Safe per primitive because this loader appends a fresh vertex range // per primitive, so no two materials ever share a vertex to disagree // over. Before the tangent generation below, which derives the frame @@ -648,7 +654,7 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { f32 b = components > 2 ? scratch[v * components + 2] : r; f32 a = components > 3 ? scratch[v * components + 3] : 1.0f; auto to_u8 = [](f32 value) { - return static_cast(std::clamp(value, 0.0f, 1.0f) * 255.0f + + return static_cast(rx::Clamp(value, 0.0f, 1.0f) * 255.0f + 0.5f); }; lod.vertices[vertex_offset + v].color = @@ -665,20 +671,20 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { SkinnedVertexExtra &extra = lod.skinning[vertex_offset + v]; f32 total = 0; for (f32 weight : source_weights) - total += std::max(weight, 0.0f); + total += rx::Max(weight, 0.0f); u32 quantized = 0; for (u32 lane = 0; lane < 4; ++lane) { extra.bone_indices[lane] = source_joints[lane] < 256 ? static_cast(source_joints[lane]) : 0; const f32 normalized = - total > 1e-8f ? std::max(source_weights[lane], 0.0f) / total + total > 1e-8f ? rx::Max(source_weights[lane], 0.0f) / total : (lane == 0 ? 1.0f : 0.0f); const u32 byte_weight = - lane == 3 ? 255 - std::min(quantized, 255u) + lane == 3 ? 255 - rx::Min(quantized, 255u) : static_cast(normalized * 255.0f + 0.5f); extra.bone_weights[lane] = - static_cast(std::min(byte_weight, 255u)); + static_cast(rx::Min(byte_weight, 255u)); quantized += extra.bone_weights[lane]; } } @@ -712,7 +718,7 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { } ReadFloats(tangent, 4, &scratch); for (u32 v = 0; v < vertex_count; ++v) { - std::memcpy(lod.vertices[vertex_offset + v].tangent, &scratch[v * 4], + base::MemCopy(lod.vertices[vertex_offset + v].tangent, &scratch[v * 4], sizeof(f32) * 4); } } else { @@ -749,7 +755,7 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { ReadFloats(attribute.data, 3, &scratch); deltas->resize((static_cast(vertex_offset) + vertex_count) * 3); - std::memcpy(deltas->data() + static_cast(vertex_offset) * 3, + base::MemCopy(deltas->data() + static_cast(vertex_offset) * 3, scratch.data(), vertex_count * sizeof(f32) * 3); } } @@ -799,9 +805,9 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { f32 dx = vertex.position[0] - mesh.bounds_center[0]; f32 dy = vertex.position[1] - mesh.bounds_center[1]; f32 dz = vertex.position[2] - mesh.bounds_center[2]; - radius_sq = std::max(radius_sq, dx * dx + dy * dy + dz * dz); + radius_sq = rx::Max(radius_sq, dx * dx + dy * dy + dz * dz); } - mesh.bounds_radius = std::sqrt(radius_sq); + mesh.bounds_radius = ::sqrt(radius_sq); } } @@ -838,9 +844,9 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { track.weights.resize(track.times.size() * targets); for (size_t k = 0; k < track.times.size(); ++k) { const f32 *row = &scratch[k * stride + (cubic ? targets : 0)]; - std::memcpy(&track.weights[k * targets], row, targets * sizeof(f32)); + base::MemCopy(&track.weights[k * targets], row, targets * sizeof(f32)); } - mesh.morph_animations.push_back(std::move(track)); + mesh.morph_animations.push_back(base::move(track)); } } @@ -856,11 +862,11 @@ bool LoadGltfScene(const std::string &path, ImportedScene *out) { f32 world[16]; cgltf_node_transform_world(&node, world); instance.position = {world[12], world[13], world[14]}; - Vec3 scale{std::sqrt(world[0] * world[0] + world[1] * world[1] + + Vec3 scale{::sqrt(world[0] * world[0] + world[1] * world[1] + world[2] * world[2]), - std::sqrt(world[4] * world[4] + world[5] * world[5] + + ::sqrt(world[4] * world[4] + world[5] * world[5] + world[6] * world[6]), - std::sqrt(world[8] * world[8] + world[9] * world[9] + + ::sqrt(world[8] * world[8] + world[9] * world[9] + world[10] * world[10])}; instance.scale = (scale.x + scale.y + scale.z) / 3.0f; QuatFromMatrix(world, scale, instance.rotation); diff --git a/engine/asset/gltf_loader.h b/engine/asset/gltf_loader.h index 908dd00e..d5799082 100644 --- a/engine/asset/gltf_loader.h +++ b/engine/asset/gltf_loader.h @@ -1,9 +1,9 @@ #ifndef RX_ASSET_GLTF_LOADER_H_ #define RX_ASSET_GLTF_LOADER_H_ -#include #include "asset/scene_import.h" +#include "base/strings/xstring.h" #include "core/export.h" namespace rx::asset { @@ -12,7 +12,7 @@ namespace rx::asset { // images. Skinned mesh-node transforms are ignored as required by glTF. // Generates tangents from uv derivatives when the source has none. Returns // false and logs on malformed input. -RX_ASSET_EXPORT bool LoadGltfScene(const std::string &path, ImportedScene *out); +RX_ASSET_EXPORT bool LoadGltfScene(const base::String &path, ImportedScene *out); } // namespace rx::asset diff --git a/engine/asset/image_file.cc b/engine/asset/image_file.cc index e9551102..96eca406 100644 --- a/engine/asset/image_file.cc +++ b/engine/asset/image_file.cc @@ -1,7 +1,9 @@ #include "asset/image_file.h" +#include "base/memory/mem_ops.h" +#include "base/strings/xstring.h" +#include "core/format.h" -#include -#include +#include // The implementation lives in third_party/stb_impl.c (rx::stb_impl); this is // only the declarations. @@ -9,7 +11,7 @@ namespace rx::asset { -std::string ImageFileProblem(const std::string& path) { +base::String ImageFileProblem(const base::String& path) { if (path.empty()) return "is empty; there is nothing to load"; int width = 0; int height = 0; @@ -18,11 +20,11 @@ std::string ImageFileProblem(const std::string& path) { // stb's reason separates "fopen failed" from a decode it refuses, which is // the difference between a mistyped path and a format this build cannot read. const char* reason = stbi_failure_reason(); - return std::format("does not read as an image ({}); the path is relative to the working " + return rx::StrFormat("does not read as an image ({}); the path is relative to the working " "directory", reason ? reason : "unknown"); } -bool LoadImageFile(const std::string& path, bool srgb, AssetId id, Texture* out) { +bool LoadImageFile(const base::String& path, bool srgb, AssetId id, Texture* out) { int width = 0; int height = 0; int channels = 0; @@ -36,7 +38,7 @@ bool LoadImageFile(const std::string& path, bool srgb, AssetId id, Texture* out) out->array_layers = 1; out->is_srgb = srgb; out->data.resize(static_cast(width) * height * 4); - std::memcpy(out->data.data(), pixels, out->data.size()); + base::MemCopy(out->data.data(), pixels, out->data.size()); stbi_image_free(pixels); return true; } diff --git a/engine/asset/image_file.h b/engine/asset/image_file.h index b220b467..d4f8b65d 100644 --- a/engine/asset/image_file.h +++ b/engine/asset/image_file.h @@ -1,10 +1,10 @@ #ifndef RX_ASSET_IMAGE_FILE_H_ #define RX_ASSET_IMAGE_FILE_H_ -#include #include "asset/asset_id.h" #include "asset/texture.h" +#include "base/strings/xstring.h" #include "core/export.h" namespace rx::asset { @@ -23,14 +23,14 @@ namespace rx::asset { // header, so a --validate sweep over a scene full of 4K maps costs no decode. // LoadImageFile goes through the same reader, so a path this accepts is one // that loads. -RX_ASSET_EXPORT std::string ImageFileProblem(const std::string& path); +RX_ASSET_EXPORT base::String ImageFileProblem(const base::String& path); // Decodes to an opaque rgba8 Texture with a single mip (the material system // generates the chain at upload). `srgb` tags the result for the colour slots // (base colour, emissive); data maps - normal, roughness, metallic, occlusion - // must pass false, or the gpu linearizes values that were never encoded and the // surface comes back visibly too smooth and too flat. -RX_ASSET_EXPORT bool LoadImageFile(const std::string& path, bool srgb, AssetId id, Texture* out); +RX_ASSET_EXPORT bool LoadImageFile(const base::String& path, bool srgb, AssetId id, Texture* out); } // namespace rx::asset diff --git a/engine/asset/material.h b/engine/asset/material.h index 7dbd5f82..5994e845 100644 --- a/engine/asset/material.h +++ b/engine/asset/material.h @@ -1,9 +1,9 @@ #ifndef RX_ASSET_MATERIAL_H_ #define RX_ASSET_MATERIAL_H_ -#include #include "asset/asset_id.h" +#include "base/strings/xstring.h" #include "core/types.h" namespace rx::asset { @@ -19,7 +19,7 @@ struct Material { // heuristics below, but a game routing submeshes to its own shading model // has nothing else to key on: the AssetId is a hash and does not invert. // Empty when the source is unnamed. - std::string name; + base::String name; AssetId base_color; AssetId normal; // Model-space normal map (_msn): the map stores the surface normal in the diff --git a/engine/asset/materialx.cc b/engine/asset/materialx.cc index 312b1783..1f3ed973 100644 --- a/engine/asset/materialx.cc +++ b/engine/asset/materialx.cc @@ -1,11 +1,11 @@ #include "asset/materialx.h" -#include -#include -#include -#include +#include "base/containers/vector.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" #include "core/log.h" +#include "core/text_reader.h" namespace rx::asset { namespace { @@ -14,45 +14,45 @@ namespace { // start at a word boundary: `name="` also occurs inside `nodename="`, and a // document that writes the connection first (which is the order MaterialX // exporters emit) then hands every input the name of the node it connects to. -std::string Attr(const std::string& tag, const char* key) { - const std::string pat = std::string(key) + "=\""; +base::String Attr(const base::String& tag, const char* key) { + const base::String pat = base::String(key) + "=\""; size_t p = 0; - while ((p = tag.find(pat, p)) != std::string::npos) { + while ((p = tag.find(pat, p)) != base::String::npos) { const char before = p == 0 ? '<' : tag[p - 1]; p += pat.size(); if (before != ' ' && before != '\t' && before != '\n' && before != '\r' && before != '<') { continue; } const size_t e = tag.find('"', p); - return e == std::string::npos ? std::string() : tag.substr(p, e - p); + return e == base::String::npos ? base::String() : tag.substr(p, e - p); } return ""; } // MaterialX vector/color values are comma separated; parse up to n floats. -void ParseFloats(const std::string& value, f32* out, int n) { - std::string s = value; +void ParseFloats(const base::String& value, f32* out, int n) { + base::String s = value; for (char& c : s) { if (c == ',') c = ' '; } - std::istringstream ss(s); + TokenReader ss(s); f32 v; - for (int i = 0; i < n && (ss >> v); ++i) out[i] = v; + for (int i = 0; i < n && ss.Next(&v); ++i) out[i] = v; } struct Input { - std::string name; - std::string value; // a constant - std::string nodename; // a connection to another node in the same scope - std::string nodegraph; // a connection into a nodegraph's output - std::string output; // which output of that nodegraph + base::String name; + base::String value; // a constant + base::String nodename; // a connection to another node in the same scope + base::String nodegraph; // a connection into a nodegraph's output + base::String output; // which output of that nodegraph }; struct Node { - std::string category; // "tiledimage", "normalmap", "open_pbr_surface", "output", ... - std::string name; - std::string nodename; // an element's source node - std::vector inputs; + base::String category; // "tiledimage", "normalmap", "open_pbr_surface", "output", ... + base::String name; + base::String nodename; // an element's source node + base::Vector inputs; }; // The document as a flat list of elements, which is all this needs: MaterialX @@ -61,24 +61,24 @@ struct Node { // ambientCG's 1.39 exporter emits) carries no information a lookup by name does // not already have. elements attach to the last element that opened and // did not close itself, which is the node they belong to under either layout. -std::vector ScanElements(const std::string& doc) { - std::vector nodes; +base::Vector ScanElements(const base::String& doc) { + base::Vector nodes; size_t p = 0; - size_t current = std::string::npos; // an index, since push_back moves the storage - while ((p = doc.find('<', p)) != std::string::npos) { + size_t current = base::String::npos; // an index, since push_back moves the storage + while ((p = doc.find('<', p)) != base::String::npos) { const size_t end = doc.find('>', p); - if (end == std::string::npos) break; - const std::string tag = doc.substr(p, end - p); + if (end == base::String::npos) break; + const base::String tag = doc.substr(p, end - p); p = end + 1; if (tag.size() < 2 || tag[1] == '/' || tag[1] == '?' || tag[1] == '!') continue; size_t name_end = tag.find_first_of(" \t\r\n", 1); - if (name_end == std::string::npos) name_end = tag.size(); - const std::string category = tag.substr(1, name_end - 1); + if (name_end == base::String::npos) name_end = tag.size(); + const base::String category = tag.substr(1, name_end - 1); const bool self_closing = tag.back() == '/'; if (category == "input") { - if (current == std::string::npos) continue; + if (current == base::String::npos) continue; nodes[current].inputs.push_back({Attr(tag, "name"), Attr(tag, "value"), Attr(tag, "nodename"), Attr(tag, "nodegraph"), Attr(tag, "output")}); @@ -92,7 +92,7 @@ std::vector ScanElements(const std::string& doc) { return nodes; } -const Node* FindNode(const std::vector& nodes, const std::string& name) { +const Node* FindNode(const base::Vector& nodes, const base::String& name) { if (name.empty()) return nullptr; for (const Node& node : nodes) { if (node.name == name) return &node; @@ -113,7 +113,7 @@ const Input* FindInput(const Node& node, const char* name) { // feeding it. Anything else is a graph this build does not evaluate, and saying // which node it gave up on is the difference between a fixable document and a // material that came back flat for no stated reason. -std::string ResolveImage(const std::vector& nodes, const Input& input, std::string* why) { +base::String ResolveImage(const base::Vector& nodes, const Input& input, base::String* why) { const Node* node = FindNode(nodes, input.nodename); if (!node && !input.nodegraph.empty()) { // : the graph's element @@ -152,12 +152,11 @@ std::string ResolveImage(const std::vector& nodes, const Input& input, std } // A document-relative filename made absolute against the document's directory. -std::string ResolveAgainstDocument(const std::string& document, const std::string& file) { - const std::filesystem::path relative(file); - if (file.empty() || relative.is_absolute()) return file; - const std::filesystem::path dir = std::filesystem::path(document).parent_path(); +base::String ResolveAgainstDocument(const base::String& document, const base::String& file) { + if (file.empty() || fs::IsAbsolute(file)) return file; + const base::StringRef dir = fs::ParentPath(document); if (dir.empty()) return file; - return (dir / relative).lexically_normal().string(); + return fs::LexicallyNormal(fs::Join(dir, file)); } // standard_surface and open_pbr_surface name the same lobes differently, and a @@ -183,7 +182,7 @@ constexpr InputAlias kInputAliases[] = { {"geometry_normal", "normal"}, }; -std::string Canonical(const std::string& name) { +base::String Canonical(const base::String& name) { for (const InputAlias& alias : kInputAliases) { if (name == alias.name) return alias.canonical; } @@ -191,7 +190,7 @@ std::string Canonical(const std::string& name) { } // Which map slot a surface input fills when it is connected to an image. -auto MapSlot(const std::string& canonical) -> std::string MaterialXMaps::* { +auto MapSlot(const base::String& canonical) -> base::String MaterialXMaps::* { if (canonical == "base_color") return &MaterialXMaps::base_color; if (canonical == "normal") return &MaterialXMaps::normal; if (canonical == "specular_roughness") return &MaterialXMaps::roughness; @@ -203,31 +202,28 @@ auto MapSlot(const std::string& canonical) -> std::string MaterialXMaps::* { } // namespace -bool LoadMaterialX(const std::string& path, Material* out, MaterialXMaps* maps) { - std::ifstream file(path); - if (!file) { +bool LoadMaterialX(const base::String& path, Material* out, MaterialXMaps* maps) { + base::String doc; + if (!fs::ReadTextFile(path, &doc)) { RX_WARN("materialx: cannot open {}", path); return false; } - std::stringstream buf; - buf << file.rdbuf(); - const std::string doc = buf.str(); // MaterialX lets a document prefix every filename it names. "./" is the no-op // ambientCG writes and the only one honoured; anything else would put the // maps somewhere the resolution below does not look, and losing a whole // texture set to one unread attribute is worth saying out loud. - if (const size_t root = doc.find("', root); - const std::string prefix = - Attr(doc.substr(root, end == std::string::npos ? end : end - root), "fileprefix"); + const base::String prefix = + Attr(doc.substr(root, end == base::String::npos ? end : end - root), "fileprefix"); if (!prefix.empty() && prefix != "./") { RX_WARN("materialx: {}: fileprefix=\"{}\" is not applied; filenames resolve against the " "document's own directory", path, prefix); } } - const std::vector nodes = ScanElements(doc); + const base::Vector nodes = ScanElements(doc); const Node* surface = nullptr; for (const Node& node : nodes) { if (node.category == "standard_surface" || node.category == "open_pbr_surface") { @@ -256,15 +252,15 @@ bool LoadMaterialX(const std::string& path, Material* out, MaterialXMaps* maps) f32 opacity = 1.0f; for (const Input& input : surface->inputs) { - const std::string name = Canonical(input.name); + const base::String name = Canonical(input.name); if (input.value.empty()) { // A connected input. Resolving it to a texture is the whole point of // pointing rx at a library document; one this build cannot follow is // named rather than dropped in silence, since the render it produces // (a flat colour) looks exactly like a material authored that way. - std::string why; - const std::string image = ResolveImage(nodes, input, &why); - std::string MaterialXMaps::*slot = MapSlot(name); + base::String why; + const base::String image = ResolveImage(nodes, input, &why); + base::String MaterialXMaps::*slot = MapSlot(name); if (image.empty()) { RX_WARN("materialx: {}: input '{}' {}; that map is DROPPED", path, input.name, why); } else if (!slot) { diff --git a/engine/asset/materialx.h b/engine/asset/materialx.h index f88f4c1e..241ca11b 100644 --- a/engine/asset/materialx.h +++ b/engine/asset/materialx.h @@ -1,9 +1,9 @@ #ifndef RX_ASSET_MATERIALX_H_ #define RX_ASSET_MATERIALX_H_ -#include #include "asset/material.h" +#include "base/strings/xstring.h" #include "core/export.h" namespace rx::asset { @@ -17,12 +17,12 @@ namespace rx::asset { // LoadMaterialX only reports the files; opening them is the caller's, since // publishing a texture needs a database and a gpu this module has neither of. struct MaterialXMaps { - std::string base_color; - std::string normal; - std::string roughness; - std::string metallic; - std::string occlusion; - std::string emissive; + base::String base_color; + base::String normal; + base::String roughness; + base::String metallic; + base::String occlusion; + base::String emissive; }; // Loads the first standard_surface or open_pbr_surface node of a MaterialX @@ -45,7 +45,7 @@ struct MaterialXMaps { // // False on a read/parse error or a document with no surface shader; `out` keeps // its defaults for whatever the document omits. -RX_ASSET_EXPORT bool LoadMaterialX(const std::string& path, Material* out, +RX_ASSET_EXPORT bool LoadMaterialX(const base::String& path, Material* out, MaterialXMaps* maps = nullptr); } // namespace rx::asset diff --git a/engine/asset/mesh.h b/engine/asset/mesh.h index 07d9c2b0..429bde12 100644 --- a/engine/asset/mesh.h +++ b/engine/asset/mesh.h @@ -1,12 +1,12 @@ #ifndef RX_ASSET_MESH_H_ #define RX_ASSET_MESH_H_ -#include #include #include "asset/asset_id.h" #include "asset/skeleton.h" +#include "base/strings/xstring.h" #include "core/types.h" namespace rx::asset { @@ -32,7 +32,7 @@ struct SkinnedVertexExtra { // Position deltas are always present; normal/tangent deltas are empty when the // source carries none. Deltas apply before skinning, in mesh space. struct MorphTarget { - std::string name; // source target name (e.g. "jawOpen"), empty if unnamed + base::String name; // source target name (e.g. "jawOpen"), empty if unnamed u64 name_hash = 0; // MakeAssetId(name).hash, 0 when unnamed base::Vector position_deltas; // 3 floats per lod 0 vertex base::Vector normal_deltas; // 3 floats per vertex, or empty @@ -42,7 +42,7 @@ struct MorphTarget { // Keyframed weight animation over a mesh's morph targets (a glTF "weights" // channel). weights holds one row of morph_targets.size() values per key. struct MorphAnimation { - std::string name; + base::String name; f32 duration = 0; // seconds, last key time bool step = false; // hold each key instead of lerping (STEP interpolation) base::Vector times; diff --git a/engine/asset/pack.cc b/engine/asset/pack.cc index ff14e346..38bbbc6e 100644 --- a/engine/asset/pack.cc +++ b/engine/asset/pack.cc @@ -1,11 +1,19 @@ #include "asset/pack.h" -#include -#include +#include #include #include "asset/asset_id.h" +#include "base/algorithm.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/optional.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "base/threading/lock_guard.h" +#include "core/file_system.h" +#include "core/scalar.h" namespace rx::asset { @@ -49,15 +57,16 @@ u64 AlignUp(u64 value, u64 alignment) { return (value + alignment - 1) & ~(align // read side -base::UniquePointer PackFile::Open(std::string archive_path) { +base::UniquePointer PackFile::Open(base::String archive_path) { base::UniquePointer pack(new PackFile()); - pack->path_ = std::move(archive_path); - pack->file_.open(pack->path_, std::ios::binary); - if (!pack->file_) return nullptr; + pack->path_ = base::move(archive_path); + pack->file_ = fs::OpenFile(pack->path_, base::File::FLAG_OPEN | base::File::FLAG_READ); + if (!pack->file_.IsValid()) return nullptr; PackHeader header{}; - if (!pack->file_.read(reinterpret_cast(&header), sizeof(header))) return nullptr; - if (std::memcmp(header.magic, kMagic, sizeof(kMagic)) != 0) return nullptr; + if (!fs::ReadAt(pack->file_, 0, base::Span(reinterpret_cast(&header), sizeof(header)))) + return nullptr; + if (base::MemCompare(header.magic, kMagic, sizeof(kMagic)) != 0) return nullptr; if (header.version != kVersion) return nullptr; const u64 toc_size = u64{header.entry_count} * sizeof(PackEntry); @@ -65,11 +74,12 @@ base::UniquePointer PackFile::Open(std::string archive_path) { if (header.data_offset < header.names_offset + header.names_size) return nullptr; pack->toc_.resize(toc_size); - if (!pack->file_.read(reinterpret_cast(pack->toc_.data()), - static_cast(toc_size))) + if (!fs::ReadAt(pack->file_, sizeof(PackHeader), base::Span(pack->toc_.data(), toc_size))) return nullptr; pack->names_.resize(header.names_size); - if (!pack->file_.read(pack->names_.data(), static_cast(header.names_size))) + if (!fs::ReadAt( + pack->file_, header.names_offset, + base::Span(reinterpret_cast(pack->names_.data()), header.names_size))) return nullptr; mz_ulong crc = mz_crc32(MZ_CRC32_INIT, pack->toc_.data(), pack->toc_.size()); @@ -91,7 +101,7 @@ PackEntryView PackFile::entry(size_t index) const { const PackEntry* entries = reinterpret_cast(toc_.data()); const PackEntry& e = entries[index]; return PackEntryView{ - .path = std::string_view(&names_[e.name_offset]), + .path = base::StringRef(&names_[e.name_offset]), .size = e.size, .stored_size = e.stored_size, .compression = static_cast(e.compression), @@ -99,39 +109,36 @@ PackEntryView PackFile::entry(size_t index) const { }; } -std::optional PackFile::Find(std::string_view normalized_path) const { +base::Optional PackFile::Find(base::StringRef normalized_path) const { const u64 hash = Fnv1a(normalized_path); const PackEntry* entries = reinterpret_cast(toc_.data()); const size_t count = entry_count(); - const PackEntry* it = std::lower_bound( + const PackEntry* it = base::LowerBound( entries, entries + count, hash, [](const PackEntry& e, u64 value) { return e.path_hash < value; }); for (; it != entries + count && it->path_hash == hash; ++it) { - if (std::string_view(&names_[it->name_offset]) == normalized_path) + if (base::StringRef(&names_[it->name_offset]) == normalized_path) return static_cast(it - entries); } - return std::nullopt; + return base::nullopt; } -std::optional> PackFile::ReadEntry(size_t index) const { +base::Optional> PackFile::ReadEntry(size_t index) const { const PackEntry* entries = reinterpret_cast(toc_.data()); const PackEntry& e = entries[index]; base::Vector stored(e.stored_size); { - std::scoped_lock lock(io_mutex_); - file_.clear(); - file_.seekg(static_cast(e.offset)); + base::LockGuard lock(io_mutex_); if (e.stored_size > 0 && - !file_.read(reinterpret_cast(stored.data()), - static_cast(e.stored_size))) - return std::nullopt; + !fs::ReadAt(file_, e.offset, base::Span(stored.data(), e.stored_size))) + return base::nullopt; } base::Vector out; switch (static_cast(e.compression)) { case PackCompression::kStore: - out = std::move(stored); + out = base::move(stored); break; case PackCompression::kDeflate: { out.resize(e.size); @@ -139,23 +146,23 @@ std::optional> PackFile::ReadEntry(size_t index) const { if (mz_uncompress(out.data(), &out_size, stored.data(), static_cast(stored.size())) != MZ_OK || out_size != e.size) - return std::nullopt; + return base::nullopt; break; } default: - return std::nullopt; + return base::nullopt; } - if (out.size() != e.size) return std::nullopt; - if (Crc32(out.data(), out.size()) != e.crc32) return std::nullopt; + if (out.size() != e.size) return base::nullopt; + if (Crc32(out.data(), out.size()) != e.crc32) return base::nullopt; return out; } // write side -void PackWriter::Add(std::string_view virtual_path, base::Vector bytes, +void PackWriter::Add(base::StringRef virtual_path, base::Vector bytes, PackCompression compression) { - std::string normalized = NormalizePath(virtual_path); + base::String normalized = NormalizePath(virtual_path); const u64 hash = Fnv1a(normalized); base::Vector* staged = staged_.find(hash); if (staged != nullptr) { @@ -164,7 +171,7 @@ void PackWriter::Add(std::string_view virtual_path, base::Vector bytes, for (u32 index : *staged) { Pending& pending = pending_[index]; if (pending.path != normalized) continue; - pending.bytes = std::move(bytes); + pending.bytes = base::move(bytes); pending.compression = compression; return; } @@ -173,10 +180,10 @@ void PackWriter::Add(std::string_view virtual_path, base::Vector bytes, staged = staged_.find(hash); } staged->push_back(static_cast(pending_.size())); - pending_.push_back(Pending{std::move(normalized), std::move(bytes), compression}); + pending_.push_back(Pending{base::move(normalized), base::move(bytes), compression}); } -bool PackWriter::WriteTo(const std::string& file_path) { +bool PackWriter::WriteTo(const base::String& file_path) { struct Staged { const Pending* source; u64 hash; @@ -197,14 +204,16 @@ bool PackWriter::WriteTo(const std::string& file_path) { static_cast(pending.bytes.size()), compression_level_) == MZ_OK && compressed_size < pending.bytes.size()) { compressed.resize(compressed_size); - s.compressed = std::move(compressed); + s.compressed = base::move(compressed); s.method = PackCompression::kDeflate; } } - staged.push_back(std::move(s)); + staged.push_back(base::move(s)); } - std::sort(staged.begin(), staged.end(), [](const Staged& a, const Staged& b) { + // (hash, path) is unique, since Add replaces a repeated path, so any + // correct sort gives this order. + base::Sort(staged.data(), staged.data() + staged.size(), [](const Staged& a, const Staged& b) { if (a.hash != b.hash) return a.hash < b.hash; return a.source->path < b.source->path; }); @@ -226,7 +235,7 @@ bool PackWriter::WriteTo(const std::string& file_path) { } PackHeader header{}; - std::memcpy(header.magic, kMagic, sizeof(kMagic)); + base::MemCopy(header.magic, kMagic, sizeof(kMagic)); header.version = kVersion; header.entry_count = static_cast(entries.size()); header.names_offset = sizeof(PackHeader) + entries.size() * sizeof(PackEntry); @@ -244,25 +253,33 @@ bool PackWriter::WriteTo(const std::string& file_path) { toc_crc = mz_crc32(toc_crc, reinterpret_cast(names.data()), names.size()); header.toc_crc32 = static_cast(toc_crc); - std::ofstream out(file_path, std::ios::binary | std::ios::trunc); - if (!out) return false; - out.write(reinterpret_cast(&header), sizeof(header)); - out.write(reinterpret_cast(entries.data()), - static_cast(entries.size() * sizeof(PackEntry))); - out.write(names.data(), static_cast(names.size())); - + base::File out = + fs::OpenFile(file_path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); + if (!out.IsValid()) return false; + bool ok = fs::WriteAll(out, + base::Span(reinterpret_cast(&header), sizeof(header))); + ok = ok && fs::WriteAll(out, base::Span( + reinterpret_cast(entries.data()), entries.size() * sizeof(PackEntry))); + ok = ok && fs::WriteAll(out, + base::Span(reinterpret_cast(names.data()), names.size())); + + static const u8 kZeros[kPayloadAlignment] = {}; u64 written = header.names_offset + header.names_size; - for (size_t i = 0; i < staged.size(); ++i) { + for (size_t i = 0; ok && i < staged.size(); ++i) { const Staged& s = staged[i]; const PackEntry& e = entries[i]; - for (; written < e.offset; ++written) out.put('\0'); + while (ok && written < e.offset) { + const u64 pad = rx::Min(e.offset - written, sizeof(kZeros)); + ok = fs::WriteAll(out, base::Span(kZeros, pad)); + written += pad; + } const u8* payload = s.method == PackCompression::kStore ? s.source->bytes.data() : s.compressed.data(); - out.write(reinterpret_cast(payload), static_cast(e.stored_size)); + ok = ok && (e.stored_size == 0 || + fs::WriteAll(out, base::Span(payload, e.stored_size))); written += e.stored_size; } - out.flush(); - return static_cast(out); + return ok; } // Vfs provider @@ -271,29 +288,29 @@ namespace { class PackFileProvider final : public FileProvider { public: - explicit PackFileProvider(base::UniquePointer pack) : pack_(std::move(pack)) {} + explicit PackFileProvider(base::UniquePointer pack) : pack_(base::move(pack)) {} - bool Contains(std::string_view normalized_path) const override { + bool Contains(base::StringRef normalized_path) const override { return pack_->Find(normalized_path).has_value(); } - std::optional> Read(std::string_view normalized_path) const override { - const std::optional index = pack_->Find(normalized_path); - if (!index) return std::nullopt; + base::Optional> Read(base::StringRef normalized_path) const override { + const base::Optional index = pack_->Find(normalized_path); + if (!index) return base::nullopt; return pack_->ReadEntry(*index); } - std::optional Size(std::string_view normalized_path) const override { - const std::optional index = pack_->Find(normalized_path); - if (!index) return std::nullopt; + base::Optional Size(base::StringRef normalized_path) const override { + const base::Optional index = pack_->Find(normalized_path); + if (!index) return base::nullopt; return pack_->entry(*index).size; } - void Enumerate(const std::function& fn) const override { + void Enumerate(base::FunctionRef fn) const override { for (size_t i = 0; i < pack_->entry_count(); ++i) fn(pack_->entry(i).path); } - std::string name() const override { return pack_->path(); } + base::String name() const override { return pack_->path(); } private: base::UniquePointer pack_; @@ -301,10 +318,10 @@ class PackFileProvider final : public FileProvider { } // namespace -base::UniquePointer MakePackFileProvider(std::string archive_path) { - base::UniquePointer pack = PackFile::Open(std::move(archive_path)); +base::UniquePointer MakePackFileProvider(base::String archive_path) { + base::UniquePointer pack = PackFile::Open(base::move(archive_path)); if (!pack) return nullptr; - return base::MakeUnique(std::move(pack)); + return base::MakeUnique(base::move(pack)); } } // namespace rx::asset diff --git a/engine/asset/pack.h b/engine/asset/pack.h index 0e5111ad..1b8e036d 100644 --- a/engine/asset/pack.h +++ b/engine/asset/pack.h @@ -1,17 +1,17 @@ #ifndef RX_ASSET_PACK_H_ #define RX_ASSET_PACK_H_ -#include -#include -#include -#include -#include #include #include #include #include "asset/vfs.h" +#include "base/filesystem/file.h" +#include "base/optional.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "base/threading/mutex.h" #include "core/export.h" #include "core/types.h" @@ -37,7 +37,7 @@ enum class PackCompression : u8 { }; struct PackEntryView { - std::string_view path; // normalized virtual path + base::StringRef path; // normalized virtual path u64 size = 0; // uncompressed u64 stored_size = 0; // bytes occupied in the archive PackCompression compression = PackCompression::kStore; @@ -50,18 +50,18 @@ class RX_ASSET_EXPORT PackFile { public: // Null when the file is missing, truncated, has a bad magic/version, or its // table of contents fails the checksum. - static base::UniquePointer Open(std::string archive_path); + static base::UniquePointer Open(base::String archive_path); size_t entry_count() const { return entry_count_; } PackEntryView entry(size_t index) const; // `normalized_path` as produced by NormalizePath (lowercase, forward slashes). - std::optional Find(std::string_view normalized_path) const; + base::Optional Find(base::StringRef normalized_path) const; // Decompresses and checksum-verifies; nullopt on any mismatch or IO failure. - std::optional> ReadEntry(size_t index) const; + base::Optional> ReadEntry(size_t index) const; - const std::string& path() const { return path_; } + const base::String& path() const { return path_; } PackFile(const PackFile&) = delete; PackFile& operator=(const PackFile&) = delete; @@ -69,12 +69,12 @@ class RX_ASSET_EXPORT PackFile { private: PackFile() = default; - std::string path_; + base::String path_; size_t entry_count_ = 0; base::Vector toc_; // raw PackEntry records base::Vector names_; // name table backing entry() string views - mutable std::mutex io_mutex_; - mutable std::ifstream file_; + mutable base::Mutex io_mutex_; + mutable base::File file_; }; // Write side: stage payloads in memory, then emit the archive in one pass. @@ -83,7 +83,7 @@ class RX_ASSET_EXPORT PackWriter { public: // `virtual_path` is normalized on the way in; adding the same path twice // replaces the earlier payload. - void Add(std::string_view virtual_path, base::Vector bytes, + void Add(base::StringRef virtual_path, base::Vector bytes, PackCompression compression = PackCompression::kDeflate); // miniz level 1..10; default 6 balances ratio and packing time. @@ -91,11 +91,11 @@ class RX_ASSET_EXPORT PackWriter { size_t entry_count() const { return pending_.size(); } - bool WriteTo(const std::string& file_path); + bool WriteTo(const base::String& file_path); private: struct Pending { - std::string path; + base::String path; base::Vector bytes; PackCompression compression; }; @@ -109,7 +109,7 @@ class RX_ASSET_EXPORT PackWriter { }; // Serves an .rxp archive as a Vfs provider. Null when the archive fails to open. -RX_ASSET_EXPORT base::UniquePointer MakePackFileProvider(std::string archive_path); +RX_ASSET_EXPORT base::UniquePointer MakePackFileProvider(base::String archive_path); } // namespace rx::asset diff --git a/engine/asset/primitives.cc b/engine/asset/primitives.cc index 1fbdf764..2654ff68 100644 --- a/engine/asset/primitives.cc +++ b/engine/asset/primitives.cc @@ -1,10 +1,11 @@ #include "asset/primitives.h" -#include -#include -#include +#include +#include "base/containers/unordered_map.h" +#include "base/memory/move.h" #include "core/math.h" +#include "core/scalar.h" namespace rx::asset { namespace { @@ -34,12 +35,12 @@ constexpr f32 kLodReduction = 0.7f; // border vertices coincide; a border that moved would tear a lit gap at every // seam. MeshLod ClusterDecimate(const MeshLod& src, const Vec3& bmin, const Vec3& ext, u32 g) { - f32 cell[3] = {std::max(ext.x, 1e-5f) / g, std::max(ext.y, 1e-5f) / g, - std::max(ext.z, 1e-5f) / g}; + f32 cell[3] = {rx::Max(ext.x, 1e-5f) / g, rx::Max(ext.y, 1e-5f) / g, + rx::Max(ext.z, 1e-5f) / g}; auto cell_of = [&](const Vertex& v) -> u64 { - u32 cx = std::min(static_cast((v.position[0] - bmin.x) / cell[0]), g - 1); - u32 cy = std::min(static_cast((v.position[1] - bmin.y) / cell[1]), g - 1); - u32 cz = std::min(static_cast((v.position[2] - bmin.z) / cell[2]), g - 1); + u32 cx = rx::Min(static_cast((v.position[0] - bmin.x) / cell[0]), g - 1); + u32 cy = rx::Min(static_cast((v.position[1] - bmin.y) / cell[1]), g - 1); + u32 cz = rx::Min(static_cast((v.position[2] - bmin.z) / cell[2]), g - 1); return (static_cast(cz) * g + cy) * g + cx; }; @@ -48,7 +49,7 @@ MeshLod ClusterDecimate(const MeshLod& src, const Vec3& bmin, const Vec3& ext, u f64 p[3] = {0, 0, 0}; u32 count = 0; }; - std::unordered_map cell_position; + base::UnorderedMap cell_position; for (const Vertex& v : src.vertices) { CellPosition& c = cell_position[cell_of(v)]; for (int k = 0; k < 3; ++k) c.p[k] += v.position[k]; @@ -61,7 +62,7 @@ MeshLod ClusterDecimate(const MeshLod& src, const Vec3& bmin, const Vec3& ext, u // them as a boundary. Positions are compared on a fine grid rather than bit // for bit, because the copies rarely agree to the last bit (a lathe's seam // column closes on sin(2 pi), not on 0). - const f32 quantum = std::max({ext.x, ext.y, ext.z, 1e-5f}) / 65536.0f; + const f32 quantum = rx::Max({ext.x, ext.y, ext.z, 1e-5f}) / 65536.0f; struct GridPosition { i32 p[3]; bool operator==(const GridPosition&) const = default; @@ -76,15 +77,15 @@ MeshLod ClusterDecimate(const MeshLod& src, const Vec3& bmin, const Vec3& ext, u base::Vector position_id(src.vertices.size()); u32 position_count = 0; { - std::unordered_map seen; + base::UnorderedMap seen; for (size_t i = 0; i < src.vertices.size(); ++i) { const Vertex& v = src.vertices[i]; - GridPosition q{{static_cast(std::lround((v.position[0] - bmin.x) / quantum)), - static_cast(std::lround((v.position[1] - bmin.y) / quantum)), - static_cast(std::lround((v.position[2] - bmin.z) / quantum))}}; - auto it = seen.find(q); - if (it == seen.end()) it = seen.emplace(q, position_count++).first; - position_id[i] = it->second; + GridPosition q{{static_cast(::lround((v.position[0] - bmin.x) / quantum)), + static_cast(::lround((v.position[1] - bmin.y) / quantum)), + static_cast(::lround((v.position[2] - bmin.z) / quantum))}}; + u32* id = seen.find(q); + if (!id) id = seen.emplace(q, position_count++).first; + position_id[i] = *id; } } // Locked per position, not per vertex: the copies an importer split apart all @@ -96,7 +97,7 @@ MeshLod ClusterDecimate(const MeshLod& src, const Vec3& bmin, const Vec3& ext, u // leave nothing to decimate. base::Vector locked(position_count); { - const f32 border = std::max({ext.x, ext.y, ext.z, 1e-5f}) * 1e-4f; + const f32 border = rx::Max({ext.x, ext.y, ext.z, 1e-5f}) * 1e-4f; auto on_bounds = [&](u32 index) { const Vertex& v = src.vertices[index]; const f32 lo[3] = {bmin.x, bmin.y, bmin.z}; @@ -106,10 +107,10 @@ MeshLod ClusterDecimate(const MeshLod& src, const Vec3& bmin, const Vec3& ext, u } return false; }; - std::unordered_map edge_uses; + base::UnorderedMap edge_uses; auto edge = [&](u32 a, u32 b) { - u32 lo = std::min(position_id[a], position_id[b]); - u32 hi = std::max(position_id[a], position_id[b]); + u32 lo = rx::Min(position_id[a], position_id[b]); + u32 hi = rx::Max(position_id[a], position_id[b]); return (static_cast(lo) << 32) | hi; }; for (size_t i = 0; i + 3 <= src.indices.size(); i += 3) { @@ -153,7 +154,7 @@ MeshLod ClusterDecimate(const MeshLod& src, const Vec3& bmin, const Vec3& ext, u MeshLod out; constexpr u32 kUnmapped = 0xffffffffu; base::Vector remap(src.vertices.size()); - std::unordered_map cell_to_new; + base::UnorderedMap cell_to_new; for (size_t s = 0; s < sub_count; ++s) { // Both are per submesh: a source vertex shared by two submeshes has to map // to a separate representative in each. @@ -168,8 +169,8 @@ MeshLod ClusterDecimate(const MeshLod& src, const Vec3& bmin, const Vec3& ext, u // merges with its own split copies and with nothing else. const bool lock = locked[position_id[index]]; const u64 key = lock ? (1ull << 63) | position_id[index] : cell_of(v); - auto it = cell_to_new.find(key); - if (it == cell_to_new.end()) { + const u32* found = cell_to_new.find(key); + if (!found) { slot = static_cast(accum.size()); cell_to_new.emplace(key, slot); Accum a; @@ -179,7 +180,7 @@ MeshLod ClusterDecimate(const MeshLod& src, const Vec3& bmin, const Vec3& ext, u a.color = v.color; accum.push_back(a); } else { - slot = it->second; + slot = *found; } Accum& a = accum[slot]; for (int k = 0; k < 3; ++k) { @@ -194,8 +195,8 @@ MeshLod ClusterDecimate(const MeshLod& src, const Vec3& bmin, const Vec3& ext, u }; const u32 first = static_cast(out.indices.size()); - const size_t begin = std::min(subs[s].index_offset, src.indices.size()); - const size_t end = std::min(begin + subs[s].index_count, src.indices.size()); + const size_t begin = rx::Min(subs[s].index_offset, src.indices.size()); + const size_t end = rx::Min(begin + subs[s].index_count, src.indices.size()); for (size_t i = begin; i + 3 <= end; i += 3) { u32 a = representative(src.indices[i]); u32 b = representative(src.indices[i + 1]); @@ -265,7 +266,7 @@ void AddLathe(MeshLod* lod, const base::Vector& rings, u32 segments) for (size_t i = 1; i < rings.size(); ++i) { f32 dr = rings[i].radius - rings[i - 1].radius; f32 dy = rings[i].y - rings[i - 1].y; - travelled += std::sqrt(dr * dr + dy * dy); + travelled += ::sqrt(dr * dr + dy * dy); v[i] = travelled; } const f32 inv_travelled = travelled > 0.0f ? 1.0f / travelled : 0.0f; @@ -277,7 +278,7 @@ void AddLathe(MeshLod* lod, const base::Vector& rings, u32 segments) for (u32 x = 0; x <= segments; ++x) { f32 u = static_cast(x) / static_cast(segments); f32 theta = u * 6.2831853f; - f32 sin_theta = std::sin(theta), cos_theta = std::cos(theta); + f32 sin_theta = ::sin(theta), cos_theta = ::cos(theta); Vertex vertex{}; vertex.position[0] = ring.radius * cos_theta; vertex.position[1] = ring.y; @@ -332,7 +333,7 @@ void AddDisc(MeshLod* lod, f32 radius, f32 y, f32 sign, u32 segments) { push(0.0f, 0.0f, 0.5f, 0.5f); for (u32 x = 0; x <= segments; ++x) { f32 theta = static_cast(x) / static_cast(segments) * 6.2831853f; - f32 cos_theta = std::cos(theta), sin_theta = std::sin(theta); + f32 cos_theta = ::cos(theta), sin_theta = ::sin(theta); push(radius * cos_theta, radius * sin_theta, 0.5f + 0.5f * cos_theta, 0.5f + 0.5f * sin_theta); } @@ -409,8 +410,8 @@ UvCell BoxUvCell(u32 box_index, u32 box_count) { // Append an axis-aligned box spanning a..b inflated by `thick`, all vertices // weighted fully to `bone`, with its faces packed into `cell` of the uv atlas. void AddBox(MeshLod* lod, const Vec3& a, const Vec3& b, f32 thick, u8 bone, const UvCell& cell) { - f32 lo[3] = {std::min(a.x, b.x) - thick, std::min(a.y, b.y) - thick, std::min(a.z, b.z) - thick}; - f32 hi[3] = {std::max(a.x, b.x) + thick, std::max(a.y, b.y) + thick, std::max(a.z, b.z) + thick}; + f32 lo[3] = {rx::Min(a.x, b.x) - thick, rx::Min(a.y, b.y) - thick, rx::Min(a.z, b.z) - thick}; + f32 hi[3] = {rx::Max(a.x, b.x) + thick, rx::Max(a.y, b.y) + thick, rx::Max(a.z, b.z) + thick}; static constexpr int kFace[6][3] = {{0, 1, 2}, {0, 1, 2}, {1, 0, 2}, {1, 0, 2}, {2, 0, 1}, {2, 0, 1}}; static constexpr f32 kSign[6] = {1, -1, 1, -1, 1, -1}; @@ -493,7 +494,7 @@ Mesh MakeBox(f32 hx, f32 hy, f32 hz, AssetId id) { for (u32 index : {0u, 1u, 2u, 0u, 2u, 3u}) lod.indices.push_back(base + index); } - mesh.bounds_radius = std::sqrt(hx * hx + hy * hy + hz * hz); + mesh.bounds_radius = ::sqrt(hx * hx + hy * hy + hz * hz); return mesh; } @@ -511,11 +512,11 @@ Mesh MakeSphere(f32 radius, u32 rings, u32 segments, AssetId id) { for (u32 y = 0; y <= rings; ++y) { f32 v = static_cast(y) / static_cast(rings); f32 phi = v * 3.14159265f; // 0..pi, pole to pole - f32 sin_phi = std::sin(phi), cos_phi = std::cos(phi); + f32 sin_phi = ::sin(phi), cos_phi = ::cos(phi); for (u32 x = 0; x <= segments; ++x) { f32 u = static_cast(x) / static_cast(segments); f32 theta = u * 6.2831853f; - f32 sin_theta = std::sin(theta), cos_theta = std::cos(theta); + f32 sin_theta = ::sin(theta), cos_theta = ::cos(theta); Vec3 n{sin_phi * cos_theta, cos_phi, sin_phi * sin_theta}; Vertex vertex{}; vertex.position[0] = n.x * radius; @@ -568,7 +569,7 @@ Mesh MakePlane(f32 hx, f32 hz, AssetId id) { // Corners run ccw in the xz plane, which is clockwise seen from +Y, so the // winding is reversed to keep the visible face the one the normal points at. for (u32 index : {0u, 3u, 2u, 0u, 2u, 1u}) lod.indices.push_back(index); - return FinishPrimitive(std::move(mesh), std::sqrt(hx * hx + hz * hz)); + return FinishPrimitive(base::move(mesh), ::sqrt(hx * hx + hz * hz)); } Mesh MakeCylinder(f32 radius, f32 half_height, u32 segments, AssetId id) { @@ -581,8 +582,8 @@ Mesh MakeCylinder(f32 radius, f32 half_height, u32 segments, AssetId id) { AddLathe(&lod, side, segments); AddDisc(&lod, radius, half_height, 1.0f, segments); AddDisc(&lod, radius, -half_height, -1.0f, segments); - return FinishPrimitive(std::move(mesh), - std::sqrt(radius * radius + half_height * half_height)); + return FinishPrimitive(base::move(mesh), + ::sqrt(radius * radius + half_height * half_height)); } Mesh MakeCone(f32 radius, f32 half_height, u32 segments, AssetId id) { @@ -597,8 +598,8 @@ Mesh MakeCone(f32 radius, f32 half_height, u32 segments, AssetId id) { side.push_back({0.0f, half_height, normal_r, radius}); AddLathe(&lod, side, segments); AddDisc(&lod, radius, -half_height, -1.0f, segments); - return FinishPrimitive(std::move(mesh), - std::sqrt(radius * radius + half_height * half_height)); + return FinishPrimitive(base::move(mesh), + ::sqrt(radius * radius + half_height * half_height)); } Mesh MakeTorus(f32 major_radius, f32 minor_radius, u32 rings, u32 segments, AssetId id) { @@ -611,12 +612,12 @@ Mesh MakeTorus(f32 major_radius, f32 minor_radius, u32 rings, u32 segments, Asse // than wrapping the uv back to 0 across the seam quad. for (u32 i = 0; i <= rings; ++i) { f32 angle = static_cast(i) / static_cast(rings) * 6.2831853f; - f32 cos_angle = std::cos(angle), sin_angle = std::sin(angle); + f32 cos_angle = ::cos(angle), sin_angle = ::sin(angle); profile.push_back({major_radius + minor_radius * cos_angle, minor_radius * sin_angle, cos_angle, sin_angle}); } AddLathe(&lod, profile, segments); - return FinishPrimitive(std::move(mesh), major_radius + minor_radius); + return FinishPrimitive(base::move(mesh), major_radius + minor_radius); } Mesh MakeCapsule(f32 radius, f32 half_height, u32 rings, u32 segments, AssetId id) { @@ -630,18 +631,18 @@ Mesh MakeCapsule(f32 radius, f32 half_height, u32 rings, u32 segments, AssetId i // them needs no samples of its own. for (u32 i = 0; i <= cap_rings; ++i) { f32 angle = -1.5707963f + static_cast(i) / static_cast(cap_rings) * 1.5707963f; - f32 cos_angle = std::cos(angle), sin_angle = std::sin(angle); + f32 cos_angle = ::cos(angle), sin_angle = ::sin(angle); profile.push_back({radius * cos_angle, -half_height + radius * sin_angle, cos_angle, sin_angle}); } for (u32 i = 0; i <= cap_rings; ++i) { f32 angle = static_cast(i) / static_cast(cap_rings) * 1.5707963f; - f32 cos_angle = std::cos(angle), sin_angle = std::sin(angle); + f32 cos_angle = ::cos(angle), sin_angle = ::sin(angle); profile.push_back({radius * cos_angle, half_height + radius * sin_angle, cos_angle, sin_angle}); } AddLathe(&lod, profile, segments); - return FinishPrimitive(std::move(mesh), half_height + radius); + return FinishPrimitive(base::move(mesh), half_height + radius); } Mesh MakeLodSphere(f32 radius, AssetId id) { @@ -652,7 +653,7 @@ Mesh MakeLodSphere(f32 radius, AssetId id) { const u32 tess[][2] = {{48, 64}, {16, 22}, {6, 8}}; for (const auto& t : tess) { Mesh level = MakeSphere(radius, t[0], t[1], id); - mesh.lods.push_back(std::move(level.lods[0])); + mesh.lods.push_back(base::move(level.lods[0])); } mesh.bounds_radius = radius; return mesh; @@ -674,12 +675,12 @@ void GenerateLods(Mesh* mesh) { Vec3 bmin{1e30f, 1e30f, 1e30f}, bmax{-1e30f, -1e30f, -1e30f}; for (const Vertex& v : base.vertices) { - bmin.x = std::min(bmin.x, v.position[0]); - bmin.y = std::min(bmin.y, v.position[1]); - bmin.z = std::min(bmin.z, v.position[2]); - bmax.x = std::max(bmax.x, v.position[0]); - bmax.y = std::max(bmax.y, v.position[1]); - bmax.z = std::max(bmax.z, v.position[2]); + bmin.x = rx::Min(bmin.x, v.position[0]); + bmin.y = rx::Min(bmin.y, v.position[1]); + bmin.z = rx::Min(bmin.z, v.position[2]); + bmax.x = rx::Max(bmax.x, v.position[0]); + bmax.y = rx::Max(bmax.y, v.position[1]); + bmax.z = rx::Max(bmax.z, v.position[2]); } Vec3 ext{bmax.x - bmin.x, bmax.y - bmin.y, bmax.z - bmin.z}; @@ -696,7 +697,7 @@ void GenerateLods(Mesh* mesh) { static_cast(lod.indices.size()) > kLodReduction * static_cast(previous)) { break; } - mesh->lods.push_back(std::move(lod)); + mesh->lods.push_back(base::move(lod)); } } @@ -713,7 +714,7 @@ void MakeSkinnedBiped(AssetId mesh_id, Skeleton* out_skeleton, Mesh* out_mesh) { bone.bind_translation = kBiped[i].offset; bone.bind_rotation = {0, 0, 0, 1}; bone.bind_scale = 1.0f; - out_skeleton->bones.push_back(std::move(bone)); + out_skeleton->bones.push_back(base::move(bone)); world[i] = kBiped[i].parent >= 0 ? world[kBiped[i].parent] + kBiped[i].offset : kBiped[i].offset; } diff --git a/engine/asset/procedural_texture.cc b/engine/asset/procedural_texture.cc index 86498f3f..59026881 100644 --- a/engine/asset/procedural_texture.cc +++ b/engine/asset/procedural_texture.cc @@ -1,18 +1,20 @@ #include "asset/procedural_texture.h" -#include -#include +#include +#include +#include "base/strings/string_ref.h" #include "core/math.h" +#include "core/scalar.h" namespace rx::asset { namespace { -f32 Fract(f32 x) { return x - std::floor(x); } +f32 Fract(f32 x) { return x - ::floor(x); } f32 Smoothstep(f32 edge0, f32 edge1, f32 x) { if (edge1 <= edge0) return x < edge0 ? 0.0f : 1.0f; - f32 t = std::clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); + f32 t = rx::Clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); return t * t * (3.0f - 2.0f * t); } @@ -24,15 +26,15 @@ constexpr f32 kEdgeSoftness = 0.02f; // 0 at the cell boundary, 1 at the cell centre. f32 EdgeDistance(f32 t) { f32 f = Fract(t); - return std::min(f, 1.0f - f) * 2.0f; + return rx::Min(f, 1.0f - f) * 2.0f; } // A square wave alternating 0 and 1 per unit cell of `t`, crossfaded across the // cell boundary rather than stepped. Both cells reach exactly 0.5 at the // boundary, which is what keeps it continuous (and the checker antialiased). f32 SoftSquare(f32 t) { - f32 cell = std::floor(t); - f32 parity = std::fmod(std::abs(cell), 2.0f) >= 1.0f ? 1.0f : 0.0f; + f32 cell = ::floor(t); + f32 parity = ::fmod(::abs(cell), 2.0f) >= 1.0f ? 1.0f : 0.0f; f32 inside = 0.5f + 0.5f * Smoothstep(0.0f, kEdgeSoftness * 2.0f, EdgeDistance(t)); return parity * inside + (1.0f - parity) * (1.0f - inside); } @@ -50,7 +52,7 @@ u32 HashLattice(i32 x, i32 y, u32 seed) { f32 ValueNoise(f32 x, f32 y, i32 px, i32 py, u32 seed) { if (px < 1) px = 1; if (py < 1) py = 1; - i32 x0 = static_cast(std::floor(x)), y0 = static_cast(std::floor(y)); + i32 x0 = static_cast(::floor(x)), y0 = static_cast(::floor(y)); f32 fx = x - static_cast(x0), fy = y - static_cast(y0); auto wrap = [](i32 v, i32 period) { return ((v % period) + period) % period; }; auto corner = [&](i32 cx, i32 cy) { @@ -66,21 +68,21 @@ f32 ValueNoise(f32 x, f32 y, i32 px, i32 py, u32 seed) { // Encodes a linear channel with the sRGB transfer function, for the textures // bound to a slot the GPU samples as sRGB. u8 EncodeSrgb(f32 linear) { - f32 v = std::clamp(linear, 0.0f, 1.0f); - f32 encoded = v <= 0.0031308f ? v * 12.92f : 1.055f * std::pow(v, 1.0f / 2.4f) - 0.055f; - return static_cast(std::lround(encoded * 255.0f)); + f32 v = rx::Clamp(linear, 0.0f, 1.0f); + f32 encoded = v <= 0.0031308f ? v * 12.92f : 1.055f * ::pow(v, 1.0f / 2.4f) - 0.055f; + return static_cast(::lround(encoded * 255.0f)); } u8 EncodeLinear(f32 v) { - return static_cast(std::lround(std::clamp(v, 0.0f, 1.0f) * 255.0f)); + return static_cast(::lround(rx::Clamp(v, 0.0f, 1.0f) * 255.0f)); } Texture BeginTexture(const PatternDesc& desc, bool srgb, AssetId id) { Texture texture; texture.id = id; texture.format = TextureFormat::kRgba8; - texture.width = std::max(desc.width, 1u); - texture.height = std::max(desc.height, 1u); + texture.width = rx::Max(desc.width, 1u); + texture.height = rx::Max(desc.height, 1u); texture.is_srgb = srgb; texture.data.resize(static_cast(texture.width) * texture.height * 4); return texture; @@ -88,7 +90,7 @@ Texture BeginTexture(const PatternDesc& desc, bool srgb, AssetId id) { } // namespace -bool ParsePatternKind(std::string_view name, PatternKind* out) { +bool ParsePatternKind(base::StringRef name, PatternKind* out) { if (name == "checker") *out = PatternKind::kChecker; else if (name == "grid") *out = PatternKind::kGrid; else if (name == "brick") *out = PatternKind::kBrick; @@ -99,39 +101,39 @@ bool ParsePatternKind(std::string_view name, PatternKind* out) { } f32 SamplePattern(const PatternDesc& desc, f32 u, f32 v) { - const f32 su = std::max(desc.scale[0], 0.0001f); - const f32 sv = std::max(desc.scale[1], 0.0001f); - const f32 line = std::clamp(desc.line_width, 0.0f, 0.9f); + const f32 su = rx::Max(desc.scale[0], 0.0001f); + const f32 sv = rx::Max(desc.scale[1], 0.0001f); + const f32 line = rx::Clamp(desc.line_width, 0.0f, 0.9f); switch (desc.kind) { case PatternKind::kChecker: { f32 a = SoftSquare(u * su), b = SoftSquare(v * sv); return a + b - 2.0f * a * b; // soft xor } case PatternKind::kGrid: { - f32 d = std::min(EdgeDistance(u * su), EdgeDistance(v * sv)); + f32 d = rx::Min(EdgeDistance(u * su), EdgeDistance(v * sv)); return Smoothstep(line - kEdgeSoftness, line + kEdgeSoftness, d); } case PatternKind::kBrick: { // scale is bricks across by courses up, and every other course is offset // by half a brick. - f32 course = std::floor(v * sv); - f32 across = u * su + (std::fmod(std::abs(course), 2.0f) >= 1.0f ? 0.5f : 0.0f); + f32 course = ::floor(v * sv); + f32 across = u * su + (::fmod(::abs(course), 2.0f) >= 1.0f ? 0.5f : 0.0f); // `line` is a fraction of a COURSE, and the joint has to come out the // same uv width both ways, so the fraction across is rescaled by how much // wider a brick is than a course is tall. A wall 4 bricks across and 20 // courses up has bricks 5x as wide, so the vertical joint is 1/5 of the // cell the horizontal one is. - f32 line_u = std::clamp(line * su / sv, 0.0f, 0.9f); + f32 line_u = rx::Clamp(line * su / sv, 0.0f, 0.9f); f32 dv = Smoothstep(line - kEdgeSoftness, line + kEdgeSoftness, EdgeDistance(v * sv)); f32 du = Smoothstep(line_u - kEdgeSoftness, line_u + kEdgeSoftness, EdgeDistance(across)); return du * dv; } case PatternKind::kGradient: - return std::clamp(v, 0.0f, 1.0f); + return rx::Clamp(v, 0.0f, 1.0f); case PatternKind::kNoise: { f32 sum = 0.0f, amplitude = 1.0f, total = 0.0f; - i32 px = std::max(1, static_cast(std::lround(su))); - i32 py = std::max(1, static_cast(std::lround(sv))); + i32 px = rx::Max(1, static_cast(::lround(su))); + i32 py = rx::Max(1, static_cast(::lround(sv))); for (int octave = 0; octave < 4; ++octave) { sum += ValueNoise(u * static_cast(px), v * static_cast(py), px, py, desc.seed + static_cast(octave) * 7919u) * diff --git a/engine/asset/procedural_texture.h b/engine/asset/procedural_texture.h index bd0f3dfe..74a335ca 100644 --- a/engine/asset/procedural_texture.h +++ b/engine/asset/procedural_texture.h @@ -1,9 +1,9 @@ #ifndef RX_ASSET_PROCEDURAL_TEXTURE_H_ #define RX_ASSET_PROCEDURAL_TEXTURE_H_ -#include #include "asset/texture.h" +#include "base/strings/string_ref.h" #include "core/export.h" namespace rx::asset { @@ -29,7 +29,7 @@ enum class PatternKind : u8 { // Resolves a pattern name ("checker", "grid", "brick", "gradient", "noise") or // returns false, so a caller can reject an unknown name instead of substituting // one. Case sensitive, like the rest of the scene-file vocabulary. -RX_ASSET_EXPORT bool ParsePatternKind(std::string_view name, PatternKind* out); +RX_ASSET_EXPORT bool ParsePatternKind(base::StringRef name, PatternKind* out); struct PatternDesc { PatternKind kind = PatternKind::kChecker; diff --git a/engine/asset/scene_import.h b/engine/asset/scene_import.h index f11dd061..46839af7 100644 --- a/engine/asset/scene_import.h +++ b/engine/asset/scene_import.h @@ -1,7 +1,6 @@ #ifndef RX_ASSET_SCENE_IMPORT_H_ #define RX_ASSET_SCENE_IMPORT_H_ -#include #include @@ -9,6 +8,7 @@ #include "asset/mesh.h" #include "asset/skeleton.h" #include "asset/texture.h" +#include "base/strings/xstring.h" #include "core/math.h" namespace rx::asset { @@ -65,7 +65,7 @@ struct ImportedScene { f32 cone_angle = 180.0f; // ShapingAPI cone, degrees (180 = no cone) f32 cone_softness = 0.0f; bool normalize = false; // divide by area (UsdLux `normalize`) - std::string texture; // dome/rect environment map, resolved to disk + base::String texture; // dome/rect environment map, resolved to disk // Chromaticity of `texture`, solid-angle averaged and normalized to unit // luminance, or (1,1,1) with no texture. A dome's `color` is only a tint on // top of its environment map, so the map is what decides what colour the diff --git a/engine/asset/simplify.cc b/engine/asset/simplify.cc index 22438bcd..67271e84 100644 --- a/engine/asset/simplify.cc +++ b/engine/asset/simplify.cc @@ -1,10 +1,11 @@ #include "asset/simplify.h" +#include "base/algorithm.h" +#include "base/containers/unordered_map.h" +#include "base/containers/unordered_set.h" +#include "base/containers/vector.h" +#include "core/scalar.h" -#include -#include -#include -#include -#include +#include namespace rx::asset { namespace { @@ -40,11 +41,58 @@ struct Candidate { f64 cost; u32 from, to; u32 stamp; // from-vertex version when queued; stale entries are skipped - bool operator<(const Candidate& other) const { return cost > other.cost; } // min-heap +}; + +// Cheapest first, and a total order beyond the cost: flat regions produce +// many exactly equal (zero) costs, and breaking those ties by content makes +// the collapse sequence a function of the mesh alone, not of the order edges +// were queued in or of any container's iteration order. +bool CollapsesBefore(const Candidate& a, const Candidate& b) { + if (a.cost != b.cost) return a.cost < b.cost; + if (a.from != b.from) return a.from < b.from; + if (a.to != b.to) return a.to < b.to; + return a.stamp < b.stamp; +} + +// Binary min-heap over CollapsesBefore. +class CandidateHeap { + public: + bool empty() const { return items_.empty(); } + const Candidate& top() const { return items_[0]; } + + void push(const Candidate& c) { + items_.push_back(c); + mem_size i = items_.size() - 1; + while (i > 0) { + const mem_size parent = (i - 1) / 2; + if (!CollapsesBefore(items_[i], items_[parent])) break; + base::Swap(items_[i], items_[parent]); + i = parent; + } + } + + void pop() { + items_[0] = items_.back(); + items_.pop_back(); + const mem_size n = items_.size(); + mem_size i = 0; + for (;;) { + const mem_size left = 2 * i + 1; + if (left >= n) break; + mem_size best = left; + if (left + 1 < n && CollapsesBefore(items_[left + 1], items_[left])) best = left + 1; + if (!CollapsesBefore(items_[best], items_[i])) break; + base::Swap(items_[i], items_[best]); + i = best; + } + } + + private: + base::Vector items_; }; u64 EdgeKey(u32 a, u32 b) { - return (static_cast(std::min(a, b)) << 32) | std::max(a, b); + return (static_cast(rx::Min(a, b)) << 32) | rx::Max(a, b); } // The actual collapse loop, over a DENSE vertex range: every scratch array is @@ -52,31 +100,31 @@ u64 EdgeKey(u32 a, u32 b) { // wrapper below does). Called once per cluster group during the DAG build - // sizing scratch by the whole source mesh instead made thousands of group // calls over a multi-million-vertex mesh spend all their time in allocation. -std::vector SimplifyDense(const Vec3* positions, u32 vertex_count, const u32* indices, +base::Vector SimplifyDense(const Vec3* positions, u32 vertex_count, const u32* indices, u32 index_count, u32 target_index_count, const u8* locked, f32* out_error) { - std::vector result(indices, indices + index_count); + base::Vector result(indices, indices + index_count); // Union-find style remap: collapsed vertices forward to their target. - std::vector remap(vertex_count); + base::Vector remap(vertex_count); for (u32 i = 0; i < vertex_count; ++i) remap[i] = i; auto resolve = [&](u32 v) { while (remap[v] != v) v = remap[v]; return v; }; - std::vector quadrics(vertex_count); - std::vector version(vertex_count, 0); + base::Vector quadrics(vertex_count); + base::Vector version(vertex_count, 0); // Adjacency: triangles per vertex (triangle ids into result/3). - std::vector> vertex_tris(vertex_count); + base::Vector> vertex_tris(vertex_count); u32 live_triangles = index_count / 3; - std::vector tri_dead(live_triangles, false); + base::Vector tri_dead(live_triangles, false); for (u32 t = 0; t < index_count / 3; ++t) { u32 i0 = result[t * 3], i1 = result[t * 3 + 1], i2 = result[t * 3 + 2]; Vec3 p0 = positions[i0], p1 = positions[i1], p2 = positions[i2]; Vec3 n = Cross(p1 - p0, p2 - p0); - f64 len = std::sqrt(static_cast(n.x) * n.x + static_cast(n.y) * n.y + + f64 len = ::sqrt(static_cast(n.x) * n.x + static_cast(n.y) * n.y + static_cast(n.z) * n.z); if (len < 1e-12) { tri_dead[t] = true; @@ -98,37 +146,38 @@ std::vector SimplifyDense(const Vec3* positions, u32 vertex_count, const u3 // Border edges of the SUBMESH itself (used by one triangle only) also lock: // collapsing them would erode the patch outline even without a lock array. - std::unordered_map edge_use; + base::UnorderedMap edge_use; for (u32 t = 0; t < index_count / 3; ++t) { if (tri_dead[t]) continue; for (u32 e = 0; e < 3; ++e) { ++edge_use[EdgeKey(result[t * 3 + e], result[t * 3 + (e + 1) % 3])]; } } - std::vector border(vertex_count, 0); - for (const auto& entry : edge_use) { - if (entry.second == 1) { - border[static_cast(entry.first >> 32)] = 1; - border[static_cast(entry.first & 0xffffffffu)] = 1; + base::Vector border(vertex_count, 0); + // Order-free: only marks vertices. + for (auto entry : edge_use) { + if (entry.value == 1) { + border[static_cast(entry.key >> 32)] = 1; + border[static_cast(entry.key & 0xffffffffu)] = 1; } } auto is_locked = [&](u32 v) { return (locked && locked[v]) || border[v]; }; - std::priority_queue heap; + CandidateHeap heap; auto push_edge = [&](u32 a, u32 b) { if (is_locked(a)) return; // half-edge collapse a -> b Quadric q = quadrics[a]; q.Add(quadrics[b]); - f64 cost = std::max(q.Evaluate(positions[b]), 0.0); + f64 cost = rx::Max(q.Evaluate(positions[b]), 0.0); heap.push({cost, a, b, version[a]}); }; { - std::unordered_set seen; + base::UnorderedSet seen; for (u32 t = 0; t < index_count / 3; ++t) { if (tri_dead[t]) continue; for (u32 e = 0; e < 3; ++e) { u32 a = result[t * 3 + e], b = result[t * 3 + (e + 1) % 3]; - if (seen.insert(EdgeKey(a, b)).second) { + if (seen.insert(EdgeKey(a, b))) { push_edge(a, b); push_edge(b, a); } @@ -170,7 +219,7 @@ std::vector SimplifyDense(const Vec3* positions, u32 vertex_count, const u3 remap[a] = b; ++version[a]; quadrics[b].Add(quadrics[a]); - max_cost = std::max(max_cost, top.cost); + max_cost = rx::Max(max_cost, top.cost); for (u32 t : vertex_tris[a]) { if (tri_dead[t]) continue; u32 v[3] = {resolve(result[t * 3]), resolve(result[t * 3 + 1]), resolve(result[t * 3 + 2])}; @@ -182,7 +231,7 @@ std::vector SimplifyDense(const Vec3* positions, u32 vertex_count, const u3 vertex_tris[b].push_back(t); } // Refresh collapse candidates around b. - std::unordered_set neighbors; + base::UnorderedSet neighbors; for (u32 t : vertex_tris[b]) { if (tri_dead[t]) continue; for (u32 k = 0; k < 3; ++k) { @@ -190,13 +239,13 @@ std::vector SimplifyDense(const Vec3* positions, u32 vertex_count, const u3 if (v != b) neighbors.insert(v); } } - for (u32 n : neighbors) { + neighbors.ForEach([&](u32 n) { push_edge(n, b); push_edge(b, n); - } + }); } - std::vector out; + base::Vector out; out.reserve(live_triangles * 3); for (u32 t = 0; t < index_count / 3; ++t) { if (tri_dead[t]) continue; @@ -207,41 +256,41 @@ std::vector SimplifyDense(const Vec3* positions, u32 vertex_count, const u3 out.push_back(v1); out.push_back(v2); } - if (out_error) *out_error = static_cast(std::sqrt(std::max(max_cost, 0.0))); + if (out_error) *out_error = static_cast(::sqrt(rx::Max(max_cost, 0.0))); return out; } } // namespace -std::vector SimplifyIndices(const Vec3* positions, u32 vertex_count, const u32* indices, +base::Vector SimplifyIndices(const Vec3* positions, u32 vertex_count, const u32* indices, u32 index_count, u32 target_index_count, const u8* locked, f32* out_error) { if (out_error) *out_error = 0.0f; if (index_count <= target_index_count || index_count < 12) { - return std::vector(indices, indices + index_count); + return base::Vector(indices, indices + index_count); } (void)vertex_count; // Compact to the vertices this submesh actually references, simplify in // that dense space, then map the survivors back to source indices. - std::unordered_map to_local; + base::UnorderedMap to_local; to_local.reserve(index_count); - std::vector to_global; - std::vector local_positions; - std::vector local_locked; - std::vector local_indices(index_count); + base::Vector to_global; + base::Vector local_positions; + base::Vector local_locked; + base::Vector local_indices(index_count); for (u32 i = 0; i < index_count; ++i) { u32 g = indices[i]; - auto [it, inserted] = to_local.emplace(g, static_cast(to_global.size())); + auto [local, inserted] = to_local.emplace(g, static_cast(to_global.size())); if (inserted) { to_global.push_back(g); local_positions.push_back(positions[g]); local_locked.push_back(locked ? locked[g] : 0); } - local_indices[i] = it->second; + local_indices[i] = *local; } - std::vector out = SimplifyDense(local_positions.data(), + base::Vector out = SimplifyDense(local_positions.data(), static_cast(to_global.size()), local_indices.data(), index_count, target_index_count, local_locked.data(), out_error); diff --git a/engine/asset/simplify.h b/engine/asset/simplify.h index 021af443..fb61ffdf 100644 --- a/engine/asset/simplify.h +++ b/engine/asset/simplify.h @@ -7,8 +7,8 @@ // DAG build) are preserved exactly - the property the crack-free LOD cut // depends on. -#include +#include "base/containers/vector.h" #include "core/export.h" #include "core/math.h" #include "core/types.h" @@ -19,7 +19,7 @@ namespace rx::asset { // `target_index_count`. Vertices with locked[v] != 0 never move or disappear. // Returns the simplified index list; *out_error receives the maximum collapse // distance (world units), an upper-bound style error for LOD selection. -RX_ASSET_EXPORT std::vector SimplifyIndices(const rx::Vec3* positions, u32 vertex_count, +RX_ASSET_EXPORT base::Vector SimplifyIndices(const rx::Vec3* positions, u32 vertex_count, const u32* indices, u32 index_count, u32 target_index_count, const u8* locked, f32* out_error); diff --git a/engine/asset/skeleton.h b/engine/asset/skeleton.h index 967bc9a7..035857cc 100644 --- a/engine/asset/skeleton.h +++ b/engine/asset/skeleton.h @@ -1,12 +1,12 @@ #ifndef RX_ASSET_SKELETON_H_ #define RX_ASSET_SKELETON_H_ -#include -#include #include #include "asset/asset_id.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/math.h" #include "core/types.h" @@ -18,7 +18,7 @@ namespace rx::asset { // static source geometry. Stored decomposed so animation can layer rotation // deltas onto the rest orientation. struct Bone { - std::string name; + base::String name; i32 parent = -1; // index into Skeleton::bones, -1 = root Vec3 bind_translation; Quat bind_rotation; @@ -32,7 +32,7 @@ struct Skeleton { AssetId id; base::Vector bones; - i32 Find(std::string_view name) const { + i32 Find(base::StringRef name) const { for (u32 i = 0; i < bones.size(); ++i) { if (bones[i].name == name) return static_cast(i); } @@ -47,7 +47,7 @@ struct Skeleton { // palette is // `bone_model[skeleton.Find(bones[i])] * inverse_bind[i]`. struct SkinBinding { - base::Vector bones; + base::Vector bones; base::Vector inverse_bind; bool empty() const { return bones.empty(); } diff --git a/engine/asset/subdivide.cc b/engine/asset/subdivide.cc index 822b805b..d37aaa26 100644 --- a/engine/asset/subdivide.cc +++ b/engine/asset/subdivide.cc @@ -1,10 +1,11 @@ #include "asset/subdivide.h" -#include +#include #include #include +#include "base/memory/move.h" #include "core/math.h" namespace rx::asset { @@ -158,9 +159,9 @@ void SubdivideOnce(MeshLod& lod) { out_submeshes.push_back(out_sm); } - lod.vertices = std::move(out_verts); - lod.indices = std::move(out_indices); - lod.submeshes = std::move(out_submeshes); + lod.vertices = base::move(out_verts); + lod.indices = base::move(out_indices); + lod.submeshes = base::move(out_submeshes); } } // namespace @@ -194,7 +195,7 @@ void RecomputeNormalsTangents(MeshLod& lod) { f32 du1 = v1.uv[0] - v0.uv[0], dv1 = v1.uv[1] - v0.uv[1]; f32 du2 = v2.uv[0] - v0.uv[0], dv2 = v2.uv[1] - v0.uv[1]; f32 det = du1 * dv2 - du2 * dv1; - if (std::fabs(det) > 1e-12f) { + if (::fabs(det) > 1e-12f) { f32 r = 1.0f / det; Vec3 tan = (e1 * dv2 - e2 * dv1) * r; Vec3 bit = (e2 * du1 - e1 * du2) * r; @@ -214,7 +215,7 @@ void RecomputeNormalsTangents(MeshLod& lod) { tan = tan - nrm * Dot(nrm, tan); // Gram-Schmidt if (Dot(tan, tan) < 1e-10f) { // Degenerate uv: pick any axis orthogonal to the normal. - Vec3 axis = std::fabs(nrm.x) < 0.9f ? Vec3{1, 0, 0} : Vec3{0, 1, 0}; + Vec3 axis = ::fabs(nrm.x) < 0.9f ? Vec3{1, 0, 0} : Vec3{0, 1, 0}; tan = Cross(axis, nrm); } tan = Normalize(tan); diff --git a/engine/asset/texture_compress.cc b/engine/asset/texture_compress.cc index 5ce072f4..549ce296 100644 --- a/engine/asset/texture_compress.cc +++ b/engine/asset/texture_compress.cc @@ -1,17 +1,9 @@ #include "asset/texture_compress.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #ifdef _WIN32 #include @@ -21,10 +13,23 @@ #include "asset/asset_id.h" #include "asset/bc_encode.h" +#include "base/atomic.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "base/threading/lock_guard.h" +#include "base/threading/mutex.h" +#include "base/containers/vector.h" +#include "base/memory/unique_pointer.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "core/format.h" +#include "core/file_system.h" #include "core/log.h" +#include "core/scalar.h" namespace rx::asset { -namespace fs = std::filesystem; namespace { // Bumped whenever the encoder or the mip filter changes what it produces. @@ -35,10 +40,10 @@ namespace { constexpr u32 kCodecVersion = 1; constexpr char kCacheMagic[4] = {'R', 'X', 'T', 'C'}; -std::atomic g_supported{false}; -std::atomic g_compress_normals{false}; +base::Atomic g_supported{false}; +base::Atomic g_compress_normals{false}; -std::mutex g_stats_mutex; +base::Mutex g_stats_mutex; TextureCompressionStats g_stats; // colour space @@ -48,7 +53,7 @@ const f32* SrgbToLinearTable() { auto* values = new f32[256]; for (u32 i = 0; i < 256; ++i) { const f32 c = static_cast(i) / 255.0f; - values[i] = c <= 0.04045f ? c / 12.92f : std::pow((c + 0.055f) / 1.055f, 2.4f); + values[i] = c <= 0.04045f ? c / 12.92f : ::pow((c + 0.055f) / 1.055f, 2.4f); } return values; }(); @@ -56,16 +61,16 @@ const f32* SrgbToLinearTable() { } u8 LinearToSrgb(f32 v) { - v = std::clamp(v, 0.0f, 1.0f); - const f32 s = v <= 0.0031308f ? v * 12.92f : 1.055f * std::pow(v, 1.0f / 2.4f) - 0.055f; - return static_cast(std::clamp(s * 255.0f + 0.5f, 0.0f, 255.0f)); + v = rx::Clamp(v, 0.0f, 1.0f); + const f32 s = v <= 0.0031308f ? v * 12.92f : 1.055f * ::pow(v, 1.0f / 2.4f) - 0.055f; + return static_cast(rx::Clamp(s * 255.0f + 0.5f, 0.0f, 255.0f)); } u32 FullMipChainLength(u32 width, u32 height) { u32 levels = 1; while (width > 1 || height > 1) { - width = std::max(1u, width / 2); - height = std::max(1u, height / 2); + width = rx::Max(1u, width / 2); + height = rx::Max(1u, height / 2); ++levels; } return levels; @@ -83,16 +88,16 @@ void Downsample(const u8* src, u32 sw, u32 sh, u8* dst, u32 dw, u32 dh, bool srg const f32 scale_y = static_cast(sh) / static_cast(dh); for (u32 y = 0; y < dh; ++y) { const f32 fy = (static_cast(y) + 0.5f) * scale_y - 0.5f; - const i32 y0 = static_cast(std::floor(fy)); + const i32 y0 = static_cast(::floor(fy)); const f32 wy = fy - static_cast(y0); - const u32 ya = static_cast(std::clamp(y0, 0, static_cast(sh) - 1)); - const u32 yb = static_cast(std::clamp(y0 + 1, 0, static_cast(sh) - 1)); + const u32 ya = static_cast(rx::Clamp(y0, 0, static_cast(sh) - 1)); + const u32 yb = static_cast(rx::Clamp(y0 + 1, 0, static_cast(sh) - 1)); for (u32 x = 0; x < dw; ++x) { const f32 fx = (static_cast(x) + 0.5f) * scale_x - 0.5f; - const i32 x0 = static_cast(std::floor(fx)); + const i32 x0 = static_cast(::floor(fx)); const f32 wx = fx - static_cast(x0); - const u32 xa = static_cast(std::clamp(x0, 0, static_cast(sw) - 1)); - const u32 xb = static_cast(std::clamp(x0 + 1, 0, static_cast(sw) - 1)); + const u32 xa = static_cast(rx::Clamp(x0, 0, static_cast(sw) - 1)); + const u32 xb = static_cast(rx::Clamp(x0 + 1, 0, static_cast(sw) - 1)); const u8* p00 = src + (static_cast(ya) * sw + xa) * 4; const u8* p10 = src + (static_cast(ya) * sw + xb) * 4; const u8* p01 = src + (static_cast(yb) * sw + xa) * 4; @@ -110,7 +115,7 @@ void Downsample(const u8* src, u32 sw, u32 sh, u8* dst, u32 dw, u32 dh, bool srg } else { const f32 v = static_cast(p00[c]) * w00 + static_cast(p10[c]) * w10 + static_cast(p01[c]) * w01 + static_cast(p11[c]) * w11; - out[c] = static_cast(std::clamp(v + 0.5f, 0.0f, 255.0f)); + out[c] = static_cast(rx::Clamp(v + 0.5f, 0.0f, 255.0f)); } } } @@ -162,7 +167,7 @@ void EncodeBlock(TextureFormat format, const u8* rgba, u8* out) { // block that is at least deterministic. RX_ERROR("EncodeBlock: no encoder for texture format {}, writing an empty block", static_cast(format)); - std::memset(out, 0, BlockBytes(format)); + base::MemSet(out, 0, BlockBytes(format)); return; } } @@ -183,10 +188,10 @@ void EncodeSurface(const u8* rgba, u32 width, u32 height, TextureFormat format, // Edges that are not a multiple of four replicate the last real // texel into the padding. Leaving it zero would pull the endpoint // fit toward black on every edge block. - const u32 sy = std::min(by * 4 + ty, height - 1); + const u32 sy = rx::Min(by * 4 + ty, height - 1); for (u32 tx = 0; tx < 4; ++tx) { - const u32 sx = std::min(bx * 4 + tx, width - 1); - std::memcpy(block + (ty * 4 + tx) * 4, rgba + (static_cast(sy) * width + sx) * 4, + const u32 sx = rx::Min(bx * 4 + tx, width - 1); + base::MemCopy(block + (ty * 4 + tx) * 4, rgba + (static_cast(sy) * width + sx) * 4, 4); } } @@ -196,24 +201,25 @@ void EncodeSurface(const u8* rgba, u32 width, u32 height, TextureFormat format, } }; - unsigned threads = std::thread::hardware_concurrency(); - if (threads == 0) threads = 1; - threads = std::min(threads, blocks_y); + unsigned threads = base::GetProcessorCount(); + threads = rx::Min(threads, blocks_y); if (threads <= 1) { encode_rows(0, blocks_y); return; } - std::vector workers; + // base::Thread is not movable; the vector must not relocate live threads. + base::Vector> workers; workers.reserve(threads - 1); const u32 per_thread = (blocks_y + threads - 1) / threads; for (unsigned t = 1; t < threads; ++t) { - const u32 first = std::min(blocks_y, t * per_thread); - const u32 last = std::min(blocks_y, first + per_thread); + const u32 first = rx::Min(blocks_y, t * per_thread); + const u32 last = rx::Min(blocks_y, first + per_thread); if (first >= last) break; - workers.emplace_back([&, first, last] { encode_rows(first, last); }); + workers.push_back(base::MakeUnique( + "rx-bc-encode", [&, first, last] { encode_rows(first, last); }, /*start_now=*/true)); } - encode_rows(0, std::min(blocks_y, per_thread)); - for (std::thread& worker : workers) worker.join(); + encode_rows(0, rx::Min(blocks_y, per_thread)); + for (auto& worker : workers) worker->Join(); } // format choice @@ -244,33 +250,32 @@ TextureFormat FormatForRole(const Texture& texture, TextureRole role) { // disk cache -std::string CacheRoot() { - if (const char* override_dir = std::getenv("RX_TEXCACHE_DIR")) return override_dir; - if (const char* xdg = std::getenv("XDG_CACHE_HOME")) return (fs::path(xdg) / "rx/texcache").string(); +base::String CacheRoot() { + if (const char* override_dir = ::getenv("RX_TEXCACHE_DIR")) return override_dir; + if (const char* xdg = ::getenv("XDG_CACHE_HOME")) return fs::Join(xdg, "rx/texcache"); #if defined(_WIN32) - if (const char* local = std::getenv("LOCALAPPDATA")) - return (fs::path(local) / "rx/texcache").string(); + if (const char* local = ::getenv("LOCALAPPDATA")) return fs::Join(local, "rx/texcache"); #else - if (const char* home = std::getenv("HOME")) return (fs::path(home) / ".cache/rx/texcache").string(); + if (const char* home = ::getenv("HOME")) return fs::Join(home, ".cache/rx/texcache"); #endif - return (fs::temp_directory_path() / "rx/texcache").string(); + return fs::Join(fs::TempDirectory(), "rx/texcache"); } // Keyed on the SOURCE PIXELS, not on the file path: the same image embedded in // forty glb files, or reached through two different relative paths, is one // cache entry and one encode. The cost is hashing the decoded mip 0 on every // load, which is a couple of milliseconds against an encode measured in tens. -std::string CacheKey(const Texture& texture, TextureFormat format) { +base::String CacheKey(const Texture& texture, TextureFormat format) { const u64 content = - MakeAssetId(std::string_view(reinterpret_cast(texture.data.data()), + MakeAssetId(base::StringRef(reinterpret_cast(texture.data.data()), texture.data.size())) .hash; char shape[96]; - std::snprintf(shape, sizeof(shape), "%016llx:%u:%u:%u:%u:%u", + ::snprintf(shape, sizeof(shape), "%016llx:%u:%u:%u:%u:%u", static_cast(content), texture.width, texture.height, static_cast(format), texture.is_srgb ? 1u : 0u, kCodecVersion); char key[17]; - std::snprintf(key, sizeof(key), "%016llx", + ::snprintf(key, sizeof(key), "%016llx", static_cast(MakeAssetId(shape).hash)); return key; } @@ -286,13 +291,13 @@ struct CacheHeader { u64 data_bytes; }; -bool ReadCache(const fs::path& path, Texture* texture, TextureFormat format) { - std::ifstream file(path, std::ios::binary); - if (!file) return false; +bool ReadCache(base::StringRef path, Texture* texture, TextureFormat format) { + base::File file = fs::OpenFile(path, base::File::FLAG_OPEN | base::File::FLAG_READ); + if (!file.IsValid()) return false; CacheHeader header{}; - file.read(reinterpret_cast(&header), sizeof(header)); - if (!file) return false; - if (std::memcmp(header.magic, kCacheMagic, 4) != 0 || header.version != kCodecVersion) return false; + if (!fs::ReadAt(file, 0, base::Span(reinterpret_cast(&header), sizeof(header)))) + return false; + if (base::MemCompare(header.magic, kCacheMagic, 4) != 0 || header.version != kCodecVersion) return false; // The key already covers all of this; a mismatch means a hash collision or a // hand-edited cache, and trusting it would upload a texture of the wrong // shape. Cheaper to re-encode than to debug that. @@ -303,17 +308,15 @@ bool ReadCache(const fs::path& path, Texture* texture, TextureFormat format) { if (header.data_bytes == 0 || header.data_bytes > (1ull << 32)) return false; if (header.mip_count != FullMipChainLength(texture->width, texture->height)) return false; base::Vector data(static_cast(header.data_bytes)); - file.read(reinterpret_cast(data.data()), static_cast(data.size())); - if (!file) return false; + if (!fs::ReadAt(file, sizeof(header), base::Span(data.data(), data.size()))) return false; texture->format = format; texture->mip_count = header.mip_count; - texture->data = std::move(data); + texture->data = base::move(data); return true; } -void WriteCache(const fs::path& path, const Texture& texture) { - std::error_code error; - fs::create_directories(path.parent_path(), error); +void WriteCache(base::StringRef path, const Texture& texture) { + fs::CreateDirectories(fs::ParentPath(path)); // Write beside the target and rename: two processes loading the same scene, // or one killed mid-write, must not leave a truncated file that the next run // reads back as a valid texture. @@ -326,12 +329,13 @@ void WriteCache(const fs::path& path, const Texture& texture) { #else const int pid = getpid(); #endif - const fs::path temp = fs::path(path).concat("." + std::to_string(pid) + ".tmp"); + const base::String temp = base::String(path) + "." + rx::ToString(pid) + ".tmp"; { - std::ofstream file(temp, std::ios::binary | std::ios::trunc); - if (!file) return; + base::File file = + fs::OpenFile(temp, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); + if (!file.IsValid()) return; CacheHeader header{}; - std::memcpy(header.magic, kCacheMagic, 4); + base::MemCopy(header.magic, kCacheMagic, 4); header.version = kCodecVersion; header.format = static_cast(texture.format); header.width = texture.width; @@ -339,17 +343,17 @@ void WriteCache(const fs::path& path, const Texture& texture) { header.mip_count = texture.mip_count; header.srgb = texture.is_srgb ? 1u : 0u; header.data_bytes = texture.data.size(); - file.write(reinterpret_cast(&header), sizeof(header)); - file.write(reinterpret_cast(texture.data.data()), - static_cast(texture.data.size())); - if (!file) { - file.close(); - fs::remove(temp, error); + const bool written = + fs::WriteAll(file, base::Span(reinterpret_cast(&header), + sizeof(header))) && + fs::WriteAll(file, base::Span(texture.data.data(), texture.data.size())); + if (!written) { + file.Close(); + fs::Remove(temp); return; } } - fs::rename(temp, path, error); - if (error) fs::remove(temp, error); + if (!fs::Rename(temp, path)) fs::Remove(temp); } } // namespace @@ -363,9 +367,9 @@ TextureCompressionOptions TextureCompressionSettings() { return {g_supported.load(), g_compress_normals.load()}; } -bool CompressTexture(Texture* texture, TextureRole role, std::string_view identity) { +bool CompressTexture(Texture* texture, TextureRole role, base::StringRef identity) { auto skip = [] { - std::lock_guard lock(g_stats_mutex); + base::LockGuard lock(g_stats_mutex); ++g_stats.skipped; return false; }; @@ -400,11 +404,11 @@ bool CompressTexture(Texture* texture, TextureRole role, std::string_view identi const TextureFormat format = FormatForRole(*texture, role); if (BlockBytes(format) == 0) return skip(); - const std::string key = CacheKey(*texture, format); - const fs::path cache_path = fs::path(CacheRoot()) / (key + ".rxtc"); + const base::String key = CacheKey(*texture, format); + const base::String cache_path = fs::Join(CacheRoot(), key + ".rxtc"); const u64 source_bytes = texture->data.size(); if (!identity.empty() && ReadCache(cache_path, texture, format)) { - std::lock_guard lock(g_stats_mutex); + base::LockGuard lock(g_stats_mutex); ++g_stats.compressed; ++g_stats.cache_hits; g_stats.source_bytes += source_bytes; @@ -412,7 +416,7 @@ bool CompressTexture(Texture* texture, TextureRole role, std::string_view identi return true; } - const auto started = std::chrono::steady_clock::now(); + const base::TimeTicks started = base::TimeTicks::Now(); const u32 mip_count = FullMipChainLength(texture->width, texture->height); u64 total = 0; { @@ -420,8 +424,8 @@ bool CompressTexture(Texture* texture, TextureRole role, std::string_view identi u32 h = texture->height; for (u32 mip = 0; mip < mip_count; ++mip) { total += SurfaceBytes(format, w, h); - w = std::max(1u, w / 2); - h = std::max(1u, h / 2); + w = rx::Max(1u, w / 2); + h = rx::Max(1u, h / 2); } } base::Vector out(static_cast(total)); @@ -431,8 +435,8 @@ bool CompressTexture(Texture* texture, TextureRole role, std::string_view identi // averaged xy shorten is what makes a minified normal map flatten instead of // sparkle. const bool filter_srgb = texture->is_srgb; - std::vector level(texture->data.data(), texture->data.data() + expected); - std::vector next; + base::Vector level(texture->data.data(), texture->data.data() + expected); + base::Vector next; u32 w = texture->width; u32 h = texture->height; u64 offset = 0; @@ -440,8 +444,8 @@ bool CompressTexture(Texture* texture, TextureRole role, std::string_view identi EncodeSurface(level.data(), w, h, format, out.data() + offset); offset += SurfaceBytes(format, w, h); if (mip + 1 == mip_count) break; - const u32 nw = std::max(1u, w / 2); - const u32 nh = std::max(1u, h / 2); + const u32 nw = rx::Max(1u, w / 2); + const u32 nh = rx::Max(1u, h / 2); next.resize(static_cast(nw) * nh * 4); Downsample(level.data(), w, h, next.data(), nw, nh, filter_srgb); level.swap(next); @@ -451,11 +455,11 @@ bool CompressTexture(Texture* texture, TextureRole role, std::string_view identi texture->format = format; texture->mip_count = mip_count; - texture->data = std::move(out); - const f64 seconds = std::chrono::duration(std::chrono::steady_clock::now() - started).count(); + texture->data = base::move(out); + const f64 seconds = (base::TimeTicks::Now() - started).InSecondsF(); if (!identity.empty()) WriteCache(cache_path, *texture); { - std::lock_guard lock(g_stats_mutex); + base::LockGuard lock(g_stats_mutex); ++g_stats.compressed; g_stats.source_bytes += source_bytes; g_stats.compressed_bytes += texture->data.size(); @@ -465,7 +469,7 @@ bool CompressTexture(Texture* texture, TextureRole role, std::string_view identi } TextureCompressionStats CompressionTotals() { - std::lock_guard lock(g_stats_mutex); + base::LockGuard lock(g_stats_mutex); return g_stats; } diff --git a/engine/asset/texture_compress.h b/engine/asset/texture_compress.h index 70fcdebb..14d1b4f8 100644 --- a/engine/asset/texture_compress.h +++ b/engine/asset/texture_compress.h @@ -1,10 +1,8 @@ #ifndef RX_ASSET_TEXTURE_COMPRESS_H_ #define RX_ASSET_TEXTURE_COMPRESS_H_ -#include -#include - #include "asset/texture.h" +#include "base/strings/string_ref.h" #include "core/export.h" namespace rx::asset { @@ -82,7 +80,7 @@ RX_ASSET_EXPORT TextureCompressionOptions TextureCompressionSettings(); // renderer prints the tally, because a change that quietly compressed nothing // would otherwise look exactly like one that worked. RX_ASSET_EXPORT bool CompressTexture(Texture* texture, TextureRole role, - std::string_view identity); + base::StringRef identity); struct TextureCompressionStats { u64 source_bytes = 0; // rgba8 mip 0 handed in diff --git a/engine/asset/usd_loader.cc b/engine/asset/usd_loader.cc index 88e262dd..65a12022 100644 --- a/engine/asset/usd_loader.cc +++ b/engine/asset/usd_loader.cc @@ -1,19 +1,29 @@ #include "asset/usd_loader.h" -#include -#include -#include -#include +#include +#include +#include + +// tinyusdz speaks std::string, std::vector, std::map and (for composition) +// std::unique_ptr. Those types appear in this file only where one of its +// signatures requires them; everything the engine keeps is base. #include +#include #include +#include +#include "core/file_system.h" #include "core/log.h" #if defined(RX_HAVE_USD) -#include -#include #include "asset/asset_id.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/string_ref.h" +#include "core/format.h" +#include "core/scalar.h" #include #include @@ -29,19 +39,19 @@ namespace rx::asset { -bool IsUsdPath(std::string_view path) { +bool IsUsdPath(base::StringRef path) { const size_t dot = path.find_last_of('.'); - if (dot == std::string_view::npos) + if (dot == base::StringRef::npos) return false; - std::string ext(path.substr(dot)); + base::String ext(path.substr(dot)); for (char &c : ext) - c = static_cast(std::tolower(static_cast(c))); + c = static_cast(::tolower(static_cast(c))); return ext == ".usd" || ext == ".usda" || ext == ".usdc" || ext == ".usdz"; } #if !defined(RX_HAVE_USD) -bool LoadUsdScene(const std::string &path, ImportedScene *, +bool LoadUsdScene(const base::String &path, ImportedScene *, const UsdLoadOptions &) { RX_ERROR("usd {}: this build has no USD support, reconfigure with RX_USD=ON", path); @@ -53,8 +63,8 @@ bool LoadUsdScene(const std::string &path, ImportedScene *, namespace { namespace tt = tinyusdz::tydra; -AssetId ScopedId(const std::string &path, const char *kind, size_t index) { - return MakeAssetId(path + "#" + kind + std::to_string(index)); +AssetId ScopedId(const base::String &path, const char *kind, size_t index) { + return MakeAssetId(path + "#" + kind + rx::ToString(index)); } // Tydra decodes images into RenderScene::buffers keeping the source channel @@ -105,7 +115,7 @@ bool ConvertImage(const tt::RenderScene &scene, const tt::TextureImage &image, d[3] = 255; break; default: - std::memcpy(d, s, 4); + base::MemCopy(d, s, 4); break; } } @@ -393,13 +403,13 @@ bool ConvertMesh(const tt::RenderMesh &src, vertex.position[0] = src.points[v][0]; vertex.position[1] = src.points[v][1]; vertex.position[2] = src.points[v][2]; - lo = {std::min(lo.x, vertex.position[0]), std::min(lo.y, vertex.position[1]), - std::min(lo.z, vertex.position[2])}; - hi = {std::max(hi.x, vertex.position[0]), std::max(hi.y, vertex.position[1]), - std::max(hi.z, vertex.position[2])}; + lo = {rx::Min(lo.x, vertex.position[0]), rx::Min(lo.y, vertex.position[1]), + rx::Min(lo.z, vertex.position[2])}; + hi = {rx::Max(hi.x, vertex.position[0]), rx::Max(hi.y, vertex.position[1]), + rx::Max(hi.z, vertex.position[2])}; if (normals) - std::memcpy(vertex.normal, normals + v * 3, 3 * sizeof(f32)); + base::MemCopy(vertex.normal, normals + v * 3, 3 * sizeof(f32)); else vertex.normal[1] = 1.0f; @@ -410,7 +420,7 @@ bool ConvertMesh(const tt::RenderMesh &src, } if (tangents) { - std::memcpy(vertex.tangent, tangents + v * 3, 3 * sizeof(f32)); + base::MemCopy(vertex.tangent, tangents + v * 3, 3 * sizeof(f32)); // Handedness the shader needs to rebuild the bitangent: compare the // authored binormal against the one the TBN would generate. f32 w = 1.0f; @@ -430,7 +440,7 @@ bool ConvertMesh(const tt::RenderMesh &src, if (colors) { const f32 alpha = opacities ? opacities[v] : 1.0f; const auto quantize = [](f32 c) { - return static_cast(std::clamp(c, 0.0f, 1.0f) * 255.0f + 0.5f); + return static_cast(rx::Clamp(c, 0.0f, 1.0f) * 255.0f + 0.5f); }; vertex.color = quantize(colors[v * 3 + 0]) | (quantize(colors[v * 3 + 1]) << 8) | @@ -444,7 +454,7 @@ bool ConvertMesh(const tt::RenderMesh &src, out->bounds_center[2] = (lo.z + hi.z) * 0.5f; const Vec3 extent{hi.x - lo.x, hi.y - lo.y, hi.z - lo.z}; out->bounds_radius = - 0.5f * std::sqrt(extent.x * extent.x + extent.y * extent.y + + 0.5f * ::sqrt(extent.x * extent.x + extent.y * extent.y + extent.z * extent.z); const auto material_of = [&](int id) -> AssetId { @@ -473,7 +483,8 @@ bool ConvertMesh(const tt::RenderMesh &src, // A materialBind GeomSubset selects faces, not index ranges, so gather each // subset's triangles into one contiguous run and give the faces no subset // claims a trailing submesh on the mesh-wide material. - std::vector claimed(triangles, false); + base::Vector claimed; + claimed.resize(triangles, false); for (const auto &[name, subset] : src.material_subsetMap) { Submesh submesh; submesh.index_offset = static_cast(lod.indices.size()); @@ -505,7 +516,7 @@ bool ConvertMesh(const tt::RenderMesh &src, if (lod.submeshes.empty()) return false; - out->lods.push_back(std::move(lod)); + out->lods.push_back(base::move(lod)); return true; } @@ -528,7 +539,7 @@ ImportedScene::Instance MakeInstance(u32 mesh_index, const Mat4 &world) { instance.position = {world.m[12], world.m[13], world.m[14]}; const auto axis_length = [&](int col) { const f32 *c = &world.m[col * 4]; - return std::sqrt(c[0] * c[0] + c[1] * c[1] + c[2] * c[2]); + return ::sqrt(c[0] * c[0] + c[1] * c[1] + c[2] * c[2]); }; instance.scale = (axis_length(0) + axis_length(1) + axis_length(2)) / 3.0f; const Quat rotation = QuatFromMat4(world); @@ -587,8 +598,9 @@ bool LoadTextureAsset(const tinyusdz::value::AssetPath &asset_path, const tinyusdz::AssetResolutionResolver &, tt::TextureImage *out, std::vector *pixels, void *userdata, std::string *, std::string *err) { - const auto *base_dir = static_cast(userdata); - const std::string asset = asset_path.GetAssetPath(); + const auto *base_dir = static_cast(userdata); + const std::string &authored = asset_path.GetAssetPath(); + const base::String asset(authored.data(), authored.size()); if (asset.empty()) { if (err) *err += "empty texture asset path\n"; @@ -598,27 +610,30 @@ bool LoadTextureAsset(const tinyusdz::value::AssetPath &asset_path, // Composition rewrites a referenced layer's asset paths to be reachable from // the process working directory, so the path usually resolves as authored; // one that does not is layer-relative and needs the stage's directory. - std::filesystem::path resolved(asset); - if (!std::filesystem::exists(resolved) && resolved.is_relative() && base_dir && + base::String resolved = asset; + if (!fs::Exists(resolved) && !fs::IsAbsolute(resolved) && base_dir && !base_dir->empty()) { - resolved = std::filesystem::path(*base_dir) / asset; + resolved = fs::Join(*base_dir, asset); } - if (!std::filesystem::exists(resolved)) { + if (!fs::Exists(resolved)) { if (err) - *err += "texture not found: " + asset + "\n"; + *err += "texture not found: " + authored + "\n"; return false; } int width = 0, height = 0, channels = 0; stbi_uc *decoded = - stbi_load(resolved.string().c_str(), &width, &height, &channels, 0); + stbi_load(resolved.c_str(), &width, &height, &channels, 0); if (!decoded) { - if (err) - *err += "could not decode texture: " + resolved.string() + "\n"; + if (err) { + *err += "could not decode texture: "; + *err += resolved.c_str(); + *err += "\n"; + } return false; } - out->asset_identifier = resolved.string(); + out->asset_identifier = resolved.c_str(); out->width = width; out->height = height; out->channels = channels; @@ -648,7 +663,7 @@ u32 NormalizeColorSpaceTokens(tinyusdz::PrimSpec &spec) { const std::string authored = metas.get_colorSpace().str(); std::string lowered = authored; for (char &c : lowered) - c = static_cast(std::tolower(static_cast(c))); + c = static_cast(::tolower(static_cast(c))); if (lowered != authored) { metas.set_colorSpace(lowered); ++changed; @@ -661,7 +676,7 @@ u32 NormalizeColorSpaceTokens(tinyusdz::PrimSpec &spec) { Vec3 TransformDirectionNormalized(const Mat4 &m, const Vec3 &v) { Vec3 d = TransformDir(m, v); - const f32 len = std::sqrt(d.x * d.x + d.y * d.y + d.z * d.z); + const f32 len = ::sqrt(d.x * d.x + d.y * d.y + d.z * d.z); if (len > 1e-8f) { d.x /= len; d.y /= len; @@ -674,23 +689,21 @@ Vec3 TransformDirectionNormalized(const Mat4 &m, const Vec3 &v) { // declares it, which for a lighting rig kept in a subdirectory is one level // below the root stage. Try the authored spelling, then the stage directory, // then the stage directory with the leading parent hops folded away. -std::string ResolveLightTexture(const std::string &asset, - const std::string &base_dir) { +base::String ResolveLightTexture(base::StringRef asset, base::StringRef base_dir) { if (asset.empty()) return {}; - namespace fs = std::filesystem; - if (fs::exists(asset)) - return asset; + if (fs::Exists(asset)) + return base::String(asset); if (!base_dir.empty()) { - const fs::path direct = fs::path(base_dir) / asset; - if (fs::exists(direct)) - return direct.string(); - std::string trimmed = asset; - while (trimmed.rfind("../", 0) == 0) + base::String direct = fs::Join(base_dir, asset); + if (fs::Exists(direct)) + return direct; + base::StringRef trimmed = asset; + while (trimmed.starts_with("../")) trimmed = trimmed.substr(3); - const fs::path folded = fs::path(base_dir) / trimmed; - if (fs::exists(folded)) - return folded.string(); + base::String folded = fs::Join(base_dir, trimmed); + if (fs::Exists(folded)) + return folded; } return {}; } @@ -699,7 +712,7 @@ std::string ResolveLightTexture(const std::string &asset, // normalized to unit luminance. Rows are weighted by sin(theta) because an // equirect image oversamples the poles; without that a bright horizon sun reads // as far less of the average than it really is. -bool AverageEnvmapColor(const std::string &path, f32 out_rgb[3]) { +bool AverageEnvmapColor(const base::String &path, f32 out_rgb[3]) { int width = 0, height = 0, channels = 0; f32 *pixels = stbi_loadf(path.c_str(), &width, &height, &channels, 3); if (!pixels) @@ -708,17 +721,17 @@ bool AverageEnvmapColor(const std::string &path, f32 out_rgb[3]) { f64 sum[3] = {0, 0, 0}; f64 weight_total = 0; // A few hundred rows is plenty for an average and keeps a 4k map cheap. - const int row_step = std::max(1, height / 256); - const int col_step = std::max(1, width / 512); + const int row_step = rx::Max(1, height / 256); + const int col_step = rx::Max(1, width / 512); for (int y = 0; y < height; y += row_step) { const f64 theta = (static_cast(y) + 0.5) / height * 3.14159265358979; - const f64 weight = std::sin(theta); + const f64 weight = ::sin(theta); for (int x = 0; x < width; x += col_step) { const f32 *p = pixels + (static_cast(y) * width + x) * 3; // Guard against inf/nan, which show up in the wild in exr-sourced hdr. for (int c = 0; c < 3; ++c) { const f32 v = p[c]; - if (std::isfinite(v) && v > 0.0f) sum[c] += weight * v; + if (::isfinite(v) && v > 0.0f) sum[c] += weight * v; } weight_total += weight; } @@ -759,21 +772,22 @@ const char *const kLuxLegacyInputs[] = { "shaping:ies:angleScale", "shaping:ies:normalize", }; -bool IsLightTypeName(const std::string &type_name) { +bool IsLightTypeName(base::StringRef type_name) { // UsdLuxDomeLight, SphereLight, RectLight, DiskLight, CylinderLight, // DistantLight, GeometryLight, PortalLight - all end in "Light". - return type_name.size() > 5 && - type_name.compare(type_name.size() - 5, 5, "Light") == 0; + return type_name.size() > 5 && type_name.ends_with("Light"); } // Renames legacy light attributes and records prims hidden by `visibility`. // Visibility is inherited, so an invisible ancestor hides the whole subtree; // the Attic keeps a full second lighting rig and a 500-bulb string-light strand // switched off exactly this way, and importing them would double-light it. -u32 NormalizeLuxSchema(tinyusdz::PrimSpec &spec, const std::string &parent_path, +u32 NormalizeLuxSchema(tinyusdz::PrimSpec &spec, base::StringRef parent_path, bool parent_hidden, - base::Vector *hidden_paths) { - const std::string path = parent_path + "/" + spec.name(); + base::Vector *hidden_paths) { + base::String path(parent_path); + path.append("/"); + path.append(spec.name().data(), spec.name().size()); bool hidden = parent_hidden; if (!hidden) { @@ -797,7 +811,7 @@ u32 NormalizeLuxSchema(tinyusdz::PrimSpec &spec, const std::string &parent_path, if (spec.props().count(modern)) continue; // authored both ways: keep new tinyusdz::Property moved = it->second; spec.props().erase(it); - spec.props().emplace(modern, std::move(moved)); + spec.props().emplace(modern, base::move(moved)); ++renamed; } } @@ -807,19 +821,18 @@ u32 NormalizeLuxSchema(tinyusdz::PrimSpec &spec, const std::string &parent_path, return renamed; } -bool CoveredBy(const std::string &abs_path, - const base::Vector &prefixes) { - for (const std::string &prefix : prefixes) { - if (abs_path.size() >= prefix.size() && - abs_path.compare(0, prefix.size(), prefix) == 0 && +bool CoveredBy(base::StringRef abs_path, + const base::Vector &prefixes) { + for (const base::String &prefix : prefixes) { + if (abs_path.starts_with(prefix) && (abs_path.size() == prefix.size() || abs_path[prefix.size()] == '/')) return true; } return false; } -bool IsHidden(const std::string &abs_path, - const base::Vector &hidden_paths, +bool IsHidden(base::StringRef abs_path, + const base::Vector &hidden_paths, const UsdLoadOptions &options) { if (CoveredBy(abs_path, options.hide)) return true; if (CoveredBy(abs_path, options.show)) return false; @@ -828,8 +841,8 @@ bool IsHidden(const std::string &abs_path, void ConvertLights(const tt::RenderScene &scene, const Mat4 &stage_to_engine, f32 meters_per_unit, - const base::Vector &hidden_paths, - const UsdLoadOptions &options, const std::string &base_dir, + const base::Vector &hidden_paths, + const UsdLoadOptions &options, base::StringRef base_dir, ImportedScene *out) { using Kind = ImportedScene::Light::Kind; u32 skipped = 0; @@ -889,22 +902,22 @@ void ConvertLights(const tt::RenderScene &scene, const Mat4 &stage_to_engine, f32 rgb[3] = {3.2406f * X - 1.5372f * Y - 0.4986f * Z, -0.9689f * X + 1.8758f * Y + 0.0415f * Z, 0.0557f * X - 0.2040f * Y + 1.0570f * Z}; - f32 peak = std::max(rgb[0], std::max(rgb[1], rgb[2])); + f32 peak = rx::Max(rgb[0], rx::Max(rgb[1], rgb[2])); if (peak <= 0.0f) peak = 1.0f; // UsdLux multiplies the blackbody colour into `inputs:color`; it does not // replace it (see tinyusdz usdLux.cc GetColorTemperatureRGB callers). for (int c = 0; c < 3; ++c) - light.color[c] *= std::max(0.0f, rgb[c] / peak); + light.color[c] *= rx::Max(0.0f, rgb[c] / peak); } - out->lights.push_back(std::move(light)); + out->lights.push_back(base::move(light)); } if (skipped) RX_DEBUG("usd: skipped {} hidden or unsupported light(s)", skipped); } void ConvertCameras(const tt::RenderScene &scene, const Mat4 &stage_to_engine, - const base::Vector &hidden_paths, + const base::Vector &hidden_paths, const UsdLoadOptions &options, ImportedScene *out) { // RenderCamera carries no transform of its own - Tydra keeps it on the node - // so pair each camera with the node that addresses it. @@ -944,7 +957,7 @@ void ConvertCameras(const tt::RenderScene &scene, const Mat4 &stage_to_engine, camera.rotation[2] = rotation.z; camera.rotation[3] = rotation.w; if (src.focalLength > 1e-6f) { - camera.yfov = 2.0f * std::atan(0.5f * src.verticalAperture / src.focalLength); + camera.yfov = 2.0f * ::atan(0.5f * src.verticalAperture / src.focalLength); } camera.znear = src.znear; camera.zfar = src.zfar; @@ -1030,12 +1043,12 @@ void ParseRenderSettings(const tinyusdz::Layer &layer, // default options and so rejects the parent-relative asset paths below. bool ComposeStage(const std::string &path, const std::string &base_dir, tinyusdz::Stage *stage, - base::Vector *hidden_paths, + base::Vector *hidden_paths, ImportedScene::RenderSettings *render_settings) { std::string warn, err; tinyusdz::Layer layer; if (!tinyusdz::LoadLayerFromFile(path, &layer, &warn, &err)) { - RX_ERROR("usd {}: {}", path, err.empty() ? "failed to open layer" : err); + RX_ERROR("usd {}: {}", path.c_str(), err.empty() ? "failed to open layer" : err.c_str()); return false; } @@ -1065,10 +1078,10 @@ bool ComposeStage(const std::string &path, const std::string &base_dir, tinyusdz::Layer composited; if (!tinyusdz::CompositeSublayers(resolver, layer, &composited, &warn, &err, sublayer_options)) { - RX_ERROR("usd {}: subLayer composition failed: {}", path, err); + RX_ERROR("usd {}: subLayer composition failed: {}", path.c_str(), err.c_str()); return false; } - layer = std::move(composited); + layer = base::move(composited); } bool settled = false; @@ -1082,32 +1095,32 @@ bool ComposeStage(const std::string &path, const std::string &base_dir, if (layer.check_unresolved_references()) { pending = true; if (!tinyusdz::CompositeReferencesInPlace( - resolver, std::make_unique(std::move(layer)), + resolver, std::make_unique(base::move(layer)), &composited, &warn, &err, reference_options)) { - RX_ERROR("usd {}: `references` composition failed: {}", path, err); + RX_ERROR("usd {}: `references` composition failed: {}", path.c_str(), err.c_str()); return false; } - layer = std::move(composited); + layer = base::move(composited); } if (layer.check_unresolved_payload()) { pending = true; if (!tinyusdz::CompositePayloadInPlace( - resolver, std::make_unique(std::move(layer)), + resolver, std::make_unique(base::move(layer)), &composited, &warn, &err, payload_options)) { - RX_ERROR("usd {}: `payload` composition failed: {}", path, err); + RX_ERROR("usd {}: `payload` composition failed: {}", path.c_str(), err.c_str()); return false; } - layer = std::move(composited); + layer = base::move(composited); } if (layer.check_unresolved_inherits()) { pending = true; if (!tinyusdz::CompositeInherits(layer, &composited, &warn, &err)) { - RX_ERROR("usd {}: `inherits` composition failed: {}", path, err); + RX_ERROR("usd {}: `inherits` composition failed: {}", path.c_str(), err.c_str()); return false; } - layer = std::move(composited); + layer = base::move(composited); } if (layer.check_unresolved_variant()) { @@ -1117,20 +1130,20 @@ bool ComposeStage(const std::string &path, const std::string &base_dir, // those to resolve rather than binding to an empty placeholder. if (!tinyusdz::ShouldDeferVariantComposition(layer)) { if (!tinyusdz::CompositeVariant(layer, &composited, &warn, &err)) { - RX_ERROR("usd {}: `variantSet` composition failed: {}", path, err); + RX_ERROR("usd {}: `variantSet` composition failed: {}", path.c_str(), err.c_str()); return false; } - layer = std::move(composited); + layer = base::move(composited); } } if (layer.check_unresolved_specializes()) { pending = true; if (!tinyusdz::CompositeSpecializes(layer, &composited, &warn, &err)) { - RX_ERROR("usd {}: `specializes` composition failed: {}", path, err); + RX_ERROR("usd {}: `specializes` composition failed: {}", path.c_str(), err.c_str()); return false; } - layer = std::move(composited); + layer = base::move(composited); } settled = !pending; @@ -1138,7 +1151,7 @@ bool ComposeStage(const std::string &path, const std::string &base_dir, if (!settled) { RX_WARN("usd {}: composition did not settle in {} passes; some arcs are " "left unresolved", - path, kMaxCompositionPasses); + path.c_str(), kMaxCompositionPasses); } // The layer is fully composed now, so bake the apiSchemas list-ops to // explicit form the way `usdcat --flatten` does. @@ -1148,24 +1161,24 @@ bool ComposeStage(const std::string &path, const std::string &base_dir, for (auto &[name, spec] : layer.primspecs()) recased += NormalizeColorSpaceTokens(spec); if (recased > 0) - RX_DEBUG("usd {}: normalized {} colorSpace token(s)", path, recased); + RX_DEBUG("usd {}: normalized {} colorSpace token(s)", path.c_str(), recased); u32 relux = 0; for (auto &[name, spec] : layer.primspecs()) relux += NormalizeLuxSchema(spec, "", false, hidden_paths); if (relux > 0) RX_DEBUG("usd {}: moved {} pre-21.02 light attribute(s) into `inputs:`", - path, relux); + path.c_str(), relux); if (!warn.empty()) - RX_WARN("usd {}: {}", path, warn); + RX_WARN("usd {}: {}", path.c_str(), warn.c_str()); // Read before LayerToStage: the layer is moved from there. ParseRenderSettings(layer, render_settings); - if (!tinyusdz::LayerToStage(std::move(layer), stage, &warn, &err)) { + if (!tinyusdz::LayerToStage(base::move(layer), stage, &warn, &err)) { RX_ERROR("usd {}: could not build a stage from the composed layer: {}", - path, err); + path.c_str(), err.c_str()); return false; } return true; @@ -1173,26 +1186,28 @@ bool ComposeStage(const std::string &path, const std::string &base_dir, } // namespace -bool LoadUsdScene(const std::string &path, ImportedScene *out, +bool LoadUsdScene(const base::String &path, ImportedScene *out, const UsdLoadOptions &options) { std::string warn, err; tinyusdz::Stage stage; - base::Vector hidden_paths; - const std::string base_dir = tinyusdz::io::GetBaseDir(path); - const bool is_usdz = tinyusdz::IsUSDZ(path); + base::Vector hidden_paths; + const std::string usd_path(path.data(), path.size()); + const std::string base_dir = tinyusdz::io::GetBaseDir(usd_path); + const base::String texture_base_dir(base_dir.data(), base_dir.size()); + const bool is_usdz = tinyusdz::IsUSDZ(usd_path); // usdz goes through the same path as a loose stage: a package selects its own // root layer (first .usdc, else first .usda), and tinyusdz's layer reader // picks it the same way its stage reader does. Reading it as a layer is what // gives a package the legacy-UsdLux normalization and the visibility pass // below - loading it straight to a Stage skips both, so packaged legacy // lights reconstruct as defaults and authored-invisible prims stay lit. - if (!ComposeStage(path, base_dir, &stage, &hidden_paths, + if (!ComposeStage(usd_path, base_dir, &stage, &hidden_paths, &out->render_settings)) { return false; } tt::RenderSceneConverterEnv env(stage); - env.usd_filename = path; + env.usd_filename = usd_path; env.mesh_config.triangulate = true; // Make every vertex attribute share one index buffer, which is what the // engine's vertex format needs; without it uvs and normals stay facevarying. @@ -1207,14 +1222,14 @@ bool LoadUsdScene(const std::string &path, ImportedScene *out, // resolver, which is the only thing that can see those entries. env.material_config.texture_image_loader_function = LoadTextureAsset; env.material_config.texture_image_loader_function_userdata = - const_cast(&base_dir); + const_cast(&texture_base_dir); } tinyusdz::USDZAsset usdz_asset; if (is_usdz) { // Textures live inside the zip; resolve them through it. - if (!tinyusdz::ReadUSDZAssetInfoFromFile(path, &usdz_asset, &warn, &err)) { - RX_ERROR("usd {}: unreadable usdz package: {}", path, err); + if (!tinyusdz::ReadUSDZAssetInfoFromFile(usd_path, &usdz_asset, &warn, &err)) { + RX_ERROR("usd {}: unreadable usdz package: {}", path, err.c_str()); return false; } if (!tinyusdz::SetupUSDZAssetResolution(env.asset_resolver, &usdz_asset)) { @@ -1228,11 +1243,11 @@ bool LoadUsdScene(const std::string &path, ImportedScene *out, tt::RenderScene render_scene; tt::RenderSceneConverter converter; if (!converter.ConvertToRenderScene(env, &render_scene)) { - RX_ERROR("usd {}: {}", path, converter.GetError()); + RX_ERROR("usd {}: {}", path, converter.GetError().c_str()); return false; } if (const std::string convert_warn = converter.GetWarning(); !convert_warn.empty()) - RX_WARN("usd {}: {}", path, convert_warn); + RX_WARN("usd {}: {}", path, convert_warn.c_str()); base::Vector image_ids; image_ids.resize(static_cast(render_scene.images.size())); @@ -1245,7 +1260,7 @@ bool LoadUsdScene(const std::string &path, ImportedScene *out, continue; } image_ids[static_cast(i)] = texture.id; - out->textures.push_back(std::move(texture)); + out->textures.push_back(base::move(texture)); } base::Vector material_ids; @@ -1262,7 +1277,7 @@ bool LoadUsdScene(const std::string &path, ImportedScene *out, ConvertMaterial(render_scene, render_scene.materials[i], image_ids, &material); material_ids[static_cast(i)] = material.id; - out->materials.push_back(std::move(material)); + out->materials.push_back(base::move(material)); } // Mesh indices have to stay aligned with RenderScene::meshes because nodes @@ -1274,9 +1289,9 @@ bool LoadUsdScene(const std::string &path, ImportedScene *out, if (!ConvertMesh(render_scene.meshes[i], material_ids, &mesh)) { ++skipped_meshes; RX_DEBUG("usd {}: skipped mesh {}", path, - render_scene.meshes[i].abs_path); + render_scene.meshes[i].abs_path.c_str()); } - out->meshes.push_back(std::move(mesh)); + out->meshes.push_back(base::move(mesh)); } // Normalize the stage's own conventions away: rotate a z-up stage into the @@ -1312,7 +1327,7 @@ bool LoadUsdScene(const std::string &path, ImportedScene *out, "{} lights, {} cameras (upAxis {}, {} m/unit)", path, out->meshes.size() - skipped_meshes, out->materials.size(), out->textures.size(), out->instances.size(), out->lights.size(), - out->cameras.size(), render_scene.meta.upAxis, meters_per_unit); + out->cameras.size(), render_scene.meta.upAxis.c_str(), meters_per_unit); if (openpbr_shaders) RX_INFO("usd {}: {} OpenPBR material(s)", path, openpbr_shaders); if (skipped_meshes || undecoded || unsupported_shaders || walk.mirrored) { diff --git a/engine/asset/usd_loader.h b/engine/asset/usd_loader.h index 1e7d50d5..2634f522 100644 --- a/engine/asset/usd_loader.h +++ b/engine/asset/usd_loader.h @@ -1,12 +1,12 @@ #ifndef RX_ASSET_USD_LOADER_H_ #define RX_ASSET_USD_LOADER_H_ -#include -#include #include #include "asset/scene_import.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/export.h" namespace rx::asset { @@ -18,13 +18,13 @@ namespace rx::asset { // editing the stage. Paths are absolute prim paths and cover their subtrees; // `hide` wins over `show`. struct UsdLoadOptions { - base::Vector show; - base::Vector hide; + base::Vector show; + base::Vector hide; }; // True for the four OpenUSD file extensions: .usd (either encoding), .usda // (ascii), .usdc (crate binary), .usdz (zip package). -RX_ASSET_EXPORT bool IsUsdPath(std::string_view path); +RX_ASSET_EXPORT bool IsUsdPath(base::StringRef path); // Loads a USD stage into an ImportedScene. Composition (sublayers, references, // payloads, variants, class inherits) is resolved first, then the composed @@ -39,7 +39,7 @@ RX_ASSET_EXPORT bool IsUsdPath(std::string_view path); // Returns false and logs on a stage that fails to open. A stage that opens but // carries geometry the importer cannot represent still returns true, with the // skipped prims logged. -RX_ASSET_EXPORT bool LoadUsdScene(const std::string &path, ImportedScene *out, +RX_ASSET_EXPORT bool LoadUsdScene(const base::String &path, ImportedScene *out, const UsdLoadOptions &options = {}); } // namespace rx::asset diff --git a/engine/asset/vfs.cc b/engine/asset/vfs.cc index d1a5292e..5783bbd8 100644 --- a/engine/asset/vfs.cc +++ b/engine/asset/vfs.cc @@ -1,17 +1,21 @@ #include "asset/vfs.h" -#include -#include -#include -#include +#include #include #include "asset/asset_id.h" +#include "base/memory/move.h" +#include "base/optional.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "base/threading/lock_guard.h" +#include "base/threading/mutex.h" +#include "core/file_system.h" namespace rx::asset { -AssetId MakeAssetId(std::string_view normalized_path) { return AssetId{Fnv1a(normalized_path)}; } +AssetId MakeAssetId(base::StringRef normalized_path) { return AssetId{Fnv1a(normalized_path)}; } namespace { @@ -19,8 +23,8 @@ namespace { // system), lookups from tooling on the main thread. Small and cold, so a plain // mutex is fine. struct PathTable { - std::mutex mutex; - base::UnorderedMap paths; + base::Mutex mutex; + base::UnorderedMap paths; }; PathTable& ThePathTable() { @@ -30,45 +34,44 @@ PathTable& ThePathTable() { } // namespace -void RecordAssetPath(AssetId id, std::string_view normalized_path) { +void RecordAssetPath(AssetId id, base::StringRef normalized_path) { if (!id) return; PathTable& table = ThePathTable(); - std::lock_guard lock(table.mutex); - if (std::string* existing = table.paths.find(id.hash)) - *existing = std::string(normalized_path); + base::LockGuard lock(table.mutex); + if (base::String* existing = table.paths.find(id.hash)) + *existing = base::String(normalized_path); else - table.paths.emplace(id.hash, std::string(normalized_path)); + table.paths.emplace(id.hash, base::String(normalized_path)); } -std::optional LookupAssetPath(AssetId id) { +base::Optional LookupAssetPath(AssetId id) { PathTable& table = ThePathTable(); - std::lock_guard lock(table.mutex); - if (const std::string* found = table.paths.find(id.hash)) return *found; - return std::nullopt; + base::LockGuard lock(table.mutex); + if (const base::String* found = table.paths.find(id.hash)) return *found; + return base::nullopt; } -std::string NormalizePath(std::string_view path) { - std::string out(path); - std::ranges::transform(out, out.begin(), [](char c) { - if (c == '\\') return '/'; - return static_cast(std::tolower(static_cast(c))); - }); +base::String NormalizePath(base::StringRef path) { + base::String out(path); + for (char& c : out) { + c = c == '\\' ? '/' : static_cast(::tolower(static_cast(c))); + } return out; } -VirtualPath SplitVirtualPath(std::string_view path) { +VirtualPath SplitVirtualPath(base::StringRef path) { const size_t pos = path.find("://"); // A mount name is a bare identifier: "a/b://c" or "://c" is not a mount. - if (pos == std::string_view::npos || pos == 0 || - path.substr(0, pos).find_first_of("/\\") != std::string_view::npos) { - return {std::string_view(), path}; + if (pos == base::StringRef::npos || pos == 0 || + path.substr(0, pos).find_first_of("/\\") != base::StringRef::npos) { + return {base::StringRef(), path}; } return {path.substr(0, pos), path.substr(pos + 3)}; } -std::optional FileProvider::Size(std::string_view normalized_path) const { - const std::optional> bytes = Read(normalized_path); - if (!bytes) return std::nullopt; +base::Optional FileProvider::Size(base::StringRef normalized_path) const { + const base::Optional> bytes = Read(normalized_path); + if (!bytes) return base::nullopt; return bytes->size(); } @@ -77,11 +80,11 @@ namespace { // A mount point ("", "game", "game://", "game://dlc/") split into its // normalized mount name and subtree prefix (trailing slash enforced). struct MountPoint { - std::string mount; - std::string prefix; + base::String mount; + base::String prefix; }; -MountPoint ParseMountPoint(std::string_view mount_point) { +MountPoint ParseMountPoint(base::StringRef mount_point) { const VirtualPath split = SplitVirtualPath(mount_point); MountPoint out; if (split.mount.empty()) { @@ -97,16 +100,16 @@ MountPoint ParseMountPoint(std::string_view mount_point) { } // namespace -void Vfs::Mount(std::string_view mount_point, base::UniquePointer provider) { +void Vfs::Mount(base::StringRef mount_point, base::UniquePointer provider) { MountPoint at = ParseMountPoint(mount_point); - mounts_.push_back(MountEntry{std::move(at.mount), std::move(at.prefix), std::move(provider)}); + mounts_.push_back(MountEntry{base::move(at.mount), base::move(at.prefix), base::move(provider)}); } void Vfs::Mount(base::UniquePointer provider) { - Mount(std::string_view(), std::move(provider)); + Mount(base::StringRef(), base::move(provider)); } -size_t Vfs::Unmount(std::string_view mount_point) { +size_t Vfs::Unmount(base::StringRef mount_point) { const MountPoint at = ParseMountPoint(mount_point); base::Vector kept; size_t removed = 0; @@ -114,74 +117,73 @@ size_t Vfs::Unmount(std::string_view mount_point) { if (mounts_[i].mount == at.mount && mounts_[i].prefix == at.prefix) { ++removed; } else { - kept.push_back(std::move(mounts_[i])); + kept.push_back(base::move(mounts_[i])); } } - mounts_ = std::move(kept); + mounts_ = base::move(kept); return removed; } -size_t Vfs::UnmountByPrefix(std::string_view prefix) { +size_t Vfs::UnmountByPrefix(base::StringRef prefix) { base::Vector kept; size_t removed = 0; for (size_t i = 0; i < mounts_.size(); ++i) { - const std::string name = mounts_[i].provider->name(); - if (name.size() >= prefix.size() && - name.compare(0, prefix.size(), prefix.data(), prefix.size()) == 0) { + const base::String name = mounts_[i].provider->name(); + if (base::StringRef(name).starts_with(prefix)) { ++removed; } else { - kept.push_back(std::move(mounts_[i])); + kept.push_back(base::move(mounts_[i])); } } - mounts_ = std::move(kept); + mounts_ = base::move(kept); return removed; } -const FileProvider* Vfs::Resolve(std::string_view path, std::string* relative) const { +const FileProvider* Vfs::Resolve(base::StringRef path, base::String* relative) const { const VirtualPath split = SplitVirtualPath(path); - const std::string mount = NormalizePath(split.mount); - const std::string normalized = NormalizePath(split.path); + const base::String mount = NormalizePath(split.mount); + const base::String normalized = NormalizePath(split.path); for (size_t i = mounts_.size(); i-- > 0;) { const MountEntry& entry = mounts_[i]; if (entry.mount != mount) continue; if (!normalized.starts_with(entry.prefix)) continue; - std::string sub = normalized.substr(entry.prefix.size()); + base::String sub = normalized.substr(entry.prefix.size()); if (!entry.provider->Contains(sub)) continue; - *relative = std::move(sub); + *relative = base::move(sub); return &*entry.provider; } return nullptr; } -std::optional> Vfs::Read(std::string_view path) const { - std::string relative; +base::Optional> Vfs::Read(base::StringRef path) const { + base::String relative; if (const FileProvider* provider = Resolve(path, &relative)) return provider->Read(relative); - return std::nullopt; + return base::nullopt; } -bool Vfs::Contains(std::string_view path) const { - std::string relative; +bool Vfs::Contains(base::StringRef path) const { + base::String relative; return Resolve(path, &relative) != nullptr; } -std::optional Vfs::Size(std::string_view path) const { - std::string relative; +base::Optional Vfs::Size(base::StringRef path) const { + base::String relative; if (const FileProvider* provider = Resolve(path, &relative)) return provider->Size(relative); - return std::nullopt; + return base::nullopt; } namespace { -void EnumerateEntryAsVirtualPaths(const std::string& mount, const std::string& prefix, +void EnumerateEntryAsVirtualPaths(const base::String& mount, const base::String& prefix, const FileProvider& provider, - const std::function& fn) { + base::FunctionRef fn) { if (mount.empty() && prefix.empty()) { // Root mount: schemeless, exactly the provider's own paths. provider.Enumerate(fn); return; } - std::string full; - provider.Enumerate([&](std::string_view path) { + base::String full; + provider.Enumerate([&](base::StringRef path) { full.clear(); if (!mount.empty()) { full += mount; @@ -195,13 +197,13 @@ void EnumerateEntryAsVirtualPaths(const std::string& mount, const std::string& p } // namespace -void Vfs::Enumerate(const std::function& fn) const { +void Vfs::Enumerate(base::FunctionRef fn) const { for (const MountEntry& entry : mounts_) EnumerateEntryAsVirtualPaths(entry.mount, entry.prefix, *entry.provider, fn); } -void Vfs::EnumerateMount(std::string_view mount_point, - const std::function& fn) const { +void Vfs::EnumerateMount(base::StringRef mount_point, + base::FunctionRef fn) const { const MountPoint at = ParseMountPoint(mount_point); for (const MountEntry& entry : mounts_) { if (entry.mount != at.mount || !entry.prefix.starts_with(at.prefix)) continue; @@ -213,47 +215,41 @@ namespace { class LooseFileProvider final : public FileProvider { public: - explicit LooseFileProvider(std::string root) : root_(std::move(root)) {} + explicit LooseFileProvider(base::String root) : root_(base::move(root)) {} - bool Contains(std::string_view normalized_path) const override { - return std::filesystem::is_regular_file(root_ / std::filesystem::path(normalized_path)); + bool Contains(base::StringRef normalized_path) const override { + return fs::IsRegularFile(fs::Join(root_, normalized_path)); } - std::optional> Read(std::string_view normalized_path) const override { - std::ifstream file(root_ / std::filesystem::path(normalized_path), - std::ios::binary | std::ios::ate); - if (!file) return std::nullopt; - base::Vector data(static_cast(file.tellg())); - file.seekg(0); - file.read(reinterpret_cast(data.data()), static_cast(data.size())); + base::Optional> Read(base::StringRef normalized_path) const override { + base::Vector data; + if (!fs::ReadFile(fs::Join(root_, normalized_path), &data)) return base::nullopt; return data; } - std::optional Size(std::string_view normalized_path) const override { - std::error_code ec; - const auto size = std::filesystem::file_size(root_ / std::filesystem::path(normalized_path), ec); - if (ec) return std::nullopt; - return size; + base::Optional Size(base::StringRef normalized_path) const override { + return fs::FileSize(fs::Join(root_, normalized_path)); } - void Enumerate(const std::function& fn) const override { - std::error_code ec; - for (const auto& entry : std::filesystem::recursive_directory_iterator(root_, ec)) { - if (!entry.is_regular_file()) continue; - fn(NormalizePath(std::filesystem::relative(entry.path(), root_).generic_string())); + void Enumerate(base::FunctionRef fn) const override { + base::Vector entries; + fs::ListDirectory(root_, &entries, /*recursive=*/true); + for (const fs::DirEntry& entry : entries) { + if (!entry.is_regular) continue; + fn(NormalizePath(fs::GenericString(fs::Relative(entry.path, root_)))); } } - std::string name() const override { return root_.string(); } + base::String name() const override { return root_; } private: - std::filesystem::path root_; + base::String root_; }; } // namespace -base::UniquePointer MakeLooseFileProvider(std::string root_directory) { - return base::MakeUnique(std::move(root_directory)); +base::UniquePointer MakeLooseFileProvider(base::String root_directory) { + return base::MakeUnique(base::move(root_directory)); } } // namespace rx::asset diff --git a/engine/asset/vfs.h b/engine/asset/vfs.h index 4d929da5..e3265d6e 100644 --- a/engine/asset/vfs.h +++ b/engine/asset/vfs.h @@ -1,14 +1,14 @@ #ifndef RX_ASSET_VFS_H_ #define RX_ASSET_VFS_H_ -#include -#include -#include -#include #include #include +#include "base/functional/function_ref.h" +#include "base/optional.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/types.h" @@ -20,14 +20,14 @@ class FileProvider { public: virtual ~FileProvider() = default; - virtual bool Contains(std::string_view normalized_path) const = 0; - virtual std::optional> Read(std::string_view normalized_path) const = 0; - virtual void Enumerate(const std::function& fn) const = 0; - virtual std::string name() const = 0; + virtual bool Contains(base::StringRef normalized_path) const = 0; + virtual base::Optional> Read(base::StringRef normalized_path) const = 0; + virtual void Enumerate(base::FunctionRef fn) const = 0; + virtual base::String name() const = 0; // Uncompressed size in bytes. The default reads the whole file; providers // with a table of contents (packs, archives) answer from metadata. - virtual std::optional Size(std::string_view normalized_path) const; + virtual base::Optional Size(base::StringRef normalized_path) const; }; // A virtual path split at its mount point: @@ -35,10 +35,10 @@ class FileProvider { // "textures/a.dds" -> {"", "textures/a.dds"} (the root mount) // Views alias the input string; neither part is normalized yet. struct VirtualPath { - std::string_view mount; - std::string_view path; + base::StringRef mount; + base::StringRef path; }; -RX_ASSET_EXPORT VirtualPath SplitVirtualPath(std::string_view path); +RX_ASSET_EXPORT VirtualPath SplitVirtualPath(base::StringRef path); // Unified virtual filesystem over named mount points. Mount points: // "" / "game" / "game://" the root or a named namespace @@ -51,53 +51,53 @@ RX_ASSET_EXPORT VirtualPath SplitVirtualPath(std::string_view path); // unguarded: quiesce background loads first, or a loader reads a freed provider. class RX_ASSET_EXPORT Vfs { public: - void Mount(std::string_view mount_point, base::UniquePointer provider); + void Mount(base::StringRef mount_point, base::UniquePointer provider); // Legacy: mounts into the root namespace, reachable by schemeless paths. void Mount(base::UniquePointer provider); // Removes every provider mounted at exactly `mount_point`, returning how many // were dropped. - size_t Unmount(std::string_view mount_point); + size_t Unmount(base::StringRef mount_point); // Removes every mounted provider whose name starts with `prefix`, returning how // many were dropped. Lets a caller swap one set of providers (reloaded mods) // for another without disturbing the rest of the stack. Single-threaded with // Read, like Mount. - size_t UnmountByPrefix(std::string_view prefix); + size_t UnmountByPrefix(base::StringRef prefix); // Accept full virtual paths ("game://a/b.dds") or schemeless root paths. - std::optional> Read(std::string_view path) const; - bool Contains(std::string_view path) const; - std::optional Size(std::string_view path) const; + base::Optional> Read(base::StringRef path) const; + bool Contains(base::StringRef path) const; + base::Optional Size(base::StringRef path) const; // Visits every entry across all mounted providers as a full virtual path // (root-mount entries stay schemeless, so pre-mount-point callers see the // same strings as before), with duplicates when an override shadows a base // file. For prefix/suffix discovery like finding the terrain LOD quads of a // worldspace. - void Enumerate(const std::function& fn) const; + void Enumerate(base::FunctionRef fn) const; // Same, restricted to the providers mounted under `mount_point`. - void EnumerateMount(std::string_view mount_point, - const std::function& fn) const; + void EnumerateMount(base::StringRef mount_point, + base::FunctionRef fn) const; size_t mount_count() const { return mounts_.size(); } private: struct MountEntry { - std::string mount; // normalized mount name, "" = root - std::string prefix; // normalized subtree prefix, "" or "dlc/.../" with trailing slash + base::String mount; // normalized mount name, "" = root + base::String prefix; // normalized subtree prefix, "" or "dlc/.../" with trailing slash base::UniquePointer provider; }; - const FileProvider* Resolve(std::string_view path, std::string* relative) const; + const FileProvider* Resolve(base::StringRef path, base::String* relative) const; base::Vector mounts_; }; RX_ASSET_EXPORT base::UniquePointer MakeLooseFileProvider( - std::string root_directory); + base::String root_directory); } // namespace rx::asset diff --git a/engine/audio/audio_clip.cc b/engine/audio/audio_clip.cc index b75b5691..be652ca1 100644 --- a/engine/audio/audio_clip.cc +++ b/engine/audio/audio_clip.cc @@ -1,21 +1,26 @@ #include "audio/audio_clip.h" -#include -#include -#include +#include +#include #include "audio/wav.h" #include "audio/xwma.h" +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/log.h" +#include "core/scalar.h" namespace rx::audio { namespace { -std::string LowerExt(std::string_view extension) { - std::string e(extension); +base::String LowerExt(base::StringRef extension) { + base::String e(extension); if (!e.empty() && e.front() == '.') e.erase(e.begin()); - std::transform(e.begin(), e.end(), e.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); + for (char& c : e) c = static_cast(::tolower(static_cast(c))); return e; } @@ -33,8 +38,8 @@ u32 TagAt(ByteSpan bytes, size_t offset) { // fallback so the dispatch is robust to misnamed files. enum class Container { kUnknown, kWav, kXwma, kFuz, kWem }; -Container Classify(ByteSpan bytes, std::string_view extension) { - const std::string ext = LowerExt(extension); +Container Classify(ByteSpan bytes, base::StringRef extension) { + const base::String ext = LowerExt(extension); if (ext == "wav") return Container::kWav; if (ext == "xwm") return Container::kXwma; if (ext == "fuz") return Container::kFuz; @@ -56,7 +61,7 @@ Container Classify(ByteSpan bytes, std::string_view extension) { // talks to the Decoder interface whatever the source was. class ClipDecoder final : public Decoder { public: - explicit ClipDecoder(AudioClip clip) : clip_(std::move(clip)) {} + explicit ClipDecoder(AudioClip clip) : clip_(base::move(clip)) {} u32 channels() const override { return clip_.channels; } u32 sample_rate() const override { return clip_.sample_rate; } @@ -64,9 +69,9 @@ class ClipDecoder final : public Decoder { u32 Read(float* out, u32 frames) override { const u64 remaining = clip_.frames() - cursor_; - const u32 n = static_cast(std::min(frames, remaining)); + const u32 n = static_cast(rx::Min(frames, remaining)); if (n > 0) { - std::memcpy(out, clip_.samples.data() + cursor_ * clip_.channels, + base::MemCopy(out, clip_.samples.data() + cursor_ * clip_.channels, static_cast(n) * clip_.channels * sizeof(float)); cursor_ += n; } @@ -99,7 +104,7 @@ AudioClip DrainToClip(Decoder* decoder) { clip.channels = decoder->channels(); clip.sample_rate = decoder->sample_rate(); constexpr u32 kChunk = 4096; - std::vector buffer(static_cast(kChunk) * std::max(1u, clip.channels)); + base::Vector buffer(static_cast(kChunk) * rx::Max(1u, clip.channels)); for (;;) { const u32 got = decoder->Read(buffer.data(), kChunk); if (got == 0) break; @@ -111,7 +116,7 @@ AudioClip DrainToClip(Decoder* decoder) { } // namespace -AudioClip DecodeClip(ByteSpan bytes, std::string_view extension) { +AudioClip DecodeClip(ByteSpan bytes, base::StringRef extension) { const Container container = Classify(bytes, extension); if (container == Container::kWav) { AudioClip clip; @@ -120,21 +125,21 @@ AudioClip DecodeClip(ByteSpan bytes, std::string_view extension) { } if (container == Container::kUnknown) return {}; // Compressed: decode through the codec backend, then collapse to a clip. - return DrainToClip(OpenCompressed(bytes, ToCompressedKind(container)).get()); + return DrainToClip(OpenCompressed(bytes, ToCompressedKind(container)).Get_UseOnlyIfYouKnowWhatYouareDoing()); } -std::unique_ptr MakeClipDecoder(AudioClip clip) { +base::UniquePointer MakeClipDecoder(AudioClip clip) { if (!clip.valid()) return nullptr; - return std::make_unique(std::move(clip)); + return base::MakeUnique(base::move(clip)); } -std::unique_ptr OpenDecoder(ByteSpan bytes, std::string_view extension) { +base::UniquePointer OpenDecoder(ByteSpan bytes, base::StringRef extension) { const Container container = Classify(bytes, extension); switch (container) { case Container::kWav: { AudioClip clip; DecodeWav(bytes, &clip); - return MakeClipDecoder(std::move(clip)); + return MakeClipDecoder(base::move(clip)); } case Container::kXwma: case Container::kFuz: diff --git a/engine/audio/audio_clip.h b/engine/audio/audio_clip.h index 8005ae31..4edcbefb 100644 --- a/engine/audio/audio_clip.h +++ b/engine/audio/audio_clip.h @@ -1,10 +1,10 @@ #ifndef RX_AUDIO_AUDIO_CLIP_H_ #define RX_AUDIO_AUDIO_CLIP_H_ -#include -#include -#include +#include "base/containers/vector.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/string_ref.h" #include "core/types.h" namespace rx::audio { @@ -16,7 +16,7 @@ namespace rx::audio { struct AudioClip { u32 channels = 0; u32 sample_rate = 0; - std::vector samples; // interleaved, channels per frame + base::Vector samples; // interleaved, channels per frame u64 frames() const { return channels ? samples.size() / channels : 0; } bool valid() const { return channels > 0 && sample_rate > 0 && !samples.empty(); } @@ -48,16 +48,16 @@ class Decoder { // magic-number fallback (a .fuz is really a wrapped xWMA, a mod's loose file may // lie about its extension). `bytes` is copied into the decoder, so the caller's // buffer need not outlive it. Null when no decoder handles the data. -std::unique_ptr OpenDecoder(ByteSpan bytes, std::string_view extension); +base::UniquePointer OpenDecoder(ByteSpan bytes, base::StringRef extension); // Decodes `bytes` completely into an AudioClip. Returns an invalid clip (see // AudioClip::valid) when the format is unsupported or the data is malformed. -AudioClip DecodeClip(ByteSpan bytes, std::string_view extension); +AudioClip DecodeClip(ByteSpan bytes, base::StringRef extension); // Wraps an already-decoded clip in a streaming Decoder (a cursor over its // samples), so a cached one-shot can be handed to the mixer like any other // source. The clip is moved in; play a copy to fire the same sound twice. -std::unique_ptr MakeClipDecoder(AudioClip clip); +base::UniquePointer MakeClipDecoder(AudioClip clip); } // namespace rx::audio diff --git a/engine/audio/audio_device.h b/engine/audio/audio_device.h index cad45e46..3ab0c5ca 100644 --- a/engine/audio/audio_device.h +++ b/engine/audio/audio_device.h @@ -1,8 +1,8 @@ #ifndef RX_AUDIO_AUDIO_DEVICE_H_ #define RX_AUDIO_AUDIO_DEVICE_H_ -#include +#include "base/containers/vector.h" #include "core/export.h" #include "core/types.h" @@ -35,7 +35,7 @@ class RX_AUDIO_EXPORT AudioDevice { private: void* stream_ = nullptr; // SDL_AudioStream*, opaque so the header stays SDL-free Mixer* mixer_ = nullptr; - std::vector scratch_; // audio-thread-only render buffer + base::Vector scratch_; // audio-thread-only render buffer bool active_ = false; }; diff --git a/engine/audio/audio_system.cc b/engine/audio/audio_system.cc index a6467cb3..14f2a147 100644 --- a/engine/audio/audio_system.cc +++ b/engine/audio/audio_system.cc @@ -1,11 +1,16 @@ #include "audio/audio_system.h" -#include - #include #include "asset/vfs.h" +#include "base/containers/vector.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/optional.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/log.h" +#include "core/scalar.h" namespace rx::audio { namespace { @@ -19,10 +24,10 @@ base::Option Volume{"audio.volume", 1.0f, "RX_AUDIO_VOLUME", "master output volume, 0..1"}; // Pulls the extension (including the dot) off a path for decoder dispatch. -std::string_view ExtensionOf(std::string_view path) { +base::StringRef ExtensionOf(base::StringRef path) { const size_t dot = path.find_last_of('.'); const size_t slash = path.find_last_of("/\\"); - if (dot == std::string_view::npos || (slash != std::string_view::npos && dot < slash)) + if (dot == base::StringRef::npos || (slash != base::StringRef::npos && dot < slash)) return {}; return path.substr(dot); } @@ -39,7 +44,7 @@ bool AudioSystem::Initialize(asset::Vfs* vfs) { #endif vfs_ = vfs; muted_ = Mute.get(); - master_ = std::clamp(Volume.get(), 0.0f, 1.0f); + master_ = rx::Clamp(Volume.get(), 0.0f, 1.0f); if (muted_) { RX_INFO("audio: suppressed (RX_AUDIO_MUTE), running silent"); @@ -67,40 +72,42 @@ void AudioSystem::SetListener(const Vec3& position, const Vec3& forward, const V mixer_.SetListener(listener); } -bool AudioSystem::HasAsset(std::string_view path) const { +bool AudioSystem::HasAsset(base::StringRef path) const { return vfs_ && vfs_->Contains(path); } -bool AudioSystem::ReadAsset(std::string_view path, std::vector* out) { +bool AudioSystem::ReadAsset(base::StringRef path, base::Vector* out) { out->clear(); if (!vfs_) return false; - std::optional> bytes = vfs_->Read(path); + base::Optional> bytes = vfs_->Read(path); if (!bytes || bytes->size() == 0) return false; out->assign(bytes->data(), bytes->data() + bytes->size()); return true; } -const AudioClip* AudioSystem::GetClip(std::string_view path) { - const std::string key(path); - auto it = clip_cache_.find(key); - if (it != clip_cache_.end()) return it->second.valid() ? &it->second : nullptr; +const AudioClip* AudioSystem::GetClip(base::StringRef path) { + const base::String key(path); + if (const base::UniquePointer* cached = clip_cache_.find(key)) { + return (*cached)->valid() ? &**cached : nullptr; + } - std::vector bytes; - AudioClip clip; + base::Vector bytes; + auto clip = base::MakeUnique(); if (ReadAsset(path, &bytes)) - clip = DecodeClip(ByteSpan{bytes.data(), bytes.size()}, ExtensionOf(path)); - if (!clip.valid()) RX_WARN("audio: could not decode '{}'", key); - auto inserted = clip_cache_.emplace(key, std::move(clip)).first; - return inserted->second.valid() ? &inserted->second : nullptr; + *clip = DecodeClip(ByteSpan{bytes.data(), bytes.size()}, ExtensionOf(path)); + if (!clip->valid()) RX_WARN("audio: could not decode '{}'", key); + const AudioClip* result = clip->valid() ? &*clip : nullptr; + clip_cache_.insert(key, base::move(clip)); + return result; } -std::unique_ptr AudioSystem::OpenStream(std::string_view path) { - std::vector bytes; +base::UniquePointer AudioSystem::OpenStream(base::StringRef path) { + base::Vector bytes; if (!ReadAsset(path, &bytes)) return nullptr; return OpenDecoder(ByteSpan{bytes.data(), bytes.size()}, ExtensionOf(path)); } -u32 AudioSystem::PlayUi(std::string_view path, f32 gain) { +u32 AudioSystem::PlayUi(base::StringRef path, f32 gain) { if (!active()) return 0; const AudioClip* clip = GetClip(path); if (!clip) return 0; @@ -110,7 +117,7 @@ u32 AudioSystem::PlayUi(std::string_view path, f32 gain) { return mixer_.Play(MakeClipDecoder(*clip), params); } -u32 AudioSystem::PlayAt(std::string_view path, const Vec3& position, PlayParams params) { +u32 AudioSystem::PlayAt(base::StringRef path, const Vec3& position, PlayParams params) { if (!active()) return 0; const AudioClip* clip = GetClip(path); if (!clip) return 0; @@ -119,12 +126,12 @@ u32 AudioSystem::PlayAt(std::string_view path, const Vec3& position, PlayParams return mixer_.Play(MakeClipDecoder(*clip), params); } -u32 AudioSystem::PlayLoop(std::string_view path, PlayParams params) { +u32 AudioSystem::PlayLoop(base::StringRef path, PlayParams params) { if (!active()) return 0; - std::unique_ptr decoder = OpenStream(path); + base::UniquePointer decoder = OpenStream(path); if (!decoder) return 0; params.loop = true; - return mixer_.Play(std::move(decoder), params); + return mixer_.Play(base::move(decoder), params); } void AudioSystem::Stop(u32 voice, f32 fade) { @@ -142,7 +149,7 @@ void AudioSystem::SetVoiceGain(u32 voice, f32 gain) { } void AudioSystem::SetMasterVolume(f32 volume) { - master_ = std::clamp(volume, 0.0f, 1.0f); + master_ = rx::Clamp(volume, 0.0f, 1.0f); mixer_.SetMasterGain(master_); } diff --git a/engine/audio/audio_system.h b/engine/audio/audio_system.h index fcdbb150..15024838 100644 --- a/engine/audio/audio_system.h +++ b/engine/audio/audio_system.h @@ -1,14 +1,15 @@ #ifndef RX_AUDIO_AUDIO_SYSTEM_H_ #define RX_AUDIO_AUDIO_SYSTEM_H_ -#include -#include -#include - #include "audio/audio_clip.h" #include "audio/audio_device.h" #include "audio/mixer.h" #include "audio/spatial.h" +#include "base/containers/unordered_map.h" +#include "base/containers/vector.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/math.h" #include "core/types.h" @@ -42,12 +43,12 @@ class RX_AUDIO_EXPORT AudioSystem { void SetListener(const Vec3& position, const Vec3& forward, const Vec3& up); // Fire-and-forget 2D sound (UI clicks, non-diegetic cues), equal in both ears. - u32 PlayUi(std::string_view path, f32 gain = 1.0f); + u32 PlayUi(base::StringRef path, f32 gain = 1.0f); // Positional one-shot at a world position; `params.position` is overwritten. - u32 PlayAt(std::string_view path, const Vec3& position, PlayParams params = {}); + u32 PlayAt(base::StringRef path, const Vec3& position, PlayParams params = {}); // Looping voice (ambient bed, music). Streams compressed sources. Returns the // voice id for a later Stop / SetVoicePosition; 0 on failure. - u32 PlayLoop(std::string_view path, PlayParams params); + u32 PlayLoop(base::StringRef path, PlayParams params); void Stop(u32 voice, f32 fade = 0.12f); void StopAll(); @@ -59,24 +60,26 @@ class RX_AUDIO_EXPORT AudioSystem { // Whether `path` exists in the mounted Vfs (loose files + archives). Lets a // caller pick a sound it can actually load before committing to play it. - bool HasAsset(std::string_view path) const; + bool HasAsset(base::StringRef path) const; Mixer& mixer() { return mixer_; } private: // Decodes (and caches) a short sound fully. Returns null on failure; failures // cache as an empty clip so a missing/unsupported file is only probed once. - const AudioClip* GetClip(std::string_view path); + const AudioClip* GetClip(base::StringRef path); // Opens a streaming decoder for a long sound (caller owns it). Null on failure. - std::unique_ptr OpenStream(std::string_view path); + base::UniquePointer OpenStream(base::StringRef path); // Reads `path` from the Vfs into `out`; false when absent. Held as a member so // the byte buffer outlives the ByteSpan handed to the decoder. - bool ReadAsset(std::string_view path, std::vector* out); + bool ReadAsset(base::StringRef path, base::Vector* out); asset::Vfs* vfs_ = nullptr; Mixer mixer_; AudioDevice device_; - std::unordered_map clip_cache_; + // Boxed: GetClip hands out pointers, and base::UnorderedMap moves its + // values when it grows. + base::UnorderedMap> clip_cache_; bool muted_ = false; f32 master_ = 1.0f; }; diff --git a/engine/audio/aux_synth.cc b/engine/audio/aux_synth.cc index 24620a73..9eb1438c 100644 --- a/engine/audio/aux_synth.cc +++ b/engine/audio/aux_synth.cc @@ -1,7 +1,8 @@ #include "audio/aux_synth.h" +#include "base/algorithm.h" +#include "core/scalar.h" -#include -#include +#include namespace rx::audio { namespace { @@ -18,7 +19,7 @@ constexpr f32 kWindFullSpeed = 55.0f; constexpr f32 kSlipFloor = 0.08f; f32 Smoothstep(f32 edge0, f32 edge1, f32 x) { - const f32 t = std::clamp((x - edge0) / std::max(1e-4f, edge1 - edge0), 0.0f, 1.0f); + const f32 t = rx::Clamp((x - edge0) / rx::Max(1e-4f, edge1 - edge0), 0.0f, 1.0f); return t * t * (3.0f - 2.0f * t); } @@ -35,8 +36,8 @@ f32 SkidSynth::Noise() { } void SkidSynth::Render(f32* out, u32 frames, const SynthParams& p) { - const f32 slip = std::clamp(p.slip, 0.0f, 1.0f); - const f32 speed = std::max(0.0f, p.speed_mps); + const f32 slip = rx::Clamp(p.slip, 0.0f, 1.0f); + const f32 speed = rx::Max(0.0f, p.speed_mps); // Gate: slip opens the layer above the floor, speed scales how loud a slide of // a given slip is. No slip or no motion -> exactly silent. @@ -44,17 +45,17 @@ void SkidSynth::Render(f32* out, u32 frames, const SynthParams& p) { const f32 speed_scale = Smoothstep(0.5f, kSkidFullSpeed, speed); const f32 level = slip_gate * speed_scale * 0.6f; if (level <= 0.0f) { - std::fill(out, out + frames, 0.0f); + base::Fill(out, out + frames, 0.0f); return; } // Band-pass centre climbs a little with speed; a fast slide squeals higher. A // front/rear slip bias (skid_bias, -1 rear .. +1 front) nudges it subtly (up to // ~12%) so a front-end wash reads a touch brighter than a rear break-away. - const f32 bias = std::clamp(p.skid_bias, -1.0f, 1.0f); + const f32 bias = rx::Clamp(p.skid_bias, -1.0f, 1.0f); const f32 centre_hz = - std::clamp((900.0f + speed * 25.0f) * (1.0f + 0.12f * bias), 300.0f, rate_ * 0.4f); - const f32 f = std::clamp(2.0f * std::sin(kPi * centre_hz / rate_), 0.0f, 1.0f); + rx::Clamp((900.0f + speed * 25.0f) * (1.0f + 0.12f * bias), 300.0f, rate_ * 0.4f); + const f32 f = rx::Clamp(2.0f * ::sin(kPi * centre_hz / rate_), 0.0f, 1.0f); const f32 q = 0.28f; // moderate resonance: a band, not a whistle for (u32 i = 0; i < frames; ++i) { @@ -64,8 +65,8 @@ void SkidSynth::Render(f32* out, u32 frames, const SynthParams& p) { const f32 high = in - svf_low_ - q * svf_band_; svf_band_ += f * high; f32 s = svf_band_ * level; - s = std::tanh(s); - out[i] = std::isfinite(s) ? s : 0.0f; + s = ::tanh(s); + out[i] = ::isfinite(s) ? s : 0.0f; } } @@ -80,19 +81,19 @@ f32 WindSynth::Noise() { } void WindSynth::Render(f32* out, u32 frames, const SynthParams& p) { - const f32 speed = std::max(0.0f, p.speed_mps); + const f32 speed = rx::Max(0.0f, p.speed_mps); // Presence grows faster than linearly so wind is a non-issue around town and // dominant on the motorway. const f32 t = Smoothstep(3.0f, kWindFullSpeed, speed); const f32 level = t * t * 0.5f; if (level <= 0.0f) { - std::fill(out, out + frames, 0.0f); + base::Fill(out, out + frames, 0.0f); return; } // Cutoff opens with speed: faster air reads brighter, not just louder. - const f32 cutoff = std::clamp(300.0f + speed * 55.0f, 200.0f, rate_ * 0.45f); - const f32 alpha = std::clamp(1.0f - std::exp(-2.0f * kPi * cutoff / rate_), 0.0f, 1.0f); + const f32 cutoff = rx::Clamp(300.0f + speed * 55.0f, 200.0f, rate_ * 0.45f); + const f32 alpha = rx::Clamp(1.0f - ::exp(-2.0f * kPi * cutoff / rate_), 0.0f, 1.0f); for (u32 i = 0; i < frames; ++i) { const f32 white = Noise(); @@ -101,8 +102,8 @@ void WindSynth::Render(f32* out, u32 frames, const SynthParams& p) { // rather than a low roar. const f32 band = lp_ - hp_prev_ * 0.02f; hp_prev_ = lp_; - f32 s = std::tanh(band * level); - out[i] = std::isfinite(s) ? s : 0.0f; + f32 s = ::tanh(band * level); + out[i] = ::isfinite(s) ? s : 0.0f; } } diff --git a/engine/audio/engine_synth.cc b/engine/audio/engine_synth.cc index 901f035e..84589093 100644 --- a/engine/audio/engine_synth.cc +++ b/engine/audio/engine_synth.cc @@ -1,7 +1,7 @@ #include "audio/engine_synth.h" +#include "core/scalar.h" -#include -#include +#include namespace rx::audio { namespace { @@ -14,24 +14,24 @@ constexpr i32 kMaxPartials = 32; // A phase in [0,1) turned into a sine. Wrapping the accumulator (not the angle) // keeps precision bounded no matter how long the engine runs. -f32 SinPhase(f32 phase) { return std::sin(phase * kTwoPi); } +f32 SinPhase(f32 phase) { return ::sin(phase * kTwoPi); } // Advances a normalised phase by `freq` Hz for one sample and wraps it. f32 Advance(f32 phase, f32 freq, f32 inv_rate) { phase += freq * inv_rate; - phase -= std::floor(phase); + phase -= ::floor(phase); return phase; } // One-pole low-pass coefficient for a cutoff in Hz. Cutoff is clamped inside the // stable band so a runaway parameter cannot make the filter blow up. f32 LowpassAlpha(f32 cutoff_hz, f32 rate) { - const f32 fc = std::clamp(cutoff_hz, 20.0f, rate * 0.49f); - return std::clamp(1.0f - std::exp(-kTwoPi * fc / rate), 0.0f, 1.0f); + const f32 fc = rx::Clamp(cutoff_hz, 20.0f, rate * 0.49f); + return rx::Clamp(1.0f - ::exp(-kTwoPi * fc / rate), 0.0f, 1.0f); } f32 Smoothstep(f32 edge0, f32 edge1, f32 x) { - const f32 t = std::clamp((x - edge0) / std::max(1e-4f, edge1 - edge0), 0.0f, 1.0f); + const f32 t = rx::Clamp((x - edge0) / rx::Max(1e-4f, edge1 - edge0), 0.0f, 1.0f); return t * t * (3.0f - 2.0f * t); } @@ -127,8 +127,8 @@ EnginePreset LightJetPreset() { EngineSynth::EngineSynth(const EnginePreset& preset, u32 output_rate) : preset_(preset), rate_(static_cast(output_rate ? output_rate : 48000)) { inv_rate_ = 1.0f / rate_; - preset_.cylinders = std::clamp(preset_.cylinders, 1, 16); - preset_.partials = std::clamp(preset_.partials, 2, kMaxPartials); + preset_.cylinders = rx::Clamp(preset_.cylinders, 1, 16); + preset_.partials = rx::Clamp(preset_.partials, 2, kMaxPartials); } f32 EngineSynth::Noise() { @@ -146,8 +146,8 @@ void EngineSynth::Render(f32* out, u32 frames, const SynthParams& p) { // Clamp telemetry into a sane operating window (SynthVoice already sanitises, // but the model must be safe when driven directly by a test). - const f32 load = std::clamp(p.load, 0.0f, 1.0f); - const f32 throttle = std::clamp(p.throttle, 0.0f, 1.0f); + const f32 load = rx::Clamp(p.load, 0.0f, 1.0f); + const f32 throttle = rx::Clamp(p.throttle, 0.0f, 1.0f); // Output muffle (submersion/occlusion): tighten the final low-pass toward a // dark 500 Hz and duck up to ~6 dB as it rises. This makes the InboardBoat @@ -155,7 +155,7 @@ void EngineSynth::Render(f32* out, u32 frames, const SynthParams& p) { // directly (a headless tool, the integration test) can express "underwater" // without VehicleAudio's mixer-gain duck. Smoothed upstream, so it is // click-free even when a ventilating boat prop toggles it every frame. - const f32 muffle = std::clamp(p.muffle, 0.0f, 1.0f); + const f32 muffle = rx::Clamp(p.muffle, 0.0f, 1.0f); const f32 out_cut_hz = Lerp(preset_.exhaust_lowpass_hz, 500.0f, muffle); const f32 muffle_gain = 1.0f - 0.5f * muffle; @@ -164,7 +164,7 @@ void EngineSynth::Render(f32* out, u32 frames, const SynthParams& p) { // caller's frame rate and re-asserting mid-flare does not retrigger it. const f32 shift_req = p.gear_shift; if (shift_req != 0.0f && shift_prev_ == 0.0f) { - shift_total_ = std::max(1, static_cast(rate * 0.18f)); // ~180 ms + shift_total_ = rx::Max(1, static_cast(rate * 0.18f)); // ~180 ms shift_left_ = shift_total_; shift_sign_ = shift_req; } @@ -175,25 +175,25 @@ void EngineSynth::Render(f32* out, u32 frames, const SynthParams& p) { auto flare_gain = [&]() -> f32 { if (shift_left_ <= 0) return 1.0f; const f32 phase = - static_cast(shift_total_ - shift_left_) / static_cast(std::max(1, shift_total_)); - const f32 bump = std::sin(kPi * std::clamp(phase, 0.0f, 1.0f)); + static_cast(shift_total_ - shift_left_) / static_cast(rx::Max(1, shift_total_)); + const f32 bump = ::sin(kPi * rx::Clamp(phase, 0.0f, 1.0f)); --shift_left_; return shift_sign_ < 0.0f ? (1.0f - 0.55f * bump) : (1.0f + 0.30f * bump); }; // Turbine path: no piston bank, a spool-tracking whine + exhaust roar if (preset_.turbine) { - const f32 spool = std::clamp(p.rpm * 0.01f, 0.0f, 1.05f); // rpm is N1 % + const f32 spool = rx::Clamp(p.rpm * 0.01f, 0.0f, 1.05f); // rpm is N1 % // Whine pitch/level tracks spool (rpm/N1); roar level tracks thrust when it is // supplied (>=0), else derives from spool as before. Split lets a spool-up // sound right: the whine climbs before the roar catches up. - const f32 roar_in = p.thrust >= 0.0f ? std::clamp(p.thrust, 0.0f, 1.0f) : spool; + const f32 roar_in = p.thrust >= 0.0f ? rx::Clamp(p.thrust, 0.0f, 1.0f) : spool; const f32 whine_hz = preset_.turbine_min_hz + spool * (preset_.turbine_max_hz - preset_.turbine_min_hz); const f32 roar_alpha = LowpassAlpha(400.0f + roar_in * 2500.0f, rate); const f32 out_alpha = LowpassAlpha(out_cut_hz, rate); const f32 whine_gain = 0.42f * spool; - const f32 roar_gain = 0.30f * std::pow(roar_in, 1.4f); + const f32 roar_gain = 0.30f * ::pow(roar_in, 1.4f); for (u32 i = 0; i < frames; ++i) { f32 tone = SinPhase(whine_phase_); // A couple of harmonics of the whine, dropped if they alias. @@ -204,15 +204,15 @@ void EngineSynth::Render(f32* out, u32 frames, const SynthParams& p) { const f32 roar = 0.5f * roar_lp_ + 0.5f * (Noise() * 0.4f); f32 s = tone * whine_gain + roar * roar_gain; out_lp_ += out_alpha * (s - out_lp_); - s = std::tanh(out_lp_) * muffle_gain * flare_gain(); - out[i] = std::isfinite(s) ? s : 0.0f; + s = ::tanh(out_lp_) * muffle_gain * flare_gain(); + out[i] = ::isfinite(s) ? s : 0.0f; whine_phase_ = Advance(whine_phase_, whine_hz, inv_rate); } return; } // Piston path - const f32 rev_freq = std::max(0.0f, p.rpm) / 60.0f; // crank revs per second + const f32 rev_freq = rx::Max(0.0f, p.rpm) / 60.0f; // crank revs per second const f32 base_freq = rev_freq * 0.5f; // half-order fundamental const i32 fire_n = preset_.cylinders; // firing order counted in half-orders @@ -238,8 +238,8 @@ void EngineSynth::Render(f32* out, u32 frames, const SynthParams& p) { // Overrun burble: closed throttle at elevated rpm gives the lumpy pop. Depth // rises as throttle falls and rpm climbs above idle. - const f32 rpm_span = std::max(1.0f, preset_.redline_rpm - preset_.idle_rpm); - const f32 rev_frac = std::clamp((p.rpm - preset_.idle_rpm) / rpm_span, 0.0f, 1.0f); + const f32 rpm_span = rx::Max(1.0f, preset_.redline_rpm - preset_.idle_rpm); + const f32 rev_frac = rx::Clamp((p.rpm - preset_.idle_rpm) / rpm_span, 0.0f, 1.0f); const f32 burble_depth = (1.0f - throttle) * Smoothstep(0.15f, 0.5f, rev_frac) * 0.8f; const f32 burble_hz = 7.0f + rev_frac * 9.0f; // pulses speed up with rpm @@ -254,7 +254,7 @@ void EngineSynth::Render(f32* out, u32 frames, const SynthParams& p) { const f32 out_alpha = LowpassAlpha(out_cut_hz, rate); // Optional propeller blade-passing tone (single-prop aircraft). - const f32 prop_rpm = std::max(0.0f, p.rpm) * preset_.prop_gear_ratio; + const f32 prop_rpm = rx::Max(0.0f, p.rpm) * preset_.prop_gear_ratio; const f32 blade_hz = static_cast(preset_.prop_blades) * prop_rpm / 60.0f; const bool prop_on = preset_.prop_blades > 0 && blade_hz > 5.0f && blade_hz * 2.0f < nyquist; const f32 prop_gain = 0.30f * (0.5f + 0.5f * throttle); @@ -291,8 +291,8 @@ void EngineSynth::Render(f32* out, u32 frames, const SynthParams& p) { // The muffle duck and the gear-shift flare ride on the limited output so both // stay bounded and click-free. out_lp_ += out_alpha * (s - out_lp_); - f32 y = std::tanh(out_lp_ * 1.3f) * muffle_gain * flare_gain(); - out[i] = std::isfinite(y) ? y : 0.0f; + f32 y = ::tanh(out_lp_ * 1.3f) * muffle_gain * flare_gain(); + out[i] = ::isfinite(y) ? y : 0.0f; base_phase_ = Advance(base_phase_, base_freq, inv_rate); burble_phase_ = Advance(burble_phase_, burble_hz, inv_rate); diff --git a/engine/audio/ffmpeg_codec.cc b/engine/audio/ffmpeg_codec.cc index 4a514f3d..f7ae3c7a 100644 --- a/engine/audio/ffmpeg_codec.cc +++ b/engine/audio/ffmpeg_codec.cc @@ -1,9 +1,12 @@ #include "audio/ffmpeg_codec.h" -#include -#include -#include +#include +#include +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" #include "core/log.h" extern "C" { @@ -35,7 +38,7 @@ int ReadPacket(void* opaque, u8* buf, int want) { const size_t remaining = src->size - src->pos; if (remaining == 0) return AVERROR_EOF; const int n = static_cast(want < static_cast(remaining) ? want : remaining); - std::memcpy(buf, src->data + src->pos, static_cast(n)); + base::MemCopy(buf, src->data + src->pos, static_cast(n)); src->pos += static_cast(n); return n; } @@ -77,7 +80,7 @@ struct Decode { } // namespace -std::unique_ptr OpenFfmpegDecoder(ByteSpan bytes) { +base::UniquePointer OpenFfmpegDecoder(ByteSpan bytes) { if (bytes.empty()) return nullptr; MemorySource source{bytes.data(), bytes.size(), 0}; Decode d; @@ -139,7 +142,7 @@ std::unique_ptr OpenFfmpegDecoder(ByteSpan bytes) { AudioClip clip; clip.channels = static_cast(out_channels); clip.sample_rate = static_cast(out_rate); - std::vector convert; + base::Vector convert; auto drain = [&](bool flush) { while (true) { @@ -180,7 +183,7 @@ std::unique_ptr OpenFfmpegDecoder(ByteSpan bytes) { } RX_INFO("audio: FFmpeg decoded {} frames, {} ch, {} Hz", clip.frames(), clip.channels, clip.sample_rate); - return MakeClipDecoder(std::move(clip)); + return MakeClipDecoder(base::move(clip)); } bool FfmpegAvailable() { return true; } diff --git a/engine/audio/ffmpeg_codec.h b/engine/audio/ffmpeg_codec.h index bc7a3c60..1548526b 100644 --- a/engine/audio/ffmpeg_codec.h +++ b/engine/audio/ffmpeg_codec.h @@ -1,9 +1,9 @@ #ifndef RX_AUDIO_FFMPEG_CODEC_H_ #define RX_AUDIO_FFMPEG_CODEC_H_ -#include #include "audio/audio_clip.h" +#include "base/memory/unique_pointer.h" #include "core/types.h" namespace rx::audio { @@ -19,7 +19,7 @@ namespace rx::audio { // a build option and out of the default build means the CI matrix stays free of a // heavyweight system dependency; enable it (and ship its shared libraries beside // the executable) to hear the games' compressed music, ambience and voice. -std::unique_ptr OpenFfmpegDecoder(ByteSpan bytes); +base::UniquePointer OpenFfmpegDecoder(ByteSpan bytes); // Whether the FFmpeg backend is compiled in. False for the stub. bool FfmpegAvailable(); diff --git a/engine/audio/ffmpeg_stub.cc b/engine/audio/ffmpeg_stub.cc index d67ef815..a31a5d54 100644 --- a/engine/audio/ffmpeg_stub.cc +++ b/engine/audio/ffmpeg_stub.cc @@ -1,5 +1,6 @@ #include "audio/ffmpeg_codec.h" +#include "base/memory/unique_pointer.h" #include "core/log.h" // Compiled when the FFmpeg backend is off (the default, and the whole CI matrix): @@ -7,7 +8,7 @@ // note rather than failing the build or crashing at runtime. namespace rx::audio { -std::unique_ptr OpenFfmpegDecoder(ByteSpan) { +base::UniquePointer OpenFfmpegDecoder(ByteSpan) { static bool warned = false; if (!warned) { warned = true; diff --git a/engine/audio/mixer.cc b/engine/audio/mixer.cc index 4dfc9357..96e1c0a4 100644 --- a/engine/audio/mixer.cc +++ b/engine/audio/mixer.cc @@ -1,8 +1,13 @@ #include "audio/mixer.h" +#include "base/algorithm.h" +#include "base/containers/vector.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/threading/lock_guard.h" #include "core/memory/memory_tracker.h" -#include -#include +#include "core/scalar.h" +#include namespace rx::audio { namespace { @@ -22,74 +27,74 @@ f32 RampStep(f32 seconds, u32 rate) { } // namespace -u32 Mixer::Play(std::unique_ptr decoder, const PlayParams& params) { +u32 Mixer::Play(base::UniquePointer decoder, const PlayParams& params) { if (!decoder || decoder->channels() == 0 || decoder->sample_rate() == 0) return 0; - std::lock_guard lock(mutex_); + base::LockGuard lock(mutex_); const u32 id = next_id_++; Command cmd; cmd.type = CmdType::kPlay; cmd.voice = id; - cmd.decoder = std::move(decoder); + cmd.decoder = base::move(decoder); cmd.params = params; - pending_.push_back(std::move(cmd)); + pending_.push_back(base::move(cmd)); return id; } void Mixer::Stop(u32 voice, f32 fade_out) { - std::lock_guard lock(mutex_); + base::LockGuard lock(mutex_); Command cmd; cmd.type = CmdType::kStop; cmd.voice = voice; cmd.value = fade_out; - pending_.push_back(std::move(cmd)); + pending_.push_back(base::move(cmd)); } void Mixer::StopAll() { - std::lock_guard lock(mutex_); + base::LockGuard lock(mutex_); Command cmd; cmd.type = CmdType::kStopAll; - pending_.push_back(std::move(cmd)); + pending_.push_back(base::move(cmd)); } void Mixer::SetVoiceGain(u32 voice, f32 gain) { - std::lock_guard lock(mutex_); + base::LockGuard lock(mutex_); Command cmd; cmd.type = CmdType::kGain; cmd.voice = voice; cmd.value = gain; - pending_.push_back(std::move(cmd)); + pending_.push_back(base::move(cmd)); } void Mixer::SetVoicePosition(u32 voice, const Vec3& position) { - std::lock_guard lock(mutex_); + base::LockGuard lock(mutex_); Command cmd; cmd.type = CmdType::kPosition; cmd.voice = voice; cmd.position = position; - pending_.push_back(std::move(cmd)); + pending_.push_back(base::move(cmd)); } void Mixer::SetListener(const Listener& listener) { - std::lock_guard lock(mutex_); + base::LockGuard lock(mutex_); Command cmd; cmd.type = CmdType::kListener; cmd.listener = listener; - pending_.push_back(std::move(cmd)); + pending_.push_back(base::move(cmd)); } void Mixer::SetMasterGain(f32 gain) { - std::lock_guard lock(mutex_); + base::LockGuard lock(mutex_); Command cmd; cmd.type = CmdType::kMaster; cmd.value = gain; - pending_.push_back(std::move(cmd)); + pending_.push_back(base::move(cmd)); } void Mixer::ApplyCommands() { - std::vector& commands = commands_scratch_; + base::Vector& commands = commands_scratch_; commands.clear(); { - std::lock_guard lock(mutex_); + base::LockGuard lock(mutex_); commands.swap(pending_); } for (Command& cmd : commands) { @@ -99,8 +104,8 @@ void Mixer::ApplyCommands() { voice.id = cmd.voice; voice.params = cmd.params; voice.gain = cmd.params.gain; - voice.src_channels = std::max(1u, cmd.decoder->channels()); - voice.decoder = std::move(cmd.decoder); + voice.src_channels = rx::Max(1u, cmd.decoder->channels()); + voice.decoder = base::move(cmd.decoder); if (cmd.params.fade_in > 0.0f) { voice.env = 0.0f; voice.env_rate = RampStep(cmd.params.fade_in, output_rate_); @@ -108,7 +113,7 @@ void Mixer::ApplyCommands() { voice.env = 1.0f; voice.env_rate = 0.0f; } - voices_.push_back(std::move(voice)); + voices_.push_back(base::move(voice)); break; } case CmdType::kStop: @@ -147,10 +152,10 @@ bool Mixer::FillSource(Voice& voice, size_t frames) { // Reclaim space ahead of the read cursor before topping up. if (voice.head > kCompactThreshold) { voice.src.erase(voice.src.begin(), - voice.src.begin() + static_cast(voice.head) * ch); + voice.src.begin() + static_cast(voice.head) * ch); voice.head = 0; } - std::vector& chunk = decode_scratch_; + base::Vector& chunk = decode_scratch_; chunk.resize(static_cast(kDecodeChunkFrames) * ch); while (voice.src.size() / ch - voice.head < frames) { if (voice.ended) return voice.src.size() / ch > voice.head; @@ -161,7 +166,7 @@ bool Mixer::FillSource(Voice& voice, size_t frames) { return voice.src.size() / ch > voice.head; } voice.src.insert(voice.src.end(), chunk.begin(), - chunk.begin() + static_cast(got) * ch); + chunk.begin() + static_cast(got) * ch); } return true; } @@ -169,7 +174,7 @@ bool Mixer::FillSource(Voice& voice, size_t frames) { void Mixer::MixInto(float* out, u32 frames) { mem::CategoryScope mem_scope(kAudioCategory); ApplyCommands(); - std::fill(out, out + static_cast(frames) * 2, 0.0f); + base::Fill(out, out + static_cast(frames) * 2, 0.0f); for (Voice& voice : voices_) { const u32 ch = voice.src_channels; @@ -193,7 +198,7 @@ void Mixer::MixInto(float* out, u32 frames) { const size_t available = voice.src.size() / ch; if (available <= voice.head) break; const size_t i0 = voice.head; - const size_t i1 = std::min(i0 + 1, available - 1); + const size_t i1 = rx::Min(i0 + 1, available - 1); const f32 t = static_cast(voice.frac); f32 sl, sr; @@ -217,7 +222,7 @@ void Mixer::MixInto(float* out, u32 frames) { voice.dead = true; break; } - const f32 e = std::clamp(voice.env, 0.0f, 1.0f); + const f32 e = rx::Clamp(voice.env, 0.0f, 1.0f); if (voice.params.positional) { const f32 mono = 0.5f * (sl + sr); @@ -240,19 +245,17 @@ void Mixer::MixInto(float* out, u32 frames) { } // Drop finished voices (end of stream, or a fade-out that completed). - voices_.erase(std::remove_if(voices_.begin(), voices_.end(), - [](const Voice& v) { - const size_t available = v.src.size() / std::max(1u, v.src_channels); - return v.dead || (v.ended && available <= v.head); - }), - voices_.end()); + base::EraseIf(voices_, [](const Voice& v) { + const size_t available = v.src.size() / rx::Max(1u, v.src_channels); + return v.dead || (v.ended && available <= v.head); + }); // Master gain and a soft clip, so a dense mix saturates gracefully instead of // wrapping. tanh is gentle near unity and only bends the peaks. const f32 m = master_; for (u32 i = 0; i < frames * 2; ++i) { f32 s = out[i] * m; - if (s > 1.0f || s < -1.0f) s = std::tanh(s); + if (s > 1.0f || s < -1.0f) s = ::tanh(s); out[i] = s; } } diff --git a/engine/audio/mixer.h b/engine/audio/mixer.h index 038b0edf..568a9c3a 100644 --- a/engine/audio/mixer.h +++ b/engine/audio/mixer.h @@ -1,12 +1,12 @@ #ifndef RX_AUDIO_MIXER_H_ #define RX_AUDIO_MIXER_H_ -#include -#include -#include #include "audio/audio_clip.h" #include "audio/spatial.h" +#include "base/containers/vector.h" +#include "base/memory/unique_pointer.h" +#include "base/threading/mutex.h" #include "core/math.h" #include "core/types.h" @@ -38,7 +38,7 @@ class Mixer { // engine thread // Starts `decoder` as a new voice and returns its id (0 if decoder is null). // The returned id stays valid for Stop/SetVoice* until the voice finishes. - u32 Play(std::unique_ptr decoder, const PlayParams& params); + u32 Play(base::UniquePointer decoder, const PlayParams& params); void Stop(u32 voice, f32 fade_out = 0.05f); void StopAll(); void SetVoiceGain(u32 voice, f32 gain); @@ -54,10 +54,10 @@ class Mixer { private: struct Voice { u32 id = 0; - std::unique_ptr decoder; + base::UniquePointer decoder; PlayParams params; // Decoded-but-not-yet-resampled source frames, consumed from `head`. - std::vector src; + base::Vector src; size_t head = 0; // first unconsumed source frame f64 frac = 0.0; // fractional position between head and head+1 u32 src_channels = 1; @@ -83,26 +83,26 @@ class Mixer { u32 voice = 0; f32 value = 0.0f; Vec3 position{}; - std::unique_ptr decoder; + base::UniquePointer decoder; PlayParams params; Listener listener; }; u32 output_rate_ = 48000; - std::mutex mutex_; - std::vector pending_; + base::Mutex mutex_; + base::Vector pending_; u32 next_id_ = 1; // Device-thread-only state below. - std::vector voices_; + base::Vector voices_; Listener listener_; f32 master_ = 1.0f; // Reusable scratch: the mixer runs on the realtime audio callback, where a // heap allocation can miss the deadline and glitch. Both keep their // capacity across callbacks (commands_scratch_ ping-pongs storage with // pending_ via swap). - std::vector commands_scratch_; - std::vector decode_scratch_; + base::Vector commands_scratch_; + base::Vector decode_scratch_; }; } // namespace rx::audio diff --git a/engine/audio/spatial.h b/engine/audio/spatial.h index 19749943..adf49430 100644 --- a/engine/audio/spatial.h +++ b/engine/audio/spatial.h @@ -1,10 +1,10 @@ #ifndef RX_AUDIO_SPATIAL_H_ #define RX_AUDIO_SPATIAL_H_ -#include -#include +#include #include "core/math.h" +#include "core/scalar.h" #include "core/types.h" namespace rx::audio { @@ -42,8 +42,8 @@ inline f32 DistanceGain(f32 distance, const Attenuation& a) { const f32 inv = a.ref_dist / distance; // inverse-distance law // Fade the inverse curve to exactly zero over the last stretch so there is no // audible pop when a voice crosses max_dist. - const f32 fade = (a.max_dist - distance) / std::max(0.001f, a.max_dist - a.ref_dist); - return inv * std::clamp(fade, 0.0f, 1.0f); + const f32 fade = (a.max_dist - distance) / rx::Max(0.001f, a.max_dist - a.ref_dist); + return inv * rx::Clamp(fade, 0.0f, 1.0f); } // Constant-power stereo pan from the listener to a world-space source. The result @@ -64,12 +64,12 @@ inline StereoGains PanForSource(const Listener& listener, const Vec3& source, const Vec3 up = Normalize(listener.up); const Vec3 right = Normalize(Cross(forward, up)); f32 pan = 0.0f; - if (distance > 1e-4f) pan = std::clamp(Dot(to_source * (1.0f / distance), right), -1.0f, 1.0f); + if (distance > 1e-4f) pan = rx::Clamp(Dot(to_source * (1.0f / distance), right), -1.0f, 1.0f); // Map pan [-1,1] -> angle [0, pi/2] and take cos/sin for equal-power gains. const f32 angle = (pan * 0.5f + 0.5f) * 1.57079633f; - f32 l = std::cos(angle); - f32 r = std::sin(angle); + f32 l = ::cos(angle); + f32 r = ::sin(angle); l = min_bleed + (1.0f - min_bleed) * l; r = min_bleed + (1.0f - min_bleed) * r; return {l * gain, r * gain}; diff --git a/engine/audio/synth_voice.cc b/engine/audio/synth_voice.cc index dc95b3a2..c2b4cefc 100644 --- a/engine/audio/synth_voice.cc +++ b/engine/audio/synth_voice.cc @@ -1,7 +1,10 @@ #include "audio/synth_voice.h" +#include "base/atomic.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "core/scalar.h" -#include -#include +#include namespace rx::audio { namespace { @@ -17,50 +20,50 @@ constexpr u32 kSmoothChunk = 32; f32 ChunkAlpha(f32 tau_seconds, u32 rate) { if (tau_seconds <= 0.0f || rate == 0) return 1.0f; const f32 dt = static_cast(kSmoothChunk) / static_cast(rate); - return std::clamp(1.0f - std::exp(-dt / tau_seconds), 0.0f, 1.0f); + return rx::Clamp(1.0f - ::exp(-dt / tau_seconds), 0.0f, 1.0f); } // Replaces a non-finite telemetry value with zero so a stray NaN upstream can // never poison the smoother (a NaN would stick forever through the one-pole). -f32 Sane(f32 v) { return std::isfinite(v) ? v : 0.0f; } +f32 Sane(f32 v) { return ::isfinite(v) ? v : 0.0f; } } // namespace void ParamMailbox::Publish(const SynthParams& p) { SynthParams& clean = slot_[write_]; clean.rpm = Sane(p.rpm); - clean.load = std::clamp(Sane(p.load), 0.0f, 1.0f); - clean.throttle = std::clamp(Sane(p.throttle), 0.0f, 1.0f); + clean.load = rx::Clamp(Sane(p.load), 0.0f, 1.0f); + clean.throttle = rx::Clamp(Sane(p.throttle), 0.0f, 1.0f); clean.speed_mps = Sane(p.speed_mps); - clean.slip = std::clamp(Sane(p.slip), 0.0f, 1.0f); - clean.muffle = std::clamp(Sane(p.muffle), 0.0f, 1.0f); + clean.slip = rx::Clamp(Sane(p.slip), 0.0f, 1.0f); + clean.muffle = rx::Clamp(Sane(p.muffle), 0.0f, 1.0f); // thrust keeps its <0 "derive from rpm" sentinel; only a real request is // clamped into 0..1 (a NaN degrades to the sentinel, i.e. old behaviour). - clean.thrust = std::isfinite(p.thrust) ? (p.thrust < 0.0f ? -1.0f : std::clamp(p.thrust, 0.0f, 1.0f)) + clean.thrust = ::isfinite(p.thrust) ? (p.thrust < 0.0f ? -1.0f : rx::Clamp(p.thrust, 0.0f, 1.0f)) : -1.0f; - clean.gear_shift = std::clamp(Sane(p.gear_shift), -1.0f, 1.0f); - clean.skid_bias = std::clamp(Sane(p.skid_bias), -1.0f, 1.0f); + clean.gear_shift = rx::Clamp(Sane(p.gear_shift), -1.0f, 1.0f); + clean.skid_bias = rx::Clamp(Sane(p.skid_bias), -1.0f, 1.0f); // Publish: atomically swap our filled slot in as the latest and take back the // slot the consumer last released. release so the slot writes above are visible // to the Fetch that acquires this index. write_ now names a slot the consumer // is not reading (the three indices stay a permutation of {0,1,2}). - const u32 prev = shared_.exchange(write_ | kDirty, std::memory_order_release); + const u32 prev = shared_.exchange(write_ | kDirty, base::memory_order_release); write_ = prev & kIndexMask; } bool ParamMailbox::Fetch(SynthParams* out) { - if (!(shared_.load(std::memory_order_acquire) & kDirty)) return false; + if (!(shared_.load(base::memory_order_acquire) & kDirty)) return false; // Hand our old read slot back and take the freshly published one. acquire pairs // with the Publish release so the slot's contents are visible before we read. - const u32 prev = shared_.exchange(read_, std::memory_order_acquire); + const u32 prev = shared_.exchange(read_, base::memory_order_acquire); read_ = prev & kIndexMask; *out = slot_[read_]; return true; } -SynthVoice::SynthVoice(u32 output_rate, std::unique_ptr synth) - : rate_(output_rate ? output_rate : 48000), synth_(std::move(synth)) { +SynthVoice::SynthVoice(u32 output_rate, base::UniquePointer synth) + : rate_(output_rate ? output_rate : 48000), synth_(base::move(synth)) { // Time constants tuned per field: rpm fast (rev-limiter bounce), pedal/slip // moderate, ground speed slow (wind should swell, not flutter). a_rpm_ = ChunkAlpha(0.015f, rate_); @@ -85,7 +88,7 @@ u32 SynthVoice::Read(f32* out, u32 frames) { } u32 done = 0; while (done < frames) { - const u32 n = std::min(kSmoothChunk, frames - done); + const u32 n = rx::Min(kSmoothChunk, frames - done); // Advance the smoothed parameters one sub-block toward the latest target. smoothed_.rpm += (target.rpm - smoothed_.rpm) * a_rpm_; smoothed_.load += (target.load - smoothed_.load) * a_load_; diff --git a/engine/audio/synth_voice.h b/engine/audio/synth_voice.h index 09024056..3ca01f59 100644 --- a/engine/audio/synth_voice.h +++ b/engine/audio/synth_voice.h @@ -1,11 +1,12 @@ #ifndef RX_AUDIO_SYNTH_VOICE_H_ #define RX_AUDIO_SYNTH_VOICE_H_ -#include -#include #include "audio/audio_clip.h" +#include "base/atomic.h" +#include "base/memory/unique_pointer.h" #include "core/export.h" +#include "core/shared.h" #include "core/types.h" namespace rx::audio { @@ -59,10 +60,9 @@ struct SynthParams { // slot the other holds; the only shared mutation is one atomic exchange per // Publish / Fetch, which makes both sides wait-free (no spin, no retry). // -// It is reference-counted (held by shared_ptr) so the writer may outlive the -// reader: when the mixer retires and deletes a SynthVoice, a VehicleAudio still -// feeding telemetry publishes into a mailbox that is still alive, never freed -// memory. Publish sanitises the telemetry (NaN -> 0, ranges clamped) so the audio +// It is shared (rx::Shared) so the writer may outlive the reader: when the +// mixer retires and deletes a SynthVoice, a VehicleAudio still feeding +// telemetry publishes into a mailbox that is still alive, never freed memory. Publish sanitises the telemetry (NaN -> 0, ranges clamped) so the audio // thread always fetches values the oscillators can trust. class RX_AUDIO_EXPORT ParamMailbox { public: @@ -81,7 +81,7 @@ class RX_AUDIO_EXPORT ParamMailbox { // Holds the index of the last-published slot plus the dirty flag. Starts on // slot 2 (default-constructed, not dirty); the producer owns slot 0 and the // consumer owns slot 1 to begin with, keeping all three roles disjoint. - std::atomic shared_{2}; + base::Atomic shared_{2}; u32 write_ = 0; // producer-private slot index u32 read_ = 1; // consumer-private slot index }; @@ -111,7 +111,7 @@ class RX_AUDIO_EXPORT SynthVoice final : public Decoder { public: // `output_rate` must be the mixer's mix rate (Mixer::output_rate) so Read // needs no resampling. `synth` is the model rendered; it is owned here. - SynthVoice(u32 output_rate, std::unique_ptr synth); + SynthVoice(u32 output_rate, base::UniquePointer synth); // device thread (mixer) u32 channels() const override { return 1; } @@ -129,17 +129,17 @@ class RX_AUDIO_EXPORT SynthVoice final : public Decoder { void SetParams(const SynthParams& p) { params_->Publish(p); } // The parameter endpoint. A caller that outlives this voice (VehicleAudio, - // whose voices the mixer may retire and delete first) keeps its own shared_ptr + // whose voices the mixer may retire and delete first) keeps its own reference // and publishes through that, so a late update never touches a freed voice. - const std::shared_ptr& params() const { return params_; } + const Shared& params() const { return params_; } private: u32 rate_; - std::unique_ptr synth_; + base::UniquePointer synth_; // Shared parameter hand-off (see ParamMailbox). Owned jointly with whoever // feeds this voice, so the writer may outlive the reader. - std::shared_ptr params_ = std::make_shared(); + Shared params_ = Shared::Make(); // Device-thread-only smoothing state. SynthParams target_{}; // latest fetched value, held between Fetches diff --git a/engine/audio/thunder_synth.cc b/engine/audio/thunder_synth.cc index e17be77b..ff8188ec 100644 --- a/engine/audio/thunder_synth.cc +++ b/engine/audio/thunder_synth.cc @@ -1,7 +1,8 @@ #include "audio/thunder_synth.h" +#include "base/memory/unique_pointer.h" +#include "core/scalar.h" -#include -#include +#include namespace rx::audio { namespace { @@ -14,29 +15,29 @@ class ThunderDecoder final : public Decoder { ThunderDecoder(u32 rate, u32 seed, f32 energy, f32 distance_m) : rate_(rate), rate_f_(static_cast(rate)), rng_(seed ? seed : 0x9e3779b9u) { - energy_ = std::clamp(energy, 0.0f, 1.0f); - f32 dist = std::max(distance_m, 0.0f); + energy_ = rx::Clamp(energy, 0.0f, 1.0f); + f32 dist = rx::Max(distance_m, 0.0f); // Air absorption: the crack is high-frequency and dies within ~1 km; the // rumble survives but its brightness closes down with range. - crack_gain_ = 1.6f * energy_ * std::exp(-dist / 900.0f); - f32 muffle = std::min(dist / 3000.0f, 1.0f); - f32 cutoff_hz = std::min(2600.0f - 2250.0f * muffle, rate_f_ * 0.45f); - body_lp_k_ = std::clamp(1.0f - std::exp(-2.0f * 3.14159265f * cutoff_hz / rate_f_), + crack_gain_ = 1.6f * energy_ * ::exp(-dist / 900.0f); + f32 muffle = rx::Min(dist / 3000.0f, 1.0f); + f32 cutoff_hz = rx::Min(2600.0f - 2250.0f * muffle, rate_f_ * 0.45f); + body_lp_k_ = rx::Clamp(1.0f - ::exp(-2.0f * 3.14159265f * cutoff_hz / rate_f_), 0.0f, 1.0f); - f32 crack_hz = std::min(1800.0f, rate_f_ * 0.2f); - svf_f_ = 2.0f * std::sin(3.14159265f * crack_hz / rate_f_); + f32 crack_hz = rx::Min(1800.0f, rate_f_ * 0.2f); + svf_f_ = 2.0f * ::sin(3.14159265f * crack_hz / rate_f_); // Convert the old 48 kHz per-sample coefficients into rate-independent // time constants. Preserve the brown-noise variance at the reference rate. - brown_decay_ = std::exp(std::log(0.998f) * (48000.0f / rate_f_)); + brown_decay_ = ::exp(::log(0.998f) * (48000.0f / rate_f_)); brown_gain_ = 0.03f * - std::sqrt((1.0f - brown_decay_ * brown_decay_) / + ::sqrt((1.0f - brown_decay_ * brown_decay_) / (1.0f - 0.998f * 0.998f)); - wobble_k_ = 1.0f - std::exp(-7.20054f / rate_f_); - bump_decay_step_ = std::exp(-1.6f / rate_f_); - global_decay_step_ = std::exp(-1.0f / ((2.2f + 2.0f * energy_) * rate_f_)); - crack_decay_step_ = std::exp(-26.0f / rate_f_); + wobble_k_ = 1.0f - ::exp(-7.20054f / rate_f_); + bump_decay_step_ = ::exp(-1.6f / rate_f_); + global_decay_step_ = ::exp(-1.0f / ((2.2f + 2.0f * energy_) * rate_f_)); + crack_decay_step_ = ::exp(-26.0f / rate_f_); seconds_ = 5.0f + 4.0f * energy_; total_frames_ = static_cast(seconds_ * rate_f_); @@ -61,7 +62,7 @@ class ThunderDecoder final : public Decoder { u32 Read(float *out, u32 frames) override { if (frame_ >= total_frames_) return 0; - u32 n = static_cast(std::min(frames, total_frames_ - frame_)); + u32 n = static_cast(rx::Min(frames, total_frames_ - frame_)); for (u32 i = 0; i < n; ++i) { f32 t = static_cast(frame_ + i) / rate_f_; f32 white = Noise(); @@ -87,7 +88,7 @@ class ThunderDecoder final : public Decoder { f32 dt_b = t - bump_t_[b]; if (dt_b > 0.0f) { if (bump_decay_[b] == 0.0f) - bump_decay_[b] = std::exp(-dt_b * 1.6f); + bump_decay_[b] = ::exp(-dt_b * 1.6f); else bump_decay_[b] *= bump_decay_step_; env += bump_w_[b] * dt_b * bump_decay_[b] * 4.349251f; @@ -103,10 +104,10 @@ class ThunderDecoder final : public Decoder { // spiking past full scale. f32 remaining = static_cast(total_frames_ - 1u - (frame_ + i)) / rate_f_; - f32 tail = std::clamp(remaining / 0.4f, 0.0f, 1.0f); + f32 tail = rx::Clamp(remaining / 0.4f, 0.0f, 1.0f); tail = tail * tail * (3.0f - 2.0f * tail); f32 s = (crack + body) * tail; - out[i] = s / (1.0f + std::fabs(s)) * 0.95f; // asymptote inside full scale + out[i] = s / (1.0f + ::fabs(s)) * 0.95f; // asymptote inside full scale } frame_ += n; return n; @@ -153,12 +154,12 @@ class ThunderDecoder final : public Decoder { } // namespace -std::unique_ptr MakeThunder(u32 output_rate, u32 seed, f32 energy, +base::UniquePointer MakeThunder(u32 output_rate, u32 seed, f32 energy, f32 distance_m) { - if (output_rate == 0 || output_rate > 768000u || !std::isfinite(energy) || - !std::isfinite(distance_m)) + if (output_rate == 0 || output_rate > 768000u || !::isfinite(energy) || + !::isfinite(distance_m)) return nullptr; - return std::make_unique(output_rate, seed, energy, + return base::MakeUnique(output_rate, seed, energy, distance_m); } diff --git a/engine/audio/thunder_synth.h b/engine/audio/thunder_synth.h index f7206806..934d3ff4 100644 --- a/engine/audio/thunder_synth.h +++ b/engine/audio/thunder_synth.h @@ -1,9 +1,9 @@ #ifndef RX_AUDIO_THUNDER_SYNTH_H_ #define RX_AUDIO_THUNDER_SYNTH_H_ -#include #include "audio/audio_clip.h" +#include "base/memory/unique_pointer.h" #include "core/export.h" #include "core/types.h" @@ -21,7 +21,7 @@ namespace rx::audio { // or null for a zero rate, a rate above 768 kHz, or non-finite input; // the mixer retires the voice at end of stream. `seed` varies the echo timing // and roll character per strike, `energy` (0..1) scales length and weight. -RX_AUDIO_EXPORT std::unique_ptr +RX_AUDIO_EXPORT base::UniquePointer MakeThunder(u32 output_rate, u32 seed, f32 energy, f32 distance_m); } // namespace rx::audio diff --git a/engine/audio/vehicle_audio.cc b/engine/audio/vehicle_audio.cc index acbbccd0..e5ce30e4 100644 --- a/engine/audio/vehicle_audio.cc +++ b/engine/audio/vehicle_audio.cc @@ -1,11 +1,13 @@ #include "audio/vehicle_audio.h" -#include -#include -#include +#include +#include #include "audio/aux_synth.h" #include "audio/mixer.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "core/scalar.h" namespace rx::audio { namespace { @@ -45,8 +47,8 @@ f32 Dist2(const Vec3& a, const Vec3& b) { VehicleAudio::VehicleAudio(Mixer& mixer, const EnginePreset& preset) : mixer_(&mixer) { const u32 rate = mixer.output_rate(); - auto start = [&](Layer& layer, std::unique_ptr model, const Attenuation& atten) { - auto voice = std::make_unique(rate, std::move(model)); + auto start = [&](Layer& layer, base::UniquePointer model, const Attenuation& atten) { + auto voice = base::MakeUnique(rate, base::move(model)); // Share the parameter mailbox before handing the voice to the mixer: the // mailbox outlives the voice, so Update can keep publishing after the mixer // retires and deletes the SynthVoice (no dangling pointer to dereference). @@ -55,20 +57,20 @@ VehicleAudio::VehicleAudio(Mixer& mixer, const EnginePreset& preset) : mixer_(&m params.positional = true; params.gain = 0.0f; // rise in from silence as Update feeds telemetry params.atten = atten; - layer.voice = mixer.Play(std::move(voice), params); + layer.voice = mixer.Play(base::move(voice), params); layer.sent_gain = 0.0f; }; - start(engine_, std::make_unique(preset, rate), EngineAtten()); - start(skid_, std::make_unique(rate), AuxAtten()); - start(wind_, std::make_unique(rate), AuxAtten()); + start(engine_, base::MakeUnique(preset, rate), EngineAtten()); + start(skid_, base::MakeUnique(rate), AuxAtten()); + start(wind_, base::MakeUnique(rate), AuxAtten()); } VehicleAudio::~VehicleAudio() { Stop(); } void VehicleAudio::SetLayerGain(Layer& layer, f32 gain) { if (!layer.voice) return; - if (std::abs(gain - layer.sent_gain) < kGainEpsilon) return; + if (::abs(gain - layer.sent_gain) < kGainEpsilon) return; mixer_->SetVoiceGain(layer.voice, gain); layer.sent_gain = gain; } @@ -83,9 +85,9 @@ void VehicleAudio::Update(const VehicleAudioState& state) { f32 skid_bias = 0.0f; // -1 rear .. +1 front, shades the skid band f32 lat_bias = 0.0f; // -1 left .. +1 right, pans the skid voice sideways if (state.wheel_count > 0) { - const u32 n = std::min(state.wheel_count, 4u); + const u32 n = rx::Min(state.wheel_count, 4u); f32 mx = 0.0f; - for (u32 w = 0; w < n; ++w) mx = std::max(mx, state.wheel_slip[w]); + for (u32 w = 0; w < n; ++w) mx = rx::Max(mx, state.wheel_slip[w]); slip = mx; // intensity from the worst-slipping wheel if (n >= 4) { // Order is FL FR RL RR. Bias by which side / axle washes more. @@ -120,7 +122,7 @@ void VehicleAudio::Update(const VehicleAudioState& state) { SynthParams params; params.rpm = state.rpm; params.load = state.load; - params.throttle = std::fabs(state.throttle); // signed input (astern); use its magnitude + params.throttle = ::fabs(state.throttle); // signed input (astern); use its magnitude params.speed_mps = state.speed_mps; params.slip = slip; params.muffle = state.submerged ? 1.0f : 0.0f; // synth-side dark/duck (smoothed) @@ -173,7 +175,7 @@ void VehicleAudio::Stop() { if (engine_.voice) mixer_->Stop(engine_.voice, 0.08f); if (skid_.voice) mixer_->Stop(skid_.voice, 0.08f); if (wind_.voice) mixer_->Stop(wind_.voice, 0.08f); - engine_.params = skid_.params = wind_.params = nullptr; + engine_.params = skid_.params = wind_.params = Shared(); engine_.voice = skid_.voice = wind_.voice = 0; stopped_ = true; } diff --git a/engine/audio/vehicle_audio.h b/engine/audio/vehicle_audio.h index 40ddf85e..39640c41 100644 --- a/engine/audio/vehicle_audio.h +++ b/engine/audio/vehicle_audio.h @@ -1,8 +1,7 @@ #ifndef RX_AUDIO_VEHICLE_AUDIO_H_ #define RX_AUDIO_VEHICLE_AUDIO_H_ -#include -#include +#include #include "audio/engine_synth.h" #include "audio/synth_voice.h" @@ -75,11 +74,11 @@ class RX_AUDIO_EXPORT VehicleAudio { private: struct Layer { u32 voice = 0; // mixer voice id (0 = not started) - // Parameter endpoint, shared with the SynthVoice. Held by shared_ptr rather + // Parameter endpoint, shared with the SynthVoice. A counted reference rather // than a raw voice pointer so publishing survives the mixer retiring and // deleting the voice: a late Update lands in a still-live mailbox, not freed // memory. - std::shared_ptr params; + Shared params; f32 sent_gain = -1.0f; // last gain pushed, to suppress no-op commands }; diff --git a/engine/audio/wav.cc b/engine/audio/wav.cc index 16661c30..a5f4ba33 100644 --- a/engine/audio/wav.cc +++ b/engine/audio/wav.cc @@ -1,7 +1,9 @@ #include "audio/wav.h" -#include +#include +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" #include "core/log.h" #include "core/types.h" @@ -66,8 +68,8 @@ struct Format { u16 samples_per_block = 0; // MS-ADPCM predictor coefficients (pairs), defaulted to the standard 7 the // encoder uses unless the file ships its own table. - std::vector coef1; - std::vector coef2; + base::Vector coef1; + base::Vector coef2; }; float SampleU8(u8 v) { return (static_cast(v) - 128.0f) / 128.0f; } @@ -90,7 +92,7 @@ bool DecodePcm(Reader& r, const Format& fmt, AudioClip* out) { if (fmt.tag == kFormatIeeeFloat && fmt.bits == 32) { u32 bits = r.U32(); float f; - std::memcpy(&f, &bits, sizeof(f)); + base::MemCopy(&f, &bits, sizeof(f)); out->samples.push_back(f); } else if (fmt.bits == 8) { out->samples.push_back(SampleU8(r.U8())); @@ -142,8 +144,8 @@ int ImaNibble(u8 nibble, int& predictor, int& index) { bool DecodeImaAdpcm(Reader& r, const Format& fmt, AudioClip* out) { const u32 ch = fmt.channels; if (ch == 0 || fmt.block_align < 4u * ch) return false; - std::vector block(fmt.block_align); - std::vector predictor(ch), index(ch); + base::Vector block(fmt.block_align); + base::Vector predictor(ch), index(ch); while (r.left >= fmt.block_align && r.ok) { for (u16 i = 0; i < fmt.block_align; ++i) block[i] = r.U8(); const u8* b = block.data(); @@ -159,7 +161,7 @@ bool DecodeImaAdpcm(Reader& r, const Format& fmt, AudioClip* out) { // Decode each channel's word into a staging row, then emit the 8 frames // interleaved so the output stays channel-major per frame. const u8* end = block.data() + fmt.block_align; - std::vector staged(static_cast(ch) * 8); + base::Vector staged(static_cast(ch) * 8); while (b + 4u * ch <= end) { for (u32 c = 0; c < ch; ++c) { for (int n = 0; n < 4; ++n) { @@ -199,7 +201,7 @@ int MsNibble(u8 nibble, int& samp1, int& samp2, int& delta, i16 coef1, i16 coef2 bool DecodeMsAdpcm(Reader& r, const Format& fmt, AudioClip* out) { const u32 ch = fmt.channels; if (ch == 0 || ch > 2 || fmt.block_align < 7u * ch) return false; - std::vector block(fmt.block_align); + base::Vector block(fmt.block_align); while (r.left >= fmt.block_align && r.ok) { for (u16 i = 0; i < fmt.block_align; ++i) block[i] = r.U8(); const u8* b = block.data(); diff --git a/engine/audio/xwma.cc b/engine/audio/xwma.cc index 50fa27f2..cbfc4523 100644 --- a/engine/audio/xwma.cc +++ b/engine/audio/xwma.cc @@ -1,10 +1,13 @@ #include "audio/xwma.h" -#include -#include +#include #include "audio/ffmpeg_codec.h" #include "audio/wav.h" +#include "base/containers/vector.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/string_ref.h" #include "core/log.h" namespace rx::audio { @@ -39,8 +42,8 @@ Chunk FindChunk(ByteSpan riff, u32 tag) { // Builds a standard RIFF/WAVE file around a raw fmt chunk and PCM data, so a Wwise // PCM payload can be decoded by the native WAV path instead of a codec backend. -std::vector WrapAsWave(const Chunk& fmt, const Chunk& data) { - std::vector w; +base::Vector WrapAsWave(const Chunk& fmt, const Chunk& data) { + base::Vector w; auto tag = [&](const char* t) { for (int i = 0; i < 4; ++i) w.push_back(static_cast(t[i])); }; @@ -63,7 +66,7 @@ std::vector WrapAsWave(const Chunk& fmt, const Chunk& data) { // Wwise PCM and IEEE float can be decoded natively; everything else (Wwise Vorbis, // xWMA, WMA) needs a codec backend. -std::unique_ptr OpenWem(ByteSpan bytes) { +base::UniquePointer OpenWem(ByteSpan bytes) { Chunk fmt = FindChunk(bytes, FourCc('f', 'm', 't', ' ')); Chunk data = FindChunk(bytes, FourCc('d', 'a', 't', 'a')); if (fmt.data && fmt.size >= 16 && data.data) { @@ -72,9 +75,9 @@ std::unique_ptr OpenWem(ByteSpan bytes) { u16 real = codec; if (codec == kExtensible && fmt.size >= 26) real = ReadU16LE(fmt.data + 24); if (real == kPcm || real == kFloat) { - std::vector wave = WrapAsWave(fmt, data); + base::Vector wave = WrapAsWave(fmt, data); AudioClip clip; - if (DecodeWav(ByteSpan{wave.data(), wave.size()}, &clip)) return MakeClipDecoder(std::move(clip)); + if (DecodeWav(ByteSpan{wave.data(), wave.size()}, &clip)) return MakeClipDecoder(base::move(clip)); } } // Compressed Wwise media (typically Wwise Vorbis): best-effort through FFmpeg. @@ -83,15 +86,15 @@ std::unique_ptr OpenWem(ByteSpan bytes) { // FUZ = "FUZE" magic, a version, a LIP (lipsync) block, then an embedded xWMA // RIFF. Strip the header and lip block and decode the audio that follows. -std::unique_ptr OpenFuz(ByteSpan bytes) { +base::UniquePointer OpenFuz(ByteSpan bytes) { if (bytes.size() < 12) return nullptr; const u32 lip_size = ReadU32LE(bytes.data() + 8); size_t audio_off = 12 + lip_size; if (audio_off + 12 > bytes.size()) { // Some tools omit/!align the lip size; fall back to scanning for the RIFF. - audio_off = std::string_view(reinterpret_cast(bytes.data()), bytes.size()) + audio_off = base::StringRef(reinterpret_cast(bytes.data()), bytes.size()) .find("RIFF", 12); - if (audio_off == std::string_view::npos) return nullptr; + if (audio_off == base::StringRef::npos) return nullptr; } ByteSpan audio = bytes.subspan(audio_off); return OpenFfmpegDecoder(audio); @@ -99,7 +102,7 @@ std::unique_ptr OpenFuz(ByteSpan bytes) { } // namespace -std::unique_ptr OpenCompressed(ByteSpan bytes, CompressedKind kind) { +base::UniquePointer OpenCompressed(ByteSpan bytes, CompressedKind kind) { switch (kind) { case CompressedKind::kXwma: return OpenFfmpegDecoder(bytes); diff --git a/engine/audio/xwma.h b/engine/audio/xwma.h index f10fa6dc..614ce4cb 100644 --- a/engine/audio/xwma.h +++ b/engine/audio/xwma.h @@ -1,9 +1,9 @@ #ifndef RX_AUDIO_XWMA_H_ #define RX_AUDIO_XWMA_H_ -#include #include "audio/audio_clip.h" +#include "base/memory/unique_pointer.h" #include "core/types.h" namespace rx::audio { @@ -18,7 +18,7 @@ enum class CompressedKind { kXwma, kFuz, kWem }; // decoded natively; the WMA and Wwise codecs are routed to the optional FFmpeg // backend (see ffmpeg_codec.h), which is null when the engine was built without // it. Returns null when nothing can decode the data. -std::unique_ptr OpenCompressed(ByteSpan bytes, CompressedKind kind); +base::UniquePointer OpenCompressed(ByteSpan bytes, CompressedKind kind); } // namespace rx::audio diff --git a/engine/authoring/command_bridge.cc b/engine/authoring/command_bridge.cc index 0f1b84e6..947de2b3 100644 --- a/engine/authoring/command_bridge.cc +++ b/engine/authoring/command_bridge.cc @@ -1,9 +1,7 @@ #include "authoring/command_bridge.h" -#include -#include -#include - +#include "base/strings/xstring.h" +#include "core/format.h" #include "core/log.h" #include "ecs/entity.h" #include "script/handler_context.h" @@ -50,8 +48,8 @@ const char* WireTypeName(rpc::RpcValue::Type type) { // "(entity, vec3)", the signature as the caller has to spell it, for a // mismatch message that says what was wanted rather than only what was wrong. -std::string SigText(const script::HandlerSig& sig) { - std::string text = "("; +base::String SigText(const script::HandlerSig& sig) { + base::String text = "("; for (u8 i = 0; i < sig.count; ++i) { if (i) text += ", "; text += script::ScriptTypeName(sig.params[i]); @@ -72,8 +70,8 @@ ecs::Entity UnpackEntity(i64 packed) { return ecs::Entity{static_cast(bits), static_cast(bits >> 32)}; } -std::string TypeError(size_t index, ScriptType want, const rpc::RpcValue& got) { - return std::format("arg {} expects {}, got {}", index, script::ScriptTypeName(want), +base::String TypeError(size_t index, ScriptType want, const rpc::RpcValue& got) { + return rx::StrFormat("arg {} expects {}, got {}", index, script::ScriptTypeName(want), WireTypeName(got.type())); } @@ -82,11 +80,11 @@ std::string TypeError(size_t index, ScriptType want, const rpc::RpcValue& got) { // member out of a defaulted ScriptValue. bool BuildStack(const script::HandlerSig& sig, const rpc::RpcArgs& args, script::HandlerContext& ctx, script::ScriptStack* stack, - std::string* error) { + base::String* error) { size_t want = 0; for (u8 p = 0; p < sig.count; ++p) want += WireSlots(sig.params[p]); if (args.size() != want) { - *error = std::format("expects {} arg(s) for {}, got {}", want, SigText(sig), + *error = rx::StrFormat("expects {} arg(s) for {}, got {}", want, SigText(sig), args.size()); return false; } @@ -153,7 +151,7 @@ bool BuildStack(const script::HandlerSig& sig, const rpc::RpcArgs& args, ScriptValue::Symbol(ctx.Syms().Intern(script::ScriptStringView(value.as_string())))); break; case ScriptType::kVoid: - *error = std::format("arg {} has no wire representation (void)", at); + *error = rx::StrFormat("arg {} has no wire representation (void)", at); return false; } at += WireSlots(sig.params[p]); @@ -165,7 +163,7 @@ bool BuildStack(const script::HandlerSig& sig, const rpc::RpcArgs& args, // context's scratch arena here: the arena is reset once the caller is done // polling, and the reply outlives that. bool BuildReply(ScriptType ret, const ScriptValue& value, script::HandlerContext& ctx, - rpc::RpcArgs* out, std::string* error) { + rpc::RpcArgs* out, base::String* error) { switch (ret) { case ScriptType::kVoid: return true; @@ -189,10 +187,10 @@ bool BuildReply(ScriptType ret, const ScriptValue& value, script::HandlerContext return true; } case ScriptType::kString: - out->emplace_back(std::string(value.as_str().view())); + out->emplace_back(base::String(value.as_str().view())); return true; case ScriptType::kSymbol: - out->emplace_back(std::string(ctx.Syms().Resolve(value.as_symbol()).view())); + out->emplace_back(base::String(ctx.Syms().Resolve(value.as_symbol()).view())); return true; } *error = "command returns a type with no wire representation"; @@ -210,7 +208,7 @@ CommandBridge::CommandBridge(script::HandlerRegistry& commands, script::HandlerC // captures the HandlerDesc address, which a later Add would relocate. const script::ScriptStringView name = desc.name; const script::HandlerSig sig = desc.sig; - registry_.On(std::string(name.view()), + registry_.On(base::String(name.view()), [this, name, sig](const rpc::RpcContext&, const rpc::RpcArgs& args) { // No reply slot means this Dispatch did not come through // Invoke, so it never passed the trust check: do nothing. @@ -218,15 +216,15 @@ CommandBridge::CommandBridge(script::HandlerRegistry& commands, script::HandlerC Reply& reply = *pending_; script::ScriptStack stack; stack.reserve(sig.count); - std::string detail; + base::String detail; if (!BuildStack(sig, args, ctx_, &stack, &detail)) { - reply.error = std::format("{}: {}", name.view(), detail); + reply.error = rx::StrFormat("{}: {}", name.view(), detail); return; } script::ScriptArgs script_args(stack); const ScriptValue result = commands_.Dispatch(ctx_, name, script_args); reply.ok = BuildReply(sig.ret, result, ctx_, &reply.values, &detail); - if (!reply.ok) reply.error = std::format("{}: {}", name.view(), detail); + if (!reply.ok) reply.error = rx::StrFormat("{}: {}", name.view(), detail); }); } } @@ -245,7 +243,7 @@ CommandBridge::Reply CommandBridge::Invoke(const rpc::RpcContext& ctx, pending_ = &reply; const bool dispatched = registry_.Dispatch(ctx, call); pending_ = nullptr; - if (!dispatched) reply.error = std::format("unknown command '{}'", call.name); + if (!dispatched) reply.error = rx::StrFormat("unknown command '{}'", call.name); return reply; } diff --git a/engine/authoring/command_bridge.h b/engine/authoring/command_bridge.h index 06e22632..121cf387 100644 --- a/engine/authoring/command_bridge.h +++ b/engine/authoring/command_bridge.h @@ -1,8 +1,8 @@ #ifndef RX_AUTHORING_COMMAND_BRIDGE_H_ #define RX_AUTHORING_COMMAND_BRIDGE_H_ -#include +#include "base/strings/xstring.h" #include "core/export.h" #include "core/types.h" #include "rpc/rpc_message.h" @@ -62,7 +62,7 @@ class RX_AUTHORING_EXPORT CommandBridge { // a void command. Strings are copied out of the context's scratch arena, so // the reply stays valid after the arena is reset. rpc::RpcArgs values; - std::string error; // set only when !ok, safe to show a caller + base::String error; // set only when !ok, safe to show a caller }; // Checks, marshals, dispatches. Never throws and never asserts on caller data: diff --git a/engine/authoring/command_endpoint.cc b/engine/authoring/command_endpoint.cc index 7a66dbb6..685aeb6a 100644 --- a/engine/authoring/command_endpoint.cc +++ b/engine/authoring/command_endpoint.cc @@ -1,6 +1,12 @@ #include "authoring/command_endpoint.h" #include "authoring/command_bridge.h" +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/optional.h" +#include "base/strings/xstring.h" +#include "core/format.h" #include "core/log.h" #include "rpc/rpc_message.h" @@ -11,11 +17,9 @@ #include #include -#include -#include -#include -#include -#include +#include +#include +#include #endif namespace rx::authoring { @@ -49,7 +53,7 @@ constexpr int kSendFlags = 0; // apple: SO_NOSIGPIPE is set on the socket inste // there rather than growing a second transport. CommandEndpoint::~CommandEndpoint() = default; -bool CommandEndpoint::Start(const std::string& path, std::string* error) { +bool CommandEndpoint::Start(const base::String& path, base::String* error) { (void)path; if (error) *error = "the authoring endpoint needs a posix unix socket"; return false; @@ -102,7 +106,7 @@ bool PeerUid(int fd, uid_t* uid) { #endif } -void PutU32(std::vector& out, u32 v) { +void PutU32(base::Vector& out, u32 v) { for (int i = 0; i < 4; ++i) out.push_back(static_cast(v >> (8 * i))); } @@ -114,9 +118,9 @@ u32 ReadU32(const u8* p) { CommandEndpoint::~CommandEndpoint() { Stop(); } -bool CommandEndpoint::Start(const std::string& path, std::string* error) { - auto fail = [&](std::string message) { - if (error) *error = std::move(message); +bool CommandEndpoint::Start(const base::String& path, base::String* error) { + auto fail = [&](base::String message) { + if (error) *error = base::move(message); if (listener_ >= 0) { ::close(listener_); listener_ = -1; @@ -128,23 +132,23 @@ bool CommandEndpoint::Start(const std::string& path, std::string* error) { addr.sun_family = AF_UNIX; if (path.size() >= sizeof(addr.sun_path)) { return fail("socket path is longer than " + - std::to_string(sizeof(addr.sun_path) - 1) + " bytes"); + rx::ToString(sizeof(addr.sun_path) - 1) + " bytes"); } - std::memcpy(addr.sun_path, path.c_str(), path.size()); + base::MemCopy(addr.sun_path, path.c_str(), path.size()); if (SocketIsLive(addr)) return fail("another process is already serving " + path); ::unlink(path.c_str()); listener_ = ::socket(AF_UNIX, SOCK_STREAM, 0); - if (listener_ < 0) return fail(std::string("socket: ") + std::strerror(errno)); - if (!SetNonBlocking(listener_)) return fail(std::string("fcntl: ") + std::strerror(errno)); + if (listener_ < 0) return fail(base::String("socket: ") + ::strerror(errno)); + if (!SetNonBlocking(listener_)) return fail(base::String("fcntl: ") + ::strerror(errno)); if (::bind(listener_, reinterpret_cast(&addr), sizeof(addr)) != 0) - return fail(std::string("bind: ") + std::strerror(errno)); + return fail(base::String("bind: ") + ::strerror(errno)); // The uid check at accept is the real gate; the mode keeps a call from another // account from even reaching it. if (::chmod(path.c_str(), S_IRUSR | S_IWUSR) != 0) - return fail(std::string("chmod: ") + std::strerror(errno)); - if (::listen(listener_, 4) != 0) return fail(std::string("listen: ") + std::strerror(errno)); + return fail(base::String("chmod: ") + ::strerror(errno)); + if (::listen(listener_, 4) != 0) return fail(base::String("listen: ") + ::strerror(errno)); path_ = path; return true; @@ -194,7 +198,7 @@ void CommandEndpoint::Accept() { bool CommandEndpoint::Serve(int index, CommandBridge& bridge) { const int fd = clients_[static_cast(index)]; - std::vector& buffer = inbox_[static_cast(index)]; + base::Vector& buffer = inbox_[static_cast(index)]; u8 chunk[4096]; for (;;) { @@ -221,10 +225,10 @@ bool CommandEndpoint::Serve(int index, CommandBridge& bridge) { const u8* payload = buffer.data() + consumed + 4; rpc::RpcCall reply; - std::optional call = rpc::DecodeCall(payload, length); + base::Optional call = rpc::DecodeCall(payload, length); if (!call) { reply.name = "error"; - reply.args.emplace_back(std::string("malformed rpc frame")); + reply.args.emplace_back(base::String("malformed rpc frame")); } else { // The uid check at accept is what makes this a trusted origin; the sender // id is how that fact reaches the bridge (see kLocalSender). @@ -232,14 +236,14 @@ bool CommandEndpoint::Serve(int index, CommandBridge& bridge) { CommandBridge::Reply result = bridge.Invoke(ctx, *call); reply.name = result.ok ? "ok" : "error"; if (result.ok) - reply.args = std::move(result.values); + reply.args = base::move(result.values); else - reply.args.emplace_back(std::move(result.error)); + reply.args.emplace_back(base::move(result.error)); } consumed += 4 + length; - std::vector frame; - const std::vector encoded = rpc::EncodeCall(reply); + base::Vector frame; + const base::Vector encoded = rpc::EncodeCall(reply); PutU32(frame, static_cast(encoded.size())); frame.insert(frame.end(), encoded.begin(), encoded.end()); // A reply is tens of bytes against a socket buffer of hundreds of kilobytes, diff --git a/engine/authoring/command_endpoint.h b/engine/authoring/command_endpoint.h index 5e39e36b..ddee830a 100644 --- a/engine/authoring/command_endpoint.h +++ b/engine/authoring/command_endpoint.h @@ -1,9 +1,9 @@ #ifndef RX_AUTHORING_COMMAND_ENDPOINT_H_ #define RX_AUTHORING_COMMAND_ENDPOINT_H_ -#include -#include +#include "base/containers/vector.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/types.h" @@ -34,7 +34,7 @@ class RX_AUTHORING_EXPORT CommandEndpoint { // Binds and listens on `path`, replacing a stale socket file left by a crashed // run. False + *error on any failure (a path in a missing directory, a path // already served by a live process, an unsupported platform). - bool Start(const std::string& path, std::string* error); + bool Start(const base::String& path, base::String* error); // Accepts new connections and serves every complete frame that has arrived, // then returns. Nonblocking: a caller mid-frame is left buffered for the next @@ -51,9 +51,9 @@ class RX_AUTHORING_EXPORT CommandEndpoint { bool Serve(int index, CommandBridge& bridge); int listener_ = -1; - std::string path_; - std::vector clients_; - std::vector> inbox_; // per client, parallel to clients_ + base::String path_; + base::Vector clients_; + base::Vector> inbox_; // per client, parallel to clients_ }; } // namespace rx::authoring diff --git a/engine/character/character.cc b/engine/character/character.cc index 0c49818b..05d5f081 100644 --- a/engine/character/character.cc +++ b/engine/character/character.cc @@ -1,8 +1,9 @@ #include "character/character.h" -#include -#include +#include +#include +#include "core/scalar.h" #include "ecs/world.h" #include "scene/camera_rig.h" #include "scene/components.h" @@ -12,7 +13,7 @@ namespace { constexpr f32 kTwoPi = 6.28318530717958647692f; -f32 WrapAngle(f32 a) { return std::remainder(a, kTwoPi); } +f32 WrapAngle(f32 a) { return ::remainder(a, kTwoPi); } // Heading quaternion: yaw about -Y so a character's forward (-Z at yaw 0) agrees // with scene::CameraOrbit's yaw and the camera it drives. @@ -20,7 +21,7 @@ Quat HeadingQuat(f32 yaw) { return QuatFromAxisAngle({0, -1, 0}, yaw); } f32 MoveTowardScalar(f32 current, f32 target, f32 max_delta) { const f32 d = target - current; - if (std::abs(d) <= max_delta) return target; + if (::abs(d) <= max_delta) return target; return current + (d > 0 ? max_delta : -max_delta); } @@ -28,7 +29,7 @@ f32 MoveTowardScalar(f32 current, f32 target, f32 max_delta) { // `half_life` seconds. Converges without overshoot; half_life <= 0 snaps. f32 ExpApproach(f32 current, f32 target, f32 half_life, f32 dt) { if (half_life <= 0.0f) return target; - const f32 t = 1.0f - std::exp2(-dt / half_life); + const f32 t = 1.0f - ::exp2(-dt / half_life); return current + (target - current) * t; } @@ -36,13 +37,13 @@ f32 ExpApproach(f32 current, f32 target, f32 half_life, f32 dt) { f32 ExpApproachAngle(f32 current, f32 target, f32 half_life, f32 dt) { const f32 diff = WrapAngle(target - current); if (half_life <= 0.0f) return WrapAngle(current + diff); - const f32 t = 1.0f - std::exp2(-dt / half_life); + const f32 t = 1.0f - ::exp2(-dt / half_life); return WrapAngle(current + diff * t); } // Heading yaw for a horizontal world direction, inverse of HeadingQuat's forward // (forward at yaw 0 is -Z, yaw increases toward +X): forward = (sin y, 0, -cos y). -f32 YawFromDir(const Vec3& d) { return std::atan2(d.x, -d.z); } +f32 YawFromDir(const Vec3& d) { return ::atan2(d.x, -d.z); } Vec3 MoveTowardVec(const Vec3& current, const Vec3& target, f32 max_delta) { const Vec3 d = target - current; @@ -66,7 +67,7 @@ f32 GaitSpeed(const CharacterMovementSettings& s, CharacterGait gait) { // Jolt cylinder half-height (excludes the two radius hemispheres) for a total // tip-to-tip capsule height. f32 CylinderHalf(f32 total_height, f32 radius) { - return std::max(total_height * 0.5f - radius, 0.01f); + return rx::Max(total_height * 0.5f - radius, 0.01f); } Vec3 TransformFeet(const scene::Transform& t) { @@ -87,7 +88,7 @@ void RemoveIfPresent(ecs::World& world, ecs::Entity e) { } // namespace void StepCharacters(ecs::World& world, physics::PhysicsWorld& physics, f32 dt) { - if (!std::isfinite(dt) || dt <= 0) return; + if (!::isfinite(dt) || dt <= 0) return; world.Each( @@ -126,7 +127,7 @@ void StepCharacters(ecs::World& world, physics::PhysicsWorld& physics, f32 dt) { } else if (stance == CharacterStance::kCrouching) { // Crouch exit needs headroom: sweep a sphere up through the gap the // standing capsule would reclaim. - const f32 gap = std::max(shape.standing_height - shape.crouched_height, 0.0f); + const f32 gap = rx::Max(shape.standing_height - shape.crouched_height, 0.0f); bool clear = true; if (gap > 1e-4f) { const Vec3 origin = feet + up * (shape.crouched_height - shape.standing_radius); @@ -142,15 +143,15 @@ void StepCharacters(ecs::World& world, physics::PhysicsWorld& physics, f32 dt) { const f32 target_blend = stance == CharacterStance::kCrouching ? 1.0f : 0.0f; f32 blend = MoveTowardScalar(prev_blend, target_blend, shape.crouch_blend_speed * dt); - blend = std::clamp(blend, 0.0f, 1.0f); + blend = rx::Clamp(blend, 0.0f, 1.0f); - const f32 radius = std::lerp(shape.standing_radius, shape.crouched_radius, blend); - const f32 height = std::lerp(shape.standing_height, shape.crouched_height, blend); + const f32 radius = rx::Lerp(shape.standing_radius, shape.crouched_radius, blend); + const f32 height = rx::Lerp(shape.standing_height, shape.crouched_height, blend); const f32 half_height = CylinderHalf(height, radius); const f32 new_total_half = half_height + radius; - const bool dims_changed = std::abs(new_total_half - old_total_half) > 1e-4f || - std::abs(radius - body.radius) > 1e-4f; + const bool dims_changed = ::abs(new_total_half - old_total_half) > 1e-4f || + ::abs(radius - body.radius) > 1e-4f; if (dims_changed && have_center) { const Vec3 new_center = feet + up * new_total_half; physics.SetCharacterPosition(body.id, new_center); @@ -165,11 +166,11 @@ void StepCharacters(ecs::World& world, physics::PhysicsWorld& physics, f32 dt) { } state.crouch_blend = blend; state.eye_height = - std::lerp(shape.standing_eye_height, shape.crouched_eye_height, blend); + rx::Lerp(shape.standing_eye_height, shape.crouched_eye_height, blend); // Analog move intent const Vec3 move_h{intent.move.x, 0, intent.move.z}; - const f32 throttle = std::clamp(Length(move_h), 0.0f, 1.0f); + const f32 throttle = rx::Clamp(Length(move_h), 0.0f, 1.0f); const Vec3 dir = throttle > 1e-4f ? Normalize(move_h) : Vec3{0, 0, 0}; // Body facing (turn smoothing) @@ -184,7 +185,7 @@ void StepCharacters(ecs::World& world, physics::PhysicsWorld& physics, f32 dt) { state.facing_yaw = state.yaw; // locked, raw } else if (throttle > 1e-3f) { const f32 target_yaw = YawFromDir(dir); - const f32 gap = std::abs(WrapAngle(target_yaw - state.facing_yaw)); + const f32 gap = ::abs(WrapAngle(target_yaw - state.facing_yaw)); // Sticky pivot: a reversal past pivot_angle latches the faster rate and // holds it through the bulk of the turn (released near completion), so a // 180 spins on the spot instead of arcing then stalling. @@ -199,7 +200,7 @@ void StepCharacters(ecs::World& world, physics::PhysicsWorld& physics, f32 dt) { const f32 raw_gait = stance == CharacterStance::kCrouching ? settings.crouch_speed : GaitSpeed(settings, intent.gait); - const f32 span = std::max(settings.sprint_speed - settings.walk_speed, 0.0f); + const f32 span = rx::Max(settings.sprint_speed - settings.walk_speed, 0.0f); if (settings.speed_blend_time > 0.0f && span > 1e-4f) { state.gait_speed = MoveTowardScalar(state.gait_speed, raw_gait, (span / settings.speed_blend_time) * dt); @@ -211,7 +212,7 @@ void StepCharacters(ecs::World& world, physics::PhysicsWorld& physics, f32 dt) { Vec3 horizontal{state.integration_velocity.x, 0, state.integration_velocity.z}; f32 accel = throttle > 1e-4f ? settings.ground_acceleration : settings.ground_deceleration; if (!state.grounded) accel = settings.ground_acceleration * settings.air_control; - horizontal = MoveTowardVec(horizontal, target_h, std::max(accel, 0.0f) * dt); + horizontal = MoveTowardVec(horizontal, target_h, rx::Max(accel, 0.0f) * dt); // Crisp stop: no ice-skate tail below the epsilon when input is released. if (throttle <= 1e-4f && Length(horizontal) < settings.stop_speed_epsilon) horizontal = {0, 0, 0}; @@ -223,9 +224,9 @@ void StepCharacters(ecs::World& world, physics::PhysicsWorld& physics, f32 dt) { // Jump buffer: a fresh press refills the window; otherwise it drains. const bool jump_pressed = intent.jump; if (jump_pressed) { - state.jump_buffer_timer = std::max(settings.jump_buffer_time, 0.0f); + state.jump_buffer_timer = rx::Max(settings.jump_buffer_time, 0.0f); } else { - state.jump_buffer_timer = std::max(0.0f, state.jump_buffer_timer - dt); + state.jump_buffer_timer = rx::Max(0.0f, state.jump_buffer_timer - dt); } const bool jump_wanted = jump_pressed || state.jump_buffer_timer > 0.0f; const bool coyote_ok = @@ -235,8 +236,8 @@ void StepCharacters(ecs::World& world, physics::PhysicsWorld& physics, f32 dt) { f32 vy = state.integration_velocity.y; if (grounded_prev && vy < 0) vy = 0; if (jump_wanted && coyote_ok) { - vy = std::sqrt(2.0f * std::max(settings.gravity, 0.0f) * - std::max(settings.jump_height, 0.0f)); + vy = ::sqrt(2.0f * rx::Max(settings.gravity, 0.0f) * + rx::Max(settings.jump_height, 0.0f)); state.jump_buffer_timer = 0; state.jump_consumed = true; } @@ -250,7 +251,7 @@ void StepCharacters(ecs::World& world, physics::PhysicsWorld& physics, f32 dt) { // out-thrust weight to lift off) and the ground clamp below still stops // any downward component, so this never bypasses fall/land handling. const Vec3& ext = intent.external_acceleration; - if (std::isfinite(ext.x) && std::isfinite(ext.y) && std::isfinite(ext.z)) + if (::isfinite(ext.x) && ::isfinite(ext.y) && ::isfinite(ext.z)) velocity += ext * dt; // Drive the controller @@ -261,7 +262,7 @@ void StepCharacters(ecs::World& world, physics::PhysicsWorld& physics, f32 dt) { const Vec3 out_feet = out_pos - up * (body.half_height + body.radius); const bool just_landed = !grounded_prev && grounded; - const f32 impact_speed = std::max(0.0f, -velocity.y); // pre-ground-clamp descent speed + const f32 impact_speed = rx::Max(0.0f, -velocity.y); // pre-ground-clamp descent speed state.grounded = grounded; // The velocity the resolved feet displacement actually realized (dt > 0 is // guaranteed at entry): the speed a wall or ceiling actually allowed. @@ -275,7 +276,7 @@ void StepCharacters(ecs::World& world, physics::PhysicsWorld& physics, f32 dt) { // the character the instant the obstruction clears. The grounded clamp // above covers the blocked-descent case. if (velocity.y > 0.0f && realized_velocity.y < velocity.y) - velocity.y = std::max(realized_velocity.y, 0.0f); + velocity.y = rx::Max(realized_velocity.y, 0.0f); // Keep the integrated (requested, now collision-reconciled) velocity as // the stepper's own gravity/accel bookkeeping for next step, and publish // the realized velocity for consumers (animation, camera, gameplay). @@ -304,13 +305,13 @@ void StepCharacters(ecs::World& world, physics::PhysicsWorld& physics, f32 dt) { } if (just_landed && shape.landing_dip_scale > 0.0f && impact_speed > shape.landing_dip_min_speed) { - const f32 dip = std::min((impact_speed - shape.landing_dip_min_speed) * + const f32 dip = rx::Min((impact_speed - shape.landing_dip_min_speed) * shape.landing_dip_scale, - std::max(shape.landing_dip_max, 0.0f)); - state.landing_dip = std::max(state.landing_dip, dip); + rx::Max(shape.landing_dip_max, 0.0f)); + state.landing_dip = rx::Max(state.landing_dip, dip); } state.landing_dip = shape.landing_dip_half_life > 0.0f - ? state.landing_dip * std::exp2(-dt / shape.landing_dip_half_life) + ? state.landing_dip * ::exp2(-dt / shape.landing_dip_half_life) : 0.0f; state.anchor_eye_y = state.eye_base_y - state.landing_dip; state.view_initialized = true; @@ -355,7 +356,7 @@ void AnswerCameraObstructions(ecs::World& world, physics::PhysicsWorld& physics) const Vec3 dir = delta * (1.0f / distance); physics::PhysicsWorld::RayHit hit; if (physics.SphereCast(o.origin, dir, distance, o.radius, &hit)) { - const f32 safe = std::clamp(hit.distance - o.margin, 0.0f, distance); + const f32 safe = rx::Clamp(hit.distance - o.margin, 0.0f, distance); scene::SetCameraObstructionResult(o, o.request_id, o.origin + dir * safe, true); } else { scene::SetCameraObstructionResult(o, o.request_id, o.desired_position, false); @@ -384,7 +385,7 @@ void ApplyCharacterViewMode(ecs::World& world, ecs::Entity entity, if (kind == CharacterViewKind::kFirstPerson) { orbit.min_pitch = -settings.fp_pitch_limit; orbit.max_pitch = settings.fp_pitch_limit; - orbit.pitch = std::clamp(orbit.pitch, orbit.min_pitch, orbit.max_pitch); + orbit.pitch = rx::Clamp(orbit.pitch, orbit.min_pitch, orbit.max_pitch); // Eye-anchored, zero lag, no boom or obstruction. RemoveIfPresent(world, entity); RemoveIfPresent(world, entity); @@ -394,9 +395,9 @@ void ApplyCharacterViewMode(ecs::World& world, ecs::Entity entity, } else { orbit.min_pitch = settings.tp_pitch_min; orbit.max_pitch = settings.tp_pitch_max; - orbit.pitch = std::clamp(orbit.pitch, orbit.min_pitch, orbit.max_pitch); + orbit.pitch = rx::Clamp(orbit.pitch, orbit.min_pitch, orbit.max_pitch); scene::CameraBoom& boom = Ensure(world, entity); - boom.distance = std::clamp(boom.distance == 0 ? settings.tp_distance : boom.distance, + boom.distance = rx::Clamp(boom.distance == 0 ? settings.tp_distance : boom.distance, settings.tp_min_distance, settings.tp_max_distance); boom.shoulder_offset = settings.tp_shoulder_offset; boom.height_offset = settings.tp_height_offset; @@ -462,7 +463,7 @@ bool ApplyCharacterZoom(ecs::World& world, ecs::Entity entity, f32 zoom_delta, } if (boom) { boom->distance = - std::clamp(desired, settings.tp_min_distance, settings.tp_max_distance); + rx::Clamp(desired, settings.tp_min_distance, settings.tp_max_distance); } return false; } diff --git a/engine/character/jetpack.cc b/engine/character/jetpack.cc index 595370b4..378813df 100644 --- a/engine/character/jetpack.cc +++ b/engine/character/jetpack.cc @@ -1,8 +1,8 @@ #include "character/jetpack.h" -#include -#include +#include +#include "core/scalar.h" #include "ecs/world.h" namespace rx::character { @@ -18,7 +18,7 @@ constexpr f32 kLn10 = 2.302585093f; } // namespace void StepJetpacks(ecs::World& world, f32 dt) { - if (!std::isfinite(dt) || dt <= 0.0f) return; + if (!::isfinite(dt) || dt <= 0.0f) return; world.Each( @@ -31,11 +31,11 @@ void StepJetpacks(ecs::World& world, f32 dt) { const bool want = in.enabled && in.thrust && st.fuel > 0.0f; const f32 demand = want ? 1.0f : 0.0f; const f32 tau = d.spool_time > 1e-4f ? d.spool_time / kLn10 : 0.0f; - const f32 a = tau > 0.0f ? 1.0f - std::exp(-dt / tau) : 1.0f; + const f32 a = tau > 0.0f ? 1.0f - ::exp(-dt / tau) : 1.0f; st.thrust = Clamp01(st.thrust + (demand - st.thrust) * a); // fuel: drain by actual thrust; refuel grounded + idle only - const f32 cap = std::max(d.fuel_capacity_s, 1e-3f); + const f32 cap = rx::Max(d.fuel_capacity_s, 1e-3f); if (st.thrust > 1e-3f) { st.fuel -= st.thrust * (dt / cap); // full thrust empties the tank in `cap` s } else if (grounded && d.refuel_rate > 0.0f) { @@ -49,16 +49,16 @@ void StepJetpacks(ecs::World& world, f32 dt) { // Vertical: TWR * gravity, so it competes with weight and only climbs // when thrust_to_weight * thrust > 1. Lateral: along the horizontal move // intent, scaled by thrust: the in-air lean that beats free-fall drift. - const f32 g = std::max(move.gravity, 0.0f); + const f32 g = rx::Max(move.gravity, 0.0f); Vec3 accel{0.0f, st.thrust * d.thrust_to_weight * g, 0.0f}; const Vec3 mh{intent.move.x, 0.0f, intent.move.z}; - const f32 mlen = std::sqrt(mh.x * mh.x + mh.z * mh.z); + const f32 mlen = ::sqrt(mh.x * mh.x + mh.z * mh.z); if (mlen > 1e-4f) { const f32 s = (st.thrust * d.lateral_accel) / mlen; accel.x += mh.x * s; accel.z += mh.z * s; } - if (std::isfinite(accel.x) && std::isfinite(accel.y) && std::isfinite(accel.z)) + if (::isfinite(accel.x) && ::isfinite(accel.y) && ::isfinite(accel.z)) intent.external_acceleration += accel; // compose (StepCharacters clears it) }); } diff --git a/engine/combat/damage.cc b/engine/combat/damage.cc index 4638f666..f8a72fc1 100644 --- a/engine/combat/damage.cc +++ b/engine/combat/damage.cc @@ -1,8 +1,8 @@ #include "combat/damage.h" -#include -#include +#include +#include "core/scalar.h" #include "ecs/world.h" #include "scene/components.h" @@ -76,7 +76,7 @@ bool ApplyDamage(ecs::World& world, const DamageRequest& request, CombatEvents* f32 absorbed = 0; if (health->armor > 0) { - absorbed = std::min(health->armor, amount * std::clamp(health->armor_absorb, 0.0f, 1.0f)); + absorbed = rx::Min(health->armor, amount * rx::Clamp(health->armor_absorb, 0.0f, 1.0f)); health->armor -= absorbed; } const f32 applied = amount - absorbed; @@ -123,20 +123,20 @@ bool ApplyDamage(ecs::World& world, const DamageRequest& request, CombatEvents* f32 Heal(ecs::World& world, ecs::Entity entity, f32 amount) { Health* health = world.Get(entity); if (!health || health->dead || amount <= 0) return 0; - const f32 given = std::min(amount, health->max_hp - health->hp); + const f32 given = rx::Min(amount, health->max_hp - health->hp); if (given <= 0) return 0; health->hp += given; return given; } void StepHealth(ecs::World& world, f32 dt) { - if (!std::isfinite(dt) || dt <= 0) return; + if (!::isfinite(dt) || dt <= 0) return; world.Each([dt](ecs::Entity, Health& health) { if (health.dead) return; health.time_since_damage += dt; if (health.regen_rate <= 0) return; if (health.time_since_damage < health.regen_delay) return; - health.hp = std::min(health.max_hp, health.hp + health.regen_rate * dt); + health.hp = rx::Min(health.max_hp, health.hp + health.regen_rate * dt); }); } @@ -153,7 +153,7 @@ u32 ApplyExplosion(ecs::World& world, physics::PhysicsWorld& physics, const HitR events->explosions.push_back(event); } - const f32 min_scale = std::clamp(params.min_scale, 0.0f, 1.0f); + const f32 min_scale = rx::Clamp(params.min_scale, 0.0f, 1.0f); u32 damaged = 0; world.Each([&](ecs::Entity entity, Health& health, diff --git a/engine/combat/projectile.cc b/engine/combat/projectile.cc index dddff771..de730804 100644 --- a/engine/combat/projectile.cc +++ b/engine/combat/projectile.cc @@ -1,10 +1,10 @@ #include "combat/projectile.h" -#include -#include +#include #include +#include "core/scalar.h" #include "ecs/world.h" #include "scene/components.h" @@ -12,7 +12,7 @@ namespace rx::combat { namespace { bool IsIgnored(const Projectile& round, physics::BodyId body) { - const u8 count = std::min(round.ignore_count, kMaxIgnoredBodies); + const u8 count = rx::Min(round.ignore_count, kMaxIgnoredBodies); for (u8 i = 0; i < count; ++i) { if (round.ignore[i] == body) return true; } @@ -48,7 +48,7 @@ ExplosionParams BlastFrom(const Projectile& round, const Vec3& position) { ecs::Entity SpawnProjectile(ecs::World& world, const Projectile& desc) { const ecs::Entity entity = world.Create(); Projectile round = desc; - round.ignore_count = std::min(round.ignore_count, kMaxIgnoredBodies); + round.ignore_count = rx::Min(round.ignore_count, kMaxIgnoredBodies); world.Add(entity, round); scene::Transform transform; transform.position[0] = desc.position.x; @@ -60,7 +60,7 @@ ecs::Entity SpawnProjectile(ecs::World& world, const Projectile& desc) { void StepProjectiles(ecs::World& world, physics::PhysicsWorld& physics, const HitRegistry& registry, CombatEvents& events, f32 dt) { - if (!std::isfinite(dt) || dt <= 0) return; + if (!::isfinite(dt) || dt <= 0) return; base::Vector spent; base::Vector blasts; @@ -73,7 +73,7 @@ void StepProjectiles(ecs::World& world, physics::PhysicsWorld& physics, if (round.drag > 0) { // drag is per metre travelled, so the decay over a step depends on how // far the round actually gets: fast rounds bleed speed faster. - velocity = velocity * std::exp(-round.drag * Length(velocity) * dt); + velocity = velocity * ::exp(-round.drag * Length(velocity) * dt); } const Vec3 from = round.position; diff --git a/engine/combat/weapon.cc b/engine/combat/weapon.cc index 498bfa87..05900326 100644 --- a/engine/combat/weapon.cc +++ b/engine/combat/weapon.cc @@ -1,11 +1,12 @@ #include "combat/weapon.h" -#include -#include +#include +#include #include #include "combat/projectile.h" +#include "core/scalar.h" #include "ecs/world.h" namespace rx::combat { @@ -36,7 +37,7 @@ f32 RandomSigned(u32& state) { return RandomUnit(state) * 2.0f - 1.0f; } f32 MoveTowardScalar(f32 current, f32 target, f32 max_delta) { const f32 d = target - current; - if (std::abs(d) <= max_delta) return target; + if (::abs(d) <= max_delta) return target; return current + (d > 0 ? max_delta : -max_delta); } @@ -44,11 +45,11 @@ f32 MoveTowardScalar(f32 current, f32 target, f32 max_delta) { // of the remaining gap closed over `dt` for a given half-life. f32 ApproachFraction(f32 half_life, f32 dt) { if (half_life <= 0) return 1.0f; - return 1.0f - std::exp2(-dt / half_life); + return 1.0f - ::exp2(-dt / half_life); } void Basis(const Vec3& forward, Vec3* right, Vec3* up) { - const Vec3 reference = std::abs(forward.y) > 0.99f ? Vec3{1, 0, 0} : Vec3{0, 1, 0}; + const Vec3 reference = ::abs(forward.y) > 0.99f ? Vec3{1, 0, 0} : Vec3{0, 1, 0}; *right = Normalize(Cross(forward, reference)); *up = Cross(*right, forward); } @@ -59,23 +60,23 @@ void Basis(const Vec3& forward, Vec3* right, Vec3* up) { Vec3 ConeDirection(const Vec3& forward, f32 spread, u32& rng) { if (spread <= 0) return forward; const f32 angle = RandomUnit(rng) * kTwoPi; - const f32 radius = std::sqrt(RandomUnit(rng)) * std::tan(spread); + const f32 radius = ::sqrt(RandomUnit(rng)) * ::tan(spread); Vec3 right; Vec3 up; Basis(forward, &right, &up); - return Normalize(forward + right * (std::cos(angle) * radius) + - up * (std::sin(angle) * radius)); + return Normalize(forward + right * (::cos(angle) * radius) + + up * (::sin(angle) * radius)); } Vec3 SafeDirection(const Vec3& v) { const f32 length = Length(v); - if (!std::isfinite(length) || length < 1e-6f) return {0, 0, -1}; + if (!::isfinite(length) || length < 1e-6f) return {0, 0, -1}; return v * (1.0f / length); } void CompleteMagazineReload(WeaponState& weapon, const WeaponDef& def) { const u32 needed = def.magazine > weapon.ammo ? def.magazine - weapon.ammo : 0; - const u32 taken = std::min(needed, weapon.reserve); + const u32 taken = rx::Min(needed, weapon.reserve); weapon.ammo += taken; weapon.reserve -= taken; weapon.reload_timer = 0; @@ -192,7 +193,7 @@ i8 GiveWeapon(Loadout& loadout, const WeaponCatalog& catalog, WeaponDefId def_id weapon = WeaponState{}; weapon.def = def_id; weapon.ammo = def->magazine; - weapon.reserve = def->reserve_max > 0 ? std::min(reserve, def->reserve_max) : reserve; + weapon.reserve = def->reserve_max > 0 ? rx::Min(reserve, def->reserve_max) : reserve; // A distinct jitter stream per slot, so two identical rifles do not fire the // same pattern in lockstep. weapon.rng = 0x9E3779B9u ^ (def_id * 0x85EBCA6Bu) ^ ((slot + 1u) * 0xC2B2AE35u); @@ -210,7 +211,7 @@ u32 AddAmmo(Loadout& loadout, const WeaponCatalog& catalog, WeaponDefId def_id, ? (def->reserve_max > weapon.reserve ? def->reserve_max - weapon.reserve : 0) : rounds - taken; - const u32 give = std::min(room, rounds - taken); + const u32 give = rx::Min(room, rounds - taken); weapon.reserve += give; taken += give; } @@ -244,19 +245,19 @@ bool StartReload(WeaponState& weapon, const WeaponDef& def) { } f32 EffectiveSpread(const WeaponDef& def, const WeaponState& weapon, const WeaponIntent& intent) { - const f32 bloom = std::clamp(weapon.bloom, 0.0f, 1.0f); + const f32 bloom = rx::Clamp(weapon.bloom, 0.0f, 1.0f); f32 spread = def.spread_min + (def.spread_max - def.spread_min) * bloom; if (intent.airborne) { spread *= def.spread_air_scale; } else { const f32 move = def.spread_move_speed > 0 - ? std::clamp(intent.speed / def.spread_move_speed, 0.0f, 1.0f) + ? rx::Clamp(intent.speed / def.spread_move_speed, 0.0f, 1.0f) : 0.0f; spread *= 1.0f + (def.spread_move_scale - 1.0f) * move; if (intent.crouched) spread *= def.spread_crouch_scale; } - spread *= 1.0f + (def.spread_ads_scale - 1.0f) * std::clamp(weapon.ads, 0.0f, 1.0f); - return std::max(spread, 0.0f); + spread *= 1.0f + (def.spread_ads_scale - 1.0f) * rx::Clamp(weapon.ads, 0.0f, 1.0f); + return rx::Max(spread, 0.0f); } f32 AimFovScale(const Loadout& loadout, const WeaponCatalog& catalog) { @@ -264,12 +265,12 @@ f32 AimFovScale(const Loadout& loadout, const WeaponCatalog& catalog) { if (!weapon) return 1.0f; const WeaponDef* def = catalog.Find(weapon->def); if (!def) return 1.0f; - return 1.0f + (def->ads_fov_scale - 1.0f) * std::clamp(weapon->ads, 0.0f, 1.0f); + return 1.0f + (def->ads_fov_scale - 1.0f) * rx::Clamp(weapon->ads, 0.0f, 1.0f); } void StepWeapons(ecs::World& world, physics::PhysicsWorld& physics, const WeaponCatalog& catalog, const HitRegistry& registry, CombatEvents& events, f32 dt) { - if (!std::isfinite(dt) || dt <= 0) return; + if (!::isfinite(dt) || dt <= 0) return; // Projectile entities are created after the walk: spawning inside World::Each // is a structural change that can skip or revisit rows. @@ -289,7 +290,7 @@ void StepWeapons(ecs::World& world, physics::PhysicsWorld& physics, const Weapon if (loadout.active >= loadout.count) loadout.active = 0; if (loadout.swap_timer > 0) { - loadout.swap_timer = std::max(0.0f, loadout.swap_timer - dt); + loadout.swap_timer = rx::Max(0.0f, loadout.swap_timer - dt); if (loadout.swap_timer == 0 && loadout.pending < loadout.count) { loadout.active = loadout.pending; } @@ -304,7 +305,7 @@ void StepWeapons(ecs::World& world, physics::PhysicsWorld& physics, const Weapon lowered.burst_remaining = 0; const WeaponDef* raised = catalog.Find(loadout.slots[slot].def); loadout.pending = slot; - loadout.swap_timer = raised ? std::max(raised->swap_time, 0.0f) : 0.0f; + loadout.swap_timer = raised ? rx::Max(raised->swap_time, 0.0f) : 0.0f; if (loadout.swap_timer <= 0) loadout.active = slot; } } @@ -321,8 +322,8 @@ void StepWeapons(ecs::World& world, physics::PhysicsWorld& physics, const Weapon const f32 ads_target = intent.aim && !swapping ? 1.0f : 0.0f; weapon.ads = MoveTowardScalar(weapon.ads, ads_target, def.ads_time > 0 ? dt / def.ads_time : 1.0f); - weapon.cooldown = std::max(0.0f, weapon.cooldown - dt); - weapon.bloom = std::max(0.0f, weapon.bloom - def.spread_decay * dt); + weapon.cooldown = rx::Max(0.0f, weapon.cooldown - dt); + weapon.bloom = rx::Max(0.0f, weapon.bloom - def.spread_decay * dt); if (swapping) { weapon.burst_remaining = 0; @@ -381,7 +382,7 @@ void StepWeapons(ecs::World& world, physics::PhysicsWorld& physics, const Weapon ++weapon.shots_fired; if (def.mode == FireMode::kBurst) { - if (weapon.burst_remaining == 0) weapon.burst_remaining = std::max(def.burst_count, 1u); + if (weapon.burst_remaining == 0) weapon.burst_remaining = rx::Max(def.burst_count, 1u); --weapon.burst_remaining; } const f32 interval = ShotInterval(def); @@ -391,17 +392,17 @@ void StepWeapons(ecs::World& world, physics::PhysicsWorld& physics, const Weapon const Vec3 aim = SafeDirection(intent.direction); const f32 spread = EffectiveSpread(def, weapon, intent); - weapon.bloom = std::min(1.0f, weapon.bloom + def.spread_per_shot); + weapon.bloom = rx::Min(1.0f, weapon.bloom + def.spread_per_shot); if (ViewRecoil* recoil = world.Get(entity)) { - const f32 scale = 1.0f + (def.recoil_ads_scale - 1.0f) * std::clamp(weapon.ads, 0.0f, 1.0f); + const f32 scale = 1.0f + (def.recoil_ads_scale - 1.0f) * rx::Clamp(weapon.ads, 0.0f, 1.0f); recoil->pending_pitch += def.recoil_pitch * scale; recoil->pending_yaw += (def.recoil_yaw + RandomSigned(weapon.rng) * def.recoil_yaw_variance) * scale; recoil->time_since_shot = 0; } if (Viewmodel* viewmodel = world.Get(entity)) { - viewmodel->punch = std::min(viewmodel->punch + viewmodel->punch_scale, + viewmodel->punch = rx::Min(viewmodel->punch + viewmodel->punch_scale, viewmodel->punch_scale * 3.0f); } @@ -410,14 +411,14 @@ void StepWeapons(ecs::World& world, physics::PhysicsWorld& physics, const Weapon shot.weapon = weapon.def; shot.origin = intent.origin; shot.direction = aim; - shot.pellets = std::max(def.pellets, 1u); + shot.pellets = rx::Max(def.pellets, 1u); shot.ammo_left = weapon.ammo; events.shots.push_back(shot); physics::BodyId ignore[kMaxIgnoredBodies]; u32 ignore_count = 0; if (const HitIgnoreList* list = world.Get(entity)) { - const u8 count = std::min(list->count, kMaxIgnoredBodies); + const u8 count = rx::Min(list->count, kMaxIgnoredBodies); for (u8 i = 0; i < count; ++i) { if (list->bodies[i] != 0) ignore[ignore_count++] = list->bodies[i]; } @@ -456,7 +457,7 @@ void StepWeapons(ecs::World& world, physics::PhysicsWorld& physics, const Weapon round.blast_min_scale = def.blast_min_scale; round.blast_impulse = def.blast_impulse; round.explode_on_expire = def.explode_on_expire; - round.ignore_count = static_cast(std::min(ignore_count, kMaxIgnoredBodies)); + round.ignore_count = static_cast(rx::Min(ignore_count, kMaxIgnoredBodies)); for (u8 i = 0; i < round.ignore_count; ++i) round.ignore[i] = ignore[i]; spawned.push_back(round); } @@ -471,7 +472,7 @@ void StepWeapons(ecs::World& world, physics::PhysicsWorld& physics, const Weapon } void StepViewRecoil(ecs::World& world, f32 dt) { - if (!std::isfinite(dt) || dt <= 0) return; + if (!::isfinite(dt) || dt <= 0) return; world.Each([dt](ecs::Entity, ViewRecoil& recoil) { recoil.time_since_shot += dt; f32 pitch = 0; @@ -485,7 +486,7 @@ void StepViewRecoil(ecs::World& world, f32 dt) { pitch += kick_pitch; yaw += kick_yaw; - const f32 keep = std::clamp(recoil.recovery_fraction, 0.0f, 1.0f); + const f32 keep = rx::Clamp(recoil.recovery_fraction, 0.0f, 1.0f); recoil.recoverable_pitch += kick_pitch * keep; recoil.recoverable_yaw += kick_yaw * keep; @@ -505,7 +506,7 @@ void StepViewRecoil(ecs::World& world, f32 dt) { } void StepViewmodels(ecs::World& world, f32 dt) { - if (!std::isfinite(dt) || dt <= 0) return; + if (!::isfinite(dt) || dt <= 0) return; world.Each([&](ecs::Entity entity, Viewmodel& viewmodel, WeaponIntent& intent) { // Sway trails the look RATE, so a flick throws the weapon and holding still @@ -513,27 +514,27 @@ void StepViewmodels(ecs::World& world, f32 dt) { const f32 yaw_rate = intent.look_yaw_delta / dt; const f32 pitch_rate = intent.look_pitch_delta / dt; const f32 sway_x = - std::clamp(-yaw_rate * viewmodel.sway_scale, -viewmodel.sway_max, viewmodel.sway_max); + rx::Clamp(-yaw_rate * viewmodel.sway_scale, -viewmodel.sway_max, viewmodel.sway_max); const f32 sway_y = - std::clamp(-pitch_rate * viewmodel.sway_scale, -viewmodel.sway_max, viewmodel.sway_max); + rx::Clamp(-pitch_rate * viewmodel.sway_scale, -viewmodel.sway_max, viewmodel.sway_max); const f32 sway_blend = ApproachFraction(viewmodel.sway_half_life, dt); const f32 speed_ratio = viewmodel.bob_reference_speed > 0 - ? std::clamp(intent.speed / viewmodel.bob_reference_speed, 0.0f, 1.0f) + ? rx::Clamp(intent.speed / viewmodel.bob_reference_speed, 0.0f, 1.0f) : 0.0f; const f32 bob_target = intent.airborne ? 0.0f : speed_ratio; viewmodel.bob_weight += (bob_target - viewmodel.bob_weight) * ApproachFraction(viewmodel.bob_half_life, dt); viewmodel.bob_phase = - std::fmod(viewmodel.bob_phase + viewmodel.bob_rate * speed_ratio * dt, kTwoPi); + ::fmod(viewmodel.bob_phase + viewmodel.bob_rate * speed_ratio * dt, kTwoPi); viewmodel.punch -= viewmodel.punch * ApproachFraction(viewmodel.punch_half_life, dt); f32 ads = 0; if (const Loadout* loadout = world.Get(entity)) { if (const WeaponState* weapon = ActiveWeapon(*loadout)) { - ads = std::clamp(weapon->ads, 0.0f, 1.0f); + ads = rx::Clamp(weapon->ads, 0.0f, 1.0f); } } // Aiming pulls the weapon to the eye line: sights are steady, so bob and @@ -542,8 +543,8 @@ void StepViewmodels(ecs::World& world, f32 dt) { const f32 bob = viewmodel.bob_amplitude * viewmodel.bob_weight * hip; Vec3 target; - target.x = sway_x * hip + std::sin(viewmodel.bob_phase) * bob; - target.y = sway_y * hip + std::sin(viewmodel.bob_phase * 2.0f) * bob * 0.5f; + target.x = sway_x * hip + ::sin(viewmodel.bob_phase) * bob; + target.y = sway_y * hip + ::sin(viewmodel.bob_phase * 2.0f) * bob * 0.5f; target.z = -viewmodel.punch; target += viewmodel.ads_offset * ads; diff --git a/engine/combat/weapon_def.cc b/engine/combat/weapon_def.cc index a34ab12c..0f6fef75 100644 --- a/engine/combat/weapon_def.cc +++ b/engine/combat/weapon_def.cc @@ -1,6 +1,5 @@ #include "combat/weapon_def.h" - -#include +#include "core/scalar.h" namespace rx::combat { @@ -30,7 +29,7 @@ void WeaponCatalog::Clear() { f32 FalloffScale(const WeaponDef& def, f32 distance) { if (def.falloff_end <= def.falloff_start) return 1.0f; if (distance <= def.falloff_start) return 1.0f; - const f32 min_scale = std::clamp(def.falloff_min_scale, 0.0f, 1.0f); + const f32 min_scale = rx::Clamp(def.falloff_min_scale, 0.0f, 1.0f); if (distance >= def.falloff_end) return min_scale; const f32 t = (distance - def.falloff_start) / (def.falloff_end - def.falloff_start); return 1.0f + (min_scale - 1.0f) * t; diff --git a/engine/core/CMakeLists.txt b/engine/core/CMakeLists.txt index 41c5b69e..5b907d49 100644 --- a/engine/core/CMakeLists.txt +++ b/engine/core/CMakeLists.txt @@ -1,6 +1,9 @@ rx_add_module(core log.cc paths.cc + file_system.cc + format.cc + text_reader.cc job_system.cc frame_timer.cc world_clock.cc diff --git a/engine/core/feature_registry.cc b/engine/core/feature_registry.cc index c98f9c85..4d20a524 100644 --- a/engine/core/feature_registry.cc +++ b/engine/core/feature_registry.cc @@ -1,7 +1,6 @@ #include "core/feature_registry.h" -#include -#include +#include #include "core/log.h" @@ -14,6 +13,7 @@ namespace { // runs from engine startup, so the linker keeps it: no static-init stripping. base::Feature g_features[] = { #define RX_FEATURE(id, name, enabled) {name, enabled}, +#include "base/strings/string_ref.h" #include "core/features.def" #undef RX_FEATURE }; @@ -24,7 +24,7 @@ static_assert(sizeof(g_features) / sizeof(g_features[0]) == kCount, // Flip the flag whose name matches `name`. Returns false if there is no such // flag. -bool ApplyOne(std::string_view name, bool enabled) { +bool ApplyOne(base::StringRef name, bool enabled) { for (auto& f : g_features) { if (name == f.name) { f.enabled = enabled; @@ -45,14 +45,14 @@ base::Span Features() { } void InitFeatures() { - const char* spec = std::getenv("RX_FEATURES"); + const char* spec = ::getenv("RX_FEATURES"); if (!spec || !*spec) return; - std::string_view rest(spec); + base::StringRef rest(spec); while (!rest.empty()) { const auto end = rest.find_first_of(", \t"); - std::string_view tok = rest.substr(0, end); - rest = end == std::string_view::npos ? std::string_view{} : rest.substr(end + 1); + base::StringRef tok = rest.substr(0, end); + rest = end == base::StringRef::npos ? base::StringRef{} : rest.substr(end + 1); if (tok.empty()) continue; bool enabled = true; @@ -61,8 +61,8 @@ void InitFeatures() { } else if (tok.front() == '-') { enabled = false; tok.remove_prefix(1); - } else if (const auto eq = tok.find('='); eq != std::string_view::npos) { - std::string_view val = tok.substr(eq + 1); + } else if (const auto eq = tok.find('='); eq != base::StringRef::npos) { + base::StringRef val = tok.substr(eq + 1); enabled = !(val == "0" || val == "false" || val == "off"); tok = tok.substr(0, eq); } diff --git a/engine/core/file_system.cc b/engine/core/file_system.cc new file mode 100644 index 00000000..5e02cbc7 --- /dev/null +++ b/engine/core/file_system.cc @@ -0,0 +1,590 @@ +#include "core/file_system.h" + +#include +#include +#include + +#include "base/filesystem/file.h" +#include "base/filesystem/path.h" + +#if defined(_WIN32) +#include + +#include "base/text/code_convert.h" +#else +#include +#include +#include +#include +#endif + +namespace rx::fs { +namespace { + +bool IsSeparator(char c) { +#if defined(_WIN32) + return c == '/' || c == '\\'; +#else + return c == '/'; +#endif +} + +// Length of the root name ("C:" on Windows, nothing elsewhere). +mem_size RootNameLength(base::StringRef path) { +#if defined(_WIN32) + if (path.size() >= 2 && path[1] == ':') return 2; +#endif + (void)path; + return 0; +} + +bool HasRootDirectory(base::StringRef path) { + const mem_size root = RootNameLength(path); + return path.size() > root && IsSeparator(path[root]); +} + +// The generic-format element sequence std::filesystem::path iterates: the +// root name, the root directory as "/", each filename, and "" for a trailing +// separator. +struct Elements { + base::StringRef root_name; + bool root_directory = false; + base::Vector names; +}; + +Elements Split(base::StringRef path) { + Elements e; + const mem_size root = RootNameLength(path); + e.root_name = path.substr(0, root); + mem_size i = root; + if (i < path.size() && IsSeparator(path[i])) { + e.root_directory = true; + while (i < path.size() && IsSeparator(path[i])) ++i; + } + while (i < path.size()) { + mem_size end = i; + while (end < path.size() && !IsSeparator(path[end])) ++end; + e.names.push_back(path.substr(i, end - i)); + if (end == path.size()) break; + while (end < path.size() && IsSeparator(path[end])) ++end; + if (end == path.size()) e.names.push_back(base::StringRef()); + i = end; + } + return e; +} + +base::StringRef View(const base::String& s) { return base::StringRef(s.data(), s.size()); } + +#if defined(_WIN32) +base::StringW Wide(base::StringRef path) { + return base::UTF8ToWide(base::StringRefU8(reinterpret_cast(path.data()), + path.size())); +} + +base::String Narrow(const wchar_t* text, mem_size length) { + base::StringU8 utf8 = base::WideToUTF8(base::StringRefW(text, length)); + return base::String(reinterpret_cast(utf8.data()), utf8.size()); +} +#else +// NUL-terminated copy for the libc calls; views are not. +base::String Z(base::StringRef path) { return base::String(path.data(), path.size()); } +#endif + +base::Path ToBasePath(base::StringRef path) { + return base::Path( + base::StringRefU8(reinterpret_cast(path.data()), path.size())); +} + +} // namespace + +bool IsAbsolute(base::StringRef path) { +#if defined(_WIN32) + return RootNameLength(path) > 0 && HasRootDirectory(path); +#else + return HasRootDirectory(path); +#endif +} + +base::StringRef Filename(base::StringRef path) { + const mem_size root = RootNameLength(path); + if (path.size() == root) return base::StringRef(); + if (IsSeparator(path[path.size() - 1])) return base::StringRef(); + mem_size start = path.size(); + while (start > root && !IsSeparator(path[start - 1])) --start; + return path.substr(start); +} + +base::StringRef ParentPath(base::StringRef path) { + const mem_size root = RootNameLength(path); + mem_size rel = root; + while (rel < path.size() && IsSeparator(path[rel])) ++rel; + // Nothing after the root: the path is its own parent ("/" or "C:/"), and + // an empty or root-name-only path has none. + if (rel == path.size()) return path; + // Drop the last element (a trailing separator counts as an empty one), + // then the separators in front of it, but never the root directory. + mem_size end = path.size(); + if (IsSeparator(path[end - 1])) { + while (end > rel && IsSeparator(path[end - 1])) --end; + return path.substr(0, end); + } + while (end > rel && !IsSeparator(path[end - 1])) --end; + while (end > rel && IsSeparator(path[end - 1])) --end; + if (end == rel) return path.substr(0, rel); + return path.substr(0, end); +} + +base::StringRef Extension(base::StringRef path) { + const base::StringRef name = Filename(path); + if (name == "." || name == "..") return base::StringRef(); + const mem_size dot = name.rfind('.'); + if (dot == base::StringRef::npos || dot == 0) return base::StringRef(); + return name.substr(dot); +} + +base::StringRef Stem(base::StringRef path) { + const base::StringRef name = Filename(path); + const base::StringRef ext = Extension(path); + return name.substr(0, name.size() - ext.size()); +} + +base::String ReplaceExtension(base::StringRef path, base::StringRef extension) { + const base::StringRef old = Extension(path); + base::String out(path.data(), path.size() - old.size()); + if (!extension.empty()) { + if (extension[0] != '.') out.push_back('.'); + out.append(extension.data(), extension.size()); + } + return out; +} + +base::String Join(base::StringRef lhs, base::StringRef rhs) { + if (IsAbsolute(rhs) || lhs.empty()) return base::String(rhs.data(), rhs.size()); + base::String out(lhs.data(), lhs.size()); + if (HasRootDirectory(rhs)) { + // Rooted but not absolute (Windows "\a"): keep only lhs's root name. + out = base::String(lhs.data(), RootNameLength(lhs)); + } else if (!IsSeparator(out[out.size() - 1]) && out.size() != RootNameLength(lhs)) { + out.push_back('/'); + } + out.append(rhs.data(), rhs.size()); + return out; +} + +base::String LexicallyNormal(base::StringRef path) { + if (path.empty()) return base::String(); + const Elements e = Split(path); + base::Vector kept; + bool trailing = false; + for (const base::StringRef& name : e.names) { + trailing = false; + if (name.empty()) { + trailing = true; + } else if (name == ".") { + trailing = true; + } else if (name == "..") { + if (!kept.empty() && kept.back() != "..") { + kept.pop_back(); + trailing = true; + } else if (!e.root_directory) { + kept.push_back(name); + } + } else { + kept.push_back(name); + } + } + base::String out(e.root_name.data(), e.root_name.size()); + if (e.root_directory) out.push_back('/'); + for (mem_size i = 0; i < kept.size(); ++i) { + if (i > 0) out.push_back('/'); + out.append(kept[i].data(), kept[i].size()); + } + // A trailing separator survives, except after "..". + if (trailing && !kept.empty() && kept.back() != "..") out.push_back('/'); + if (out.empty()) out = "."; + return out; +} + +base::String LexicallyRelative(base::StringRef path, base::StringRef base_dir) { + const Elements p = Split(path); + const Elements b = Split(base_dir); + if (p.root_name != b.root_name || IsAbsolute(path) != IsAbsolute(base_dir) || + (!p.root_directory && b.root_directory)) { + return base::String(); + } + mem_size i = 0; + while (i < p.names.size() && i < b.names.size() && p.names[i] == b.names[i]) ++i; + if (i == p.names.size() && i == b.names.size()) return base::String("."); + i64 n = 0; + for (mem_size j = i; j < b.names.size(); ++j) { + const base::StringRef& name = b.names[j]; + if (name == "..") { + --n; + } else if (!name.empty() && name != ".") { + ++n; + } + } + if (n < 0) return base::String(); + if (n == 0 && (i == p.names.size() || p.names[i].empty())) return base::String("."); + base::String out; + for (i64 k = 0; k < n; ++k) { + if (!out.empty()) out.push_back('/'); + out.append(".."); + } + for (mem_size j = i; j < p.names.size(); ++j) { + out = Join(View(out), p.names[j]); + // Join of "" appends a separator only when there is a filename to follow. + if (p.names[j].empty() && !out.empty() && !IsSeparator(out[out.size() - 1])) { + out.push_back('/'); + } + } + return out; +} + +base::String GenericString(base::StringRef path) { + base::String out(path.data(), path.size()); +#if defined(_WIN32) + for (char& c : out) { + if (c == '\\') c = '/'; + } +#endif + return out; +} + +#if defined(_WIN32) + +namespace { +bool Attributes(base::StringRef path, WIN32_FILE_ATTRIBUTE_DATA* data) { + return GetFileAttributesExW(Wide(path).c_str(), GetFileExInfoStandard, data) != 0; +} +} // namespace + +bool Exists(base::StringRef path) { + WIN32_FILE_ATTRIBUTE_DATA data; + return Attributes(path, &data); +} + +bool IsRegularFile(base::StringRef path) { + WIN32_FILE_ATTRIBUTE_DATA data; + return Attributes(path, &data) && !(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); +} + +bool IsDirectory(base::StringRef path) { + WIN32_FILE_ATTRIBUTE_DATA data; + return Attributes(path, &data) && (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); +} + +base::Optional FileSize(base::StringRef path) { + WIN32_FILE_ATTRIBUTE_DATA data; + if (!Attributes(path, &data) || (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + return base::nullopt; + } + return (static_cast(data.nFileSizeHigh) << 32) | data.nFileSizeLow; +} + +base::Optional LastWriteTime(base::StringRef path) { + WIN32_FILE_ATTRIBUTE_DATA data; + if (!Attributes(path, &data)) return base::nullopt; + const u64 ticks = (static_cast(data.ftLastWriteTime.dwHighDateTime) << 32) | + data.ftLastWriteTime.dwLowDateTime; + // 100 ns ticks since 1601 to ns since 1970. + constexpr u64 kEpochDelta = 116444736000000000ull; + return static_cast(ticks - kEpochDelta) * 100; +} + +base::String CurrentPath() { + const DWORD size = GetCurrentDirectoryW(0, nullptr); + if (size == 0) return base::String("."); + base::Vector buffer; + buffer.resize(size); + const DWORD written = GetCurrentDirectoryW(size, buffer.data()); + return Narrow(buffer.data(), written); +} + +base::String TempDirectory() { + wchar_t buffer[MAX_PATH + 1]; + DWORD size = GetTempPathW(MAX_PATH + 1, buffer); + if (size == 0) return base::String("."); + if (size > 1 && (buffer[size - 1] == L'\\' || buffer[size - 1] == L'/')) --size; + return Narrow(buffer, size); +} + +base::String WeaklyCanonical(base::StringRef path) { + const base::StringW wide = Wide(View(Absolute(path))); + const DWORD size = GetFullPathNameW(wide.c_str(), 0, nullptr, nullptr); + if (size == 0) return LexicallyNormal(path); + base::Vector buffer; + buffer.resize(size); + const DWORD written = GetFullPathNameW(wide.c_str(), size, buffer.data(), nullptr); + return LexicallyNormal(View(Narrow(buffer.data(), written))); +} + +bool CreateDirectories(base::StringRef path) { + if (path.empty()) return false; + if (IsDirectory(path)) return false; + const base::StringRef parent = ParentPath(path); + if (!parent.empty() && parent.size() < path.size() && !IsDirectory(parent)) { + CreateDirectories(parent); + } + if (Filename(path).empty()) return IsDirectory(path); + return CreateDirectoryW(Wide(path).c_str(), nullptr) != 0; +} + +bool Remove(base::StringRef path) { + const base::StringW wide = Wide(path); + if (IsDirectory(path)) return RemoveDirectoryW(wide.c_str()) != 0; + return DeleteFileW(wide.c_str()) != 0; +} + +bool Rename(base::StringRef from, base::StringRef to) { + // Write-through so a rename that replaces a saved file is on disk when it + // returns, which the terrain and texture caches' write-then-rename relies on. + return MoveFileExW(Wide(from).c_str(), Wide(to).c_str(), + MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != 0; +} + +namespace { +bool ListInto(base::StringRef dir, base::Vector* out, bool recursive) { + base::String pattern = Join(dir, "*"); + WIN32_FIND_DATAW data; + HANDLE find = FindFirstFileW(Wide(View(pattern)).c_str(), &data); + if (find == INVALID_HANDLE_VALUE) return false; + do { + const wchar_t* name = data.cFileName; + if (name[0] == L'.' && (name[1] == 0 || (name[1] == L'.' && name[2] == 0))) continue; + mem_size length = 0; + while (name[length]) ++length; + DirEntry entry; + entry.path = Join(dir, View(Narrow(name, length))); + entry.is_directory = (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + entry.is_regular = !entry.is_directory; + const bool descend = recursive && entry.is_directory && + !(data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT); + base::String child = entry.path; + out->push_back(base::move(entry)); + if (descend) ListInto(View(child), out, true); + } while (FindNextFileW(find, &data)); + FindClose(find); + return true; +} +} // namespace + +bool ListDirectory(base::StringRef dir, base::Vector* out, bool recursive) { + return ListInto(dir, out, recursive); +} + +#else // POSIX + +bool Exists(base::StringRef path) { + struct stat st; + return ::stat(Z(path).c_str(), &st) == 0; +} + +bool IsRegularFile(base::StringRef path) { + struct stat st; + return ::stat(Z(path).c_str(), &st) == 0 && S_ISREG(st.st_mode); +} + +bool IsDirectory(base::StringRef path) { + struct stat st; + return ::stat(Z(path).c_str(), &st) == 0 && S_ISDIR(st.st_mode); +} + +base::Optional FileSize(base::StringRef path) { + struct stat st; + if (::stat(Z(path).c_str(), &st) != 0 || !S_ISREG(st.st_mode)) return base::nullopt; + return static_cast(st.st_size); +} + +base::Optional LastWriteTime(base::StringRef path) { + struct stat st; + if (::stat(Z(path).c_str(), &st) != 0) return base::nullopt; +#if defined(__APPLE__) + return static_cast(st.st_mtimespec.tv_sec) * 1000000000 + st.st_mtimespec.tv_nsec; +#else + return static_cast(st.st_mtim.tv_sec) * 1000000000 + st.st_mtim.tv_nsec; +#endif +} + +base::String CurrentPath() { + char buffer[PATH_MAX]; + if (!::getcwd(buffer, sizeof(buffer))) return base::String("."); + return base::String(buffer); +} + +base::String TempDirectory() { + // std::filesystem::temp_directory_path's POSIX order. + static const char* const kVars[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR"}; + for (const char* var : kVars) { + if (const char* dir = ::getenv(var); dir && *dir) return base::String(dir); + } + return base::String("/tmp"); +} + +base::String WeaklyCanonical(base::StringRef path) { + // An existing path is simply canonical; otherwise resolve the longest + // existing prefix and append the rest lexically. + { + char buffer[PATH_MAX]; + if (!path.empty() && ::realpath(Z(path).c_str(), buffer)) return base::String(buffer); + } + const Elements e = Split(path); + base::String prefix(e.root_name.data(), e.root_name.size()); + if (e.root_directory) prefix.push_back('/'); + mem_size used = 0; + base::String resolved; + for (mem_size i = 0; i <= e.names.size(); ++i) { + base::String candidate = prefix; + for (mem_size j = 0; j < i; ++j) candidate = Join(View(candidate), e.names[j]); + if (candidate.empty()) continue; + char buffer[PATH_MAX]; + if (!::realpath(candidate.c_str(), buffer)) break; + resolved = base::String(buffer); + used = i; + } + if (resolved.empty()) return LexicallyNormal(path); + for (mem_size j = used; j < e.names.size(); ++j) resolved = Join(View(resolved), e.names[j]); + return used < e.names.size() ? LexicallyNormal(View(resolved)) : resolved; +} + +bool CreateDirectories(base::StringRef path) { + if (path.empty() || IsDirectory(path)) return false; + const base::StringRef parent = ParentPath(path); + if (!parent.empty() && parent.size() < path.size() && !IsDirectory(parent)) { + CreateDirectories(parent); + } + if (Filename(path).empty()) return IsDirectory(path); + return ::mkdir(Z(path).c_str(), 0777) == 0; +} + +bool Remove(base::StringRef path) { + const base::String z = Z(path); + if (::unlink(z.c_str()) == 0) return true; + return (errno == EISDIR || errno == EPERM) && ::rmdir(z.c_str()) == 0; +} + +bool Rename(base::StringRef from, base::StringRef to) { + return ::rename(Z(from).c_str(), Z(to).c_str()) == 0; +} + +namespace { +bool ListInto(base::StringRef dir, base::Vector* out, bool recursive) { + DIR* handle = ::opendir(Z(dir).c_str()); + if (!handle) return false; + while (const dirent* ent = ::readdir(handle)) { + const char* name = ent->d_name; + if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) continue; + DirEntry entry; + entry.path = Join(dir, name); + struct stat st; + // lstat: recursion does not follow directory symlinks, like + // recursive_directory_iterator without follow_directory_symlink. + bool is_link = false; + if (::lstat(entry.path.c_str(), &st) == 0) { + is_link = S_ISLNK(st.st_mode); + if (is_link && ::stat(entry.path.c_str(), &st) != 0) st.st_mode = 0; + entry.is_directory = S_ISDIR(st.st_mode); + entry.is_regular = S_ISREG(st.st_mode); + } + const bool descend = recursive && entry.is_directory && !is_link; + base::String child = entry.path; + out->push_back(base::move(entry)); + if (descend) ListInto(View(child), out, true); + } + ::closedir(handle); + return true; +} +} // namespace + +bool ListDirectory(base::StringRef dir, base::Vector* out, bool recursive) { + return ListInto(dir, out, recursive); +} + +#endif + +base::String Absolute(base::StringRef path) { + if (IsAbsolute(path)) return base::String(path.data(), path.size()); + return Join(View(CurrentPath()), path); +} + +base::String Relative(base::StringRef path, base::StringRef base_dir) { + return LexicallyRelative(View(WeaklyCanonical(path)), View(WeaklyCanonical(base_dir))); +} + +u64 RemoveAll(base::StringRef path) { + if (!IsDirectory(path)) return Remove(path) ? 1 : 0; + base::Vector entries; + ListDirectory(path, &entries, /*recursive=*/true); + u64 removed = 0; + // Children come after their directory in a pre-order listing. + for (mem_size i = entries.size(); i > 0; --i) { + if (Remove(View(entries[i - 1].path))) ++removed; + } + if (Remove(path)) ++removed; + return removed; +} + +base::File OpenFile(base::StringRef path, u32 flags) { + return base::File(ToBasePath(path), flags); +} + +namespace { +constexpr mem_size kChunk = mem_size{1} << 30; +} // namespace + +bool ReadAt(base::File& file, u64 offset, base::Span bytes) { + for (mem_size done = 0; done < bytes.size();) { + const mem_size n = bytes.size() - done < kChunk ? bytes.size() - done : kChunk; + if (!file.ReadAndCheck(static_cast(offset + done), bytes.subspan(done, n))) return false; + done += n; + } + return true; +} + +bool WriteAll(base::File& file, base::Span bytes) { + for (mem_size done = 0; done < bytes.size();) { + const mem_size n = bytes.size() - done < kChunk ? bytes.size() - done : kChunk; + if (!file.WriteAtCurrentPosAndCheck(bytes.subspan(done, n))) return false; + done += n; + } + return true; +} + +bool ReadFile(base::StringRef path, base::Vector* out) { + base::File file(ToBasePath(path), base::File::FLAG_OPEN | base::File::FLAG_READ); + if (!file.IsValid()) return false; + base::File::Info info; + if (!file.GetInfo(&info) || info.is_directory) return false; + out->resize(static_cast(info.size)); + mem_size done = 0; + while (done < out->size()) { + const mem_size chunk = out->size() - done > (1u << 30) ? (1u << 30) : out->size() - done; + const int got = file.Read(static_cast(done), reinterpret_cast(out->data() + done), + static_cast(chunk)); + if (got <= 0) break; + done += static_cast(got); + } + out->resize(done); + return done == static_cast(info.size); +} + +bool ReadTextFile(base::StringRef path, base::String* out) { + base::Vector bytes; + if (!ReadFile(path, &bytes)) return false; + *out = base::String(reinterpret_cast(bytes.data()), bytes.size()); + return true; +} + +bool WriteFile(base::StringRef path, base::Span bytes) { + base::File file(ToBasePath(path), + base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); + if (!file.IsValid()) return false; + return WriteAll(file, bytes); +} + +bool WriteTextFile(base::StringRef path, base::StringRef text) { + return WriteFile(path, base::Span(reinterpret_cast(text.data()), + text.size())); +} + +} // namespace rx::fs diff --git a/engine/core/file_system.h b/engine/core/file_system.h new file mode 100644 index 00000000..25b3fb72 --- /dev/null +++ b/engine/core/file_system.h @@ -0,0 +1,89 @@ +#ifndef RX_CORE_FILE_SYSTEM_H_ +#define RX_CORE_FILE_SYSTEM_H_ + +#include "base/containers/span.h" +#include "base/containers/vector.h" +#include "base/filesystem/file.h" +#include "base/optional.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/export.h" +#include "core/types.h" + +// Files and paths for the engine, over base::File and the OS. Paths are UTF-8 +// strings in generic form: '/' separates, and Windows accepts '\\' as well. +// +// The lexical functions follow std::filesystem::path on the generic format +// exactly (Filename("a/b/") is empty, LexicallyNormal("a/b/..") is "a/"), +// because asset keys and cache names are built from their output. +namespace rx::fs { + +// Lexical, no disk access. +RX_CORE_EXPORT bool IsAbsolute(base::StringRef path); +RX_CORE_EXPORT base::StringRef Filename(base::StringRef path); +RX_CORE_EXPORT base::StringRef ParentPath(base::StringRef path); +RX_CORE_EXPORT base::StringRef Extension(base::StringRef path); +RX_CORE_EXPORT base::StringRef Stem(base::StringRef path); +// std::filesystem::path::replace_extension: drops Extension(path), then +// appends |extension|, with a '.' in front when it has none. +RX_CORE_EXPORT base::String ReplaceExtension(base::StringRef path, base::StringRef extension); +// std::filesystem::path::operator/: an absolute rhs replaces lhs. +RX_CORE_EXPORT base::String Join(base::StringRef lhs, base::StringRef rhs); +RX_CORE_EXPORT base::String LexicallyNormal(base::StringRef path); +RX_CORE_EXPORT base::String LexicallyRelative(base::StringRef path, base::StringRef base_dir); +// '\\' to '/' on Windows, the input elsewhere. +RX_CORE_EXPORT base::String GenericString(base::StringRef path); + +// Disk queries. Each is false/empty when the path cannot be stat'ed. +RX_CORE_EXPORT bool Exists(base::StringRef path); +RX_CORE_EXPORT bool IsRegularFile(base::StringRef path); +RX_CORE_EXPORT bool IsDirectory(base::StringRef path); +RX_CORE_EXPORT base::Optional FileSize(base::StringRef path); +// Nanoseconds since the Unix epoch. Only meaningful compared to itself. +RX_CORE_EXPORT base::Optional LastWriteTime(base::StringRef path); + +RX_CORE_EXPORT base::String CurrentPath(); +RX_CORE_EXPORT base::String TempDirectory(); +RX_CORE_EXPORT base::String Absolute(base::StringRef path); +// The existing prefix resolved through symlinks, the rest appended lexically. +RX_CORE_EXPORT base::String WeaklyCanonical(base::StringRef path); +// LexicallyRelative of both paths after WeaklyCanonical. +RX_CORE_EXPORT base::String Relative(base::StringRef path, base::StringRef base_dir); + +// Mutation. Each returns false on failure; errno/GetLastError tell why. +RX_CORE_EXPORT bool CreateDirectories(base::StringRef path); +// A file or an empty directory. False also when nothing was there. +RX_CORE_EXPORT bool Remove(base::StringRef path); +// Everything under path, and path. Returns how many entries went. +RX_CORE_EXPORT u64 RemoveAll(base::StringRef path); +// Replaces an existing destination file, as std::filesystem::rename does. +RX_CORE_EXPORT bool Rename(base::StringRef from, base::StringRef to); + +struct DirEntry { + base::String path; // the directory argument joined with the entry name + bool is_directory = false; + bool is_regular = false; +}; +// Entries in readdir / FindNextFile order, which is the order +// std::filesystem::directory_iterator walks; "." and ".." are skipped. A +// recursive listing is a pre-order walk that descends into each directory as +// it is met, like recursive_directory_iterator. False if dir cannot be opened. +RX_CORE_EXPORT bool ListDirectory(base::StringRef dir, base::Vector* out, + bool recursive = false); + +// base::File on a UTF-8 path; flags are base::File::FLAG_*. +RX_CORE_EXPORT base::File OpenFile(base::StringRef path, u32 flags); +// All of |bytes|, in the int-sized pieces base::File takes. False on a short +// read or write. +RX_CORE_EXPORT bool ReadAt(base::File& file, u64 offset, base::Span bytes); +RX_CORE_EXPORT bool WriteAll(base::File& file, base::Span bytes); + +// Whole files. Read fails on a missing or unreadable file; Write truncates. +RX_CORE_EXPORT bool ReadFile(base::StringRef path, base::Vector* out); +RX_CORE_EXPORT bool ReadTextFile(base::StringRef path, base::String* out); +RX_CORE_EXPORT bool WriteFile(base::StringRef path, base::Span bytes); +RX_CORE_EXPORT bool WriteTextFile(base::StringRef path, base::StringRef text); + +} // namespace rx::fs + +#endif // RX_CORE_FILE_SYSTEM_H_ diff --git a/engine/core/format.cc b/engine/core/format.cc new file mode 100644 index 00000000..b1ef947e --- /dev/null +++ b/engine/core/format.cc @@ -0,0 +1,244 @@ +#include "core/format.h" + +#include +#include + +#include "base/strings/float_format.h" + +namespace rx { +namespace { + +// |value| as count significant decimal digits times 10^exponent, the first +// digit ahead of the point: 1.25e-3 is {"125", 3, -3}. +struct Decimal { + char digits[24]; + int count = 0; + int exponent = 0; +}; + +// The correctly rounded |precision|-digit decimal of a finite positive value, +// taken apart from base's exact %e. +Decimal Round(f64 value, int precision) { + // FormatFloatTo does not terminate its output; atoi below must not run on + // into whatever the stack holds after the exponent. + char text[64]; + const mem_size n = + base::FormatFloatTo(text, sizeof(text) - 1, value, 'e', precision - 1, '\0', false); + text[n < sizeof(text) ? n : sizeof(text) - 1] = '\0'; + Decimal d; + mem_size i = 0; + for (; i < n && text[i] != 'e'; ++i) { + if (text[i] != '.') d.digits[d.count++] = text[i]; + } + d.exponent = atoi(text + i + 1); + return d; +} + +// Moves the last digit one step up or down, carrying, at the same length. +Decimal Step(Decimal d, int direction) { + int i = d.count - 1; + if (direction > 0) { + while (i >= 0 && d.digits[i] == '9') d.digits[i--] = '0'; + if (i < 0) { + // 9.99 -> 10.0: renormalize to 1.00 one decade up. + d.digits[0] = '1'; + for (int k = 1; k < d.count; ++k) d.digits[k] = '0'; + ++d.exponent; + } else { + ++d.digits[i]; + } + } else { + while (i >= 0 && d.digits[i] == '0') d.digits[i--] = '9'; + --d.digits[i]; + if (d.digits[0] == '0') { + // 1.00 -> 0.99(9): renormalize to 9.99 one decade down. + for (int k = 0; k + 1 < d.count; ++k) d.digits[k] = d.digits[k + 1]; + d.digits[d.count - 1] = '9'; + --d.exponent; + } + } + return d; +} + +// "d.ddde[-]x", enough for strtod. +void Scientific(const Decimal& d, char* out) { + int n = 0; + out[n++] = d.digits[0]; + if (d.count > 1) { + out[n++] = '.'; + for (int k = 1; k < d.count; ++k) out[n++] = d.digits[k]; + } + out[n++] = 'e'; + int e = d.exponent; + if (e < 0) { + out[n++] = '-'; + e = -e; + } + char rev[8]; + int r = 0; + do { + rev[r++] = static_cast('0' + e % 10); + e /= 10; + } while (e); + while (r) out[n++] = rev[--r]; + out[n] = '\0'; +} + +template +bool ReadsBackAs(const Decimal& d, T value) { + char text[48]; + Scientific(d, text); + if constexpr (sizeof(T) == sizeof(f32)) { + return ::strtof(text, nullptr) == value; + } else { + return ::strtod(text, nullptr) == value; + } +} + +template +Decimal Shortest(T value) { + constexpr int kMaxDigits = sizeof(T) == sizeof(f32) ? 9 : 17; + for (int p = 1; p < kMaxDigits; ++p) { + const Decimal nearest = Round(value, p); + if (ReadsBackAs(nearest, value)) return nearest; + // At a power of two the rounding interval is narrower below than above, + // so the nearest p-digit decimal can miss while the one on the far side + // of |value| still reads back. No other p-digit decimal can. + char text[48]; + Scientific(nearest, text); + const bool nearest_below = ::strtod(text, nullptr) < static_cast(value); + const Decimal other = Step(nearest, nearest_below ? 1 : -1); + if (ReadsBackAs(other, value)) return other; + } + return Round(value, kMaxDigits); +} + +void AppendDecimal(base::String& out, Decimal d, f64 value) { + while (d.count > 1 && d.digits[d.count - 1] == '0') --d.count; + + // printf's %e: at least two exponent digits. + char sci[48]; + int s = 0; + sci[s++] = d.digits[0]; + if (d.count > 1) { + sci[s++] = '.'; + for (int k = 1; k < d.count; ++k) sci[s++] = d.digits[k]; + } + sci[s++] = 'e'; + int e = d.exponent; + sci[s++] = e < 0 ? '-' : '+'; + if (e < 0) e = -e; + char rev[8]; + int r = 0; + do { + rev[r++] = static_cast('0' + e % 10); + e /= 10; + } while (e); + if (r < 2) rev[r++] = '0'; + while (r) sci[s++] = rev[--r]; + + // %f of the same digits: the point after exponent + 1 digits. + const int point = d.exponent + 1; + // With no fraction the value is an integer, and the fixed form is its exact + // digits: every same-length spelling reads back, and std picks the closest. + char whole[400]; + int fixed_length; + if (point <= 0) { + fixed_length = 2 - point + d.count; // "0." zeros digits + } else if (point >= d.count) { + fixed_length = static_cast( + base::FormatFloatTo(whole, sizeof(whole), value, 'f', 0, '\0', false)); + } else { + fixed_length = d.count + 1; + } + + if (s < fixed_length) { + out.append(sci, static_cast(s)); + return; + } + if (point <= 0) { + out.append("0."); + for (int k = 0; k < -point; ++k) out.push_back('0'); + out.append(d.digits, static_cast(d.count)); + } else if (point >= d.count) { + out.append(whole, static_cast(fixed_length)); + } else { + out.append(d.digits, static_cast(point)); + out.push_back('.'); + out.append(d.digits + point, static_cast(d.count - point)); + } +} + +template +void AppendShortestImpl(base::String& out, T value) { + if (::isnan(value)) { + out.append(::signbit(value) ? "-nan" : "nan"); + return; + } + if (::signbit(value)) { + out.push_back('-'); + value = -value; + } + if (::isinf(value)) { + out.append("inf"); + return; + } + if (value == 0) { + out.push_back('0'); + return; + } + AppendDecimal(out, Shortest(value), static_cast(value)); +} + +} // namespace + +void AppendShortest(base::String& out, f32 value) { AppendShortestImpl(out, value); } +void AppendShortest(base::String& out, f64 value) { AppendShortestImpl(out, value); } + +namespace format_detail { + +void FormatImpl(base::String& out, const char* fmt, const Appender* appenders, + const void* const* values, mem_size count) { + mem_size next_auto = 0; + const char* run = fmt; + const char* p = fmt; + auto flush = [&](const char* end) { + if (end > run) out.append(run, static_cast(end - run)); + }; + while (*p) { + if ((p[0] == '{' && p[1] == '{') || (p[0] == '}' && p[1] == '}')) { + flush(p + 1); + p += 2; + run = p; + continue; + } + if (*p != '{') { + ++p; + continue; + } + flush(p); + ++p; + mem_size index = 0; + bool manual = false; + while (*p >= '0' && *p <= '9') { + index = index * 10 + static_cast(*p - '0'); + manual = true; + ++p; + } + if (!manual) index = next_auto++; + base::StringRef spec; + if (*p == ':') { + const char* spec_start = ++p; + while (*p && *p != '}') ++p; + spec = base::StringRef(spec_start, static_cast(p - spec_start)); + } + // The compile-time check rules out a bad index or a missing '}'. + if (index < count) appenders[index](out, spec, values[index]); + if (*p == '}') ++p; + run = p; + } + flush(p); +} + +} // namespace format_detail +} // namespace rx diff --git a/engine/core/format.h b/engine/core/format.h new file mode 100644 index 00000000..4076bb58 --- /dev/null +++ b/engine/core/format.h @@ -0,0 +1,150 @@ +#ifndef RX_CORE_FORMAT_H_ +#define RX_CORE_FORMAT_H_ + +#include "base/meta/traits.h" +#include "base/strings/format.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/export.h" +#include "core/types.h" + +namespace rx { + +// std::format for the engine, over base::FormatTo. The two differ where it +// shows: base writes a plain {} float as printf's %g (six digits) and reads +// {:.3} on a float as %.3f, where std::format writes the shortest text that +// reads back to the same value and reads {:.3} as %.3g. Scene files, the +// --validate report and the log were all written with std::format, so this +// keeps its answers: those two cases are done here, everything else is +// handed to base one placeholder at a time. +// +// The format string is checked at compile time, as std::format_string was: +// braces must balance, and every placeholder must name an argument. + +// Appends the shortest text that parses back to exactly |value|, as +// std::to_chars and std::format("{}") produce it: fixed or scientific, +// whichever is shorter, fixed on a tie. +RX_CORE_EXPORT void AppendShortest(base::String& out, f32 value); +RX_CORE_EXPORT void AppendShortest(base::String& out, f64 value); + +namespace format_detail { + +// Compile-time: calling this from a consteval context fails the build with +// the message in the call site. +void FormatStringError(const char*); + +consteval void Check(const char* s, mem_size arg_count) { + mem_size next_auto = 0; + bool used_manual = false; + for (mem_size i = 0; s[i]; ++i) { + if (s[i] == '}') { + if (s[i + 1] != '}') FormatStringError("unmatched '}' in format string"); + ++i; + continue; + } + if (s[i] != '{') continue; + if (s[i + 1] == '{') { + ++i; + continue; + } + ++i; + mem_size index = 0; + bool manual = false; + while (s[i] >= '0' && s[i] <= '9') { + index = index * 10 + static_cast(s[i] - '0'); + manual = true; + ++i; + } + if (manual) { + used_manual = true; + } else { + if (used_manual) FormatStringError("mixed automatic and manual argument indices"); + index = next_auto++; + } + if (index >= arg_count) FormatStringError("format string names a missing argument"); + while (s[i] && s[i] != '}') ++i; + if (!s[i]) FormatStringError("unterminated '{' in format string"); + } +} + +// Keeps the format string from taking part in argument deduction. +template +struct Identity { + using type = T; +}; + +using Appender = void (*)(base::String& out, base::StringRef spec, const void* value); + +template +void Append(base::String& out, base::StringRef spec, const void* value) { + const T& v = *static_cast(value); + if constexpr (base::is_floating_point_v) { + if (spec.empty()) { + AppendShortest(out, v); + return; + } + } + // "{:" + spec + "}" for a single argument. A spec is a handful of chars. + char buffer[64]; + mem_size n = 0; + buffer[n++] = '{'; + buffer[n++] = ':'; + for (mem_size i = 0; i < spec.size() && n < sizeof(buffer) - 3; ++i) buffer[n++] = spec[i]; + if constexpr (base::is_floating_point_v) { + // std reads a precision with no type as general, base as fixed. + const char last = spec[spec.size() - 1]; + const bool has_type = (last >= 'a' && last <= 'z') || (last >= 'A' && last <= 'Z'); + if (!has_type && spec.find('.') != base::StringRef::npos) buffer[n++] = 'g'; + } + buffer[n++] = '}'; + buffer[n] = '\0'; + base::FormatTo(out, buffer, v); +} + +RX_CORE_EXPORT void FormatImpl(base::String& out, const char* fmt, const Appender* appenders, + const void* const* values, mem_size count); + +} // namespace format_detail + +template +struct FormatString { + template + consteval FormatString(const char (&s)[N]) : str(s) { // NOLINT(runtime/explicit) + format_detail::Check(s, sizeof...(Args)); + } + const char* str; +}; + +template +void StrFormatTo(base::String& out, FormatString::type...> fmt, + const Args&... args) { + if constexpr (sizeof...(Args) == 0) { + format_detail::FormatImpl(out, fmt.str, nullptr, nullptr, 0); + } else { + const format_detail::Appender appenders[] = {&format_detail::Append...}; + const void* const values[] = {static_cast(&args)...}; + format_detail::FormatImpl(out, fmt.str, appenders, values, sizeof...(Args)); + } +} + +template +base::String StrFormat(FormatString::type...> fmt, const Args&... args) { + base::String out; + StrFormatTo(out, fmt, args...); + return out; +} + +// std::to_string: integers in decimal, floating point as printf's %f, which +// base::ToString does not do (it keeps one or two decimals). +template +base::String ToString(T value) { + if constexpr (base::is_floating_point_v) { + return StrFormat("{:f}", value); + } else { + return StrFormat("{}", value); + } +} + +} // namespace rx + +#endif // RX_CORE_FORMAT_H_ diff --git a/engine/core/frame_timer.cc b/engine/core/frame_timer.cc index f8f82bdd..3d047174 100644 --- a/engine/core/frame_timer.cc +++ b/engine/core/frame_timer.cc @@ -1,20 +1,18 @@ #include "core/frame_timer.h" - -#include +#include "core/scalar.h" namespace rx { -FrameTimer::FrameTimer(f64 fixed_step) : fixed_step_(fixed_step), last_(Clock::now()) {} +FrameTimer::FrameTimer(f64 fixed_step) : fixed_step_(fixed_step), last_(base::TimeTicks::Now()) {} int FrameTimer::Tick() { - auto now = Clock::now(); - frame_delta_ = fixed_delta_ > 0.0 ? fixed_delta_ - : std::chrono::duration(now - last_).count(); + const base::TimeTicks now = base::TimeTicks::Now(); + frame_delta_ = fixed_delta_ > 0.0 ? fixed_delta_ : (now - last_).InSecondsF(); last_ = now; ++frame_index_; // Clamp so a debugger pause does not produce a spiral of death. - accumulator_ += std::min(frame_delta_, 0.25); + accumulator_ += rx::Min(frame_delta_, 0.25); int steps = 0; while (accumulator_ >= fixed_step_) { accumulator_ -= fixed_step_; diff --git a/engine/core/frame_timer.h b/engine/core/frame_timer.h index 0db390d7..532ef5db 100644 --- a/engine/core/frame_timer.h +++ b/engine/core/frame_timer.h @@ -1,7 +1,7 @@ #ifndef RX_CORE_FRAME_TIMER_H_ #define RX_CORE_FRAME_TIMER_H_ -#include +#include "base/time/time.h" #include "core/export.h" #include "core/types.h" @@ -31,14 +31,12 @@ class RX_CORE_EXPORT FrameTimer { u64 frame_index() const { return frame_index_; } private: - using Clock = std::chrono::steady_clock; - f64 fixed_step_; f64 fixed_delta_ = 0.0; // > 0 = lockstep f64 accumulator_ = 0.0; f64 frame_delta_ = 0.0; u64 frame_index_ = 0; - Clock::time_point last_; + base::TimeTicks last_; }; } // namespace rx diff --git a/engine/core/input_bindings.cc b/engine/core/input_bindings.cc index 36aa0bc4..057b90ba 100644 --- a/engine/core/input_bindings.cc +++ b/engine/core/input_bindings.cc @@ -1,13 +1,18 @@ +#include "base/containers/vector.h" +#include "base/functional/function.h" +#include "base/algorithm.h" +#include "base/memory/move.h" +#include "base/strings/xstring.h" +#include "base/strings/format.h" +#include "core/file_system.h" #include "core/input_bindings.h" +#include "core/scalar.h" +#include "core/text_reader.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include namespace rx { namespace { @@ -50,7 +55,7 @@ static_assert(sizeof(kPadAxisTokens) / sizeof(kPadAxisTokens[0]) == int FindToken(const char* name, const char* const* table, int count) { for (int i = 0; i < count; ++i) - if (std::strcmp(name, table[i]) == 0) return i; + if (::strcmp(name, table[i]) == 0) return i; return -1; } @@ -58,22 +63,22 @@ int FindToken(const char* name, const char* const* table, int count) { // Binding tokens (device-only, independent of the game's action set) -std::string BindingToken(const Binding& b) { +base::String BindingToken(const Binding& b) { switch (b.kind) { case SourceKind::kKey: - if (b.code < static_cast(Key::kCount)) return std::string("key:") + kKeyTokens[b.code]; + if (b.code < static_cast(Key::kCount)) return base::String("key:") + kKeyTokens[b.code]; break; case SourceKind::kMouseButton: if (b.code < static_cast(MouseButton::kCount)) - return std::string("mouse:") + kMouseTokens[b.code]; + return base::String("mouse:") + kMouseTokens[b.code]; break; case SourceKind::kGamepadButton: if (b.code < static_cast(GamepadButton::kCount)) - return std::string("pad:") + kPadButtonTokens[b.code]; + return base::String("pad:") + kPadButtonTokens[b.code]; break; case SourceKind::kGamepadAxis: if (b.code < static_cast(GamepadAxis::kCount)) { - std::string s = std::string("padaxis:") + kPadAxisTokens[b.code]; + base::String s = base::String("padaxis:") + kPadAxisTokens[b.code]; if (b.axis_dir > 0) s += '+'; else if (b.axis_dir < 0) s += '-'; return s; @@ -86,10 +91,10 @@ std::string BindingToken(const Binding& b) { } bool BindingFromToken(const char* token, Binding* out) { - const char* colon = std::strchr(token, ':'); + const char* colon = ::strchr(token, ':'); if (!colon) return false; - std::string kind(token, colon - token); - std::string rest(colon + 1); + base::String kind(token, colon - token); + base::String rest(colon + 1); Binding b; if (kind == "key") { int i = FindToken(rest.c_str(), kKeyTokens, static_cast(Key::kCount)); @@ -124,21 +129,21 @@ bool BindingFromToken(const char* token, Binding* out) { return true; } -std::string BindingLabel(const Binding& b) { +base::String BindingLabel(const Binding& b) { switch (b.kind) { case SourceKind::kKey: return b.code < static_cast(Key::kCount) ? kKeyTokens[b.code] : "?"; case SourceKind::kMouseButton: return b.code < static_cast(MouseButton::kCount) - ? std::string("Mouse ") + kMouseTokens[b.code] + ? base::String("Mouse ") + kMouseTokens[b.code] : "?"; case SourceKind::kGamepadButton: return b.code < static_cast(GamepadButton::kCount) - ? std::string("Pad ") + kPadButtonTokens[b.code] + ? base::String("Pad ") + kPadButtonTokens[b.code] : "?"; case SourceKind::kGamepadAxis: { if (b.code >= static_cast(GamepadAxis::kCount)) return "?"; - std::string s = std::string("Pad ") + kPadAxisTokens[b.code]; + base::String s = base::String("Pad ") + kPadAxisTokens[b.code]; if (b.axis_dir > 0) s += "+"; else if (b.axis_dir < 0) s += "-"; return s; @@ -167,8 +172,8 @@ void InputMap::RegisterFoldId(AxisId axis, ActionId positive, ActionId negative) folds_.push_back({axis, positive, negative}); } -void InputMap::SetDefaultsFn(std::function fn) { - defaults_ = std::move(fn); +void InputMap::SetDefaultsFn(base::Function fn) { + defaults_ = base::move(fn); LoadDefaults(); } @@ -182,7 +187,7 @@ const char* InputMap::AxisName(AxisId a) const { bool InputMap::ActionFromName(const char* name, ActionId* out) const { for (int i = 0; i < action_count_; ++i) - if (action_names_[i] && std::strcmp(name, action_names_[i]) == 0) { + if (action_names_[i] && ::strcmp(name, action_names_[i]) == 0) { *out = static_cast(i); return true; } @@ -191,7 +196,7 @@ bool InputMap::ActionFromName(const char* name, ActionId* out) const { bool InputMap::AxisFromName(const char* name, AxisId* out) const { for (int i = 0; i < axis_count_; ++i) - if (axis_names_[i] && std::strcmp(name, axis_names_[i]) == 0) { + if (axis_names_[i] && ::strcmp(name, axis_names_[i]) == 0) { *out = static_cast(i); return true; } @@ -208,14 +213,14 @@ void InputMap::AddActionBinding(ActionId a, Binding b) { if (a >= kMaxActions) return; auto& v = action_[a]; if (static_cast(v.size()) >= kMaxBindings) return; - if (std::find(v.begin(), v.end(), b) == v.end()) v.push_back(b); + if (base::Find(v.begin(), v.end(), b) == v.end()) v.push_back(b); } void InputMap::AddAxisBindingId(AxisId a, Binding b) { if (a >= kMaxAxes) return; auto& v = axis_[a]; if (static_cast(v.size()) >= kMaxBindings) return; - if (std::find(v.begin(), v.end(), b) == v.end()) v.push_back(b); + if (base::Find(v.begin(), v.end(), b) == v.end()) v.push_back(b); } namespace { @@ -231,13 +236,11 @@ void InputMap::RebindAction(ActionId a, Binding b) { if (a >= kMaxActions) return; // Remove this source from every action so it isn't bound twice. for (auto& v : action_) - v.erase(std::remove(v.begin(), v.end(), b), v.end()); + base::EraseIf(v, [&](const Binding& x) { return x == b; }); // Drop the target's existing same-family binding, then add the new one. auto& t = action_[a]; const int fam = SourceFamily(b.kind); - t.erase(std::remove_if(t.begin(), t.end(), - [&](const Binding& x) { return SourceFamily(x.kind) == fam; }), - t.end()); + base::EraseIf(t, [&](const Binding& x) { return SourceFamily(x.kind) == fam; }); if (static_cast(t.size()) < kMaxBindings) t.push_back(b); else t.back() = b; } @@ -258,7 +261,7 @@ void InputMap::ResetToDefaults() { ActionId InputMap::ConflictingAction(const Binding& b) const { for (int a = 0; a < action_count_; ++a) - if (std::find(action_[a].begin(), action_[a].end(), b) != action_[a].end()) + if (base::Find(action_[a].begin(), action_[a].end(), b) != action_[a].end()) return static_cast(a); return static_cast(action_count_); } @@ -282,7 +285,7 @@ bool InputMap::SourceHeld(const Binding& b, const InputState& kbm, const Gamepad f32 thr = trigger ? trigger_threshold : stick_deadzone; if (b.axis_dir > 0) return v > thr; if (b.axis_dir < 0) return v < -thr; - return std::fabs(v) > thr; + return ::fabs(v) > thr; } case SourceKind::kNone: return false; @@ -295,11 +298,11 @@ f32 InputMap::AxisValue(const Binding& b, const GamepadState& pad) const { b.code >= static_cast(GamepadAxis::kCount)) return 0; f32 v = pad.axes[b.code]; - if (std::fabs(v) < stick_deadzone) return 0; + if (::fabs(v) < stick_deadzone) return 0; // Rescale so the value ramps from 0 at the deadzone edge to 1 at full throw. - f32 s = (std::fabs(v) - stick_deadzone) / (1.0f - stick_deadzone); - s = std::clamp(s, 0.0f, 1.0f); - return std::copysign(s, v); + f32 s = (::fabs(v) - stick_deadzone) / (1.0f - stick_deadzone); + s = rx::Clamp(s, 0.0f, 1.0f); + return ::copysign(s, v); } void InputMap::Resolve(const InputState& kbm, const GamepadState& pad, const TouchState& touch, @@ -308,7 +311,7 @@ void InputMap::Resolve(const InputState& kbm, const GamepadState& pad, const Tou for (int a = 0; a < axis_count_; ++a) { f32 v = 0; for (const Binding& b : axis_[a]) v += AxisValue(b, pad); - out->analog[a] = std::clamp(v, -1.0f, 1.0f); + out->analog[a] = rx::Clamp(v, -1.0f, 1.0f); } // Digital actions: a source held => action held; edge from the previous pump. @@ -330,7 +333,7 @@ void InputMap::Resolve(const InputState& kbm, const GamepadState& pad, const Tou f32 v = out->analog[f.axis]; if (out->held[f.positive]) v += 1.0f; if (out->held[f.negative]) v -= 1.0f; - out->analog[f.axis] = std::clamp(v, -1.0f, 1.0f); + out->analog[f.axis] = rx::Clamp(v, -1.0f, 1.0f); } // Track the most recently active device for prompt glyphs. @@ -347,7 +350,7 @@ void InputMap::Resolve(const InputState& kbm, const GamepadState& pad, const Tou bool trigger = x == static_cast(GamepadAxis::kLeftTrigger) || x == static_cast(GamepadAxis::kRightTrigger); f32 thr = trigger ? trigger_threshold : stick_deadzone; - if (std::fabs(pad.axes[x]) > thr) pad_active = true; + if (::fabs(pad.axes[x]) > thr) pad_active = true; } } // A finger landing wins over a stale pointer position: on a handheld the @@ -367,60 +370,62 @@ void InputMap::Resolve(const InputState& kbm, const GamepadState& pad, const Tou namespace { -std::string Trim(const std::string& s) { +base::String Trim(const base::String& s) { size_t a = s.find_first_not_of(" \t\r\n"); - if (a == std::string::npos) return ""; + if (a == base::String::npos) return ""; size_t b = s.find_last_not_of(" \t\r\n"); return s.substr(a, b - a + 1); } } // namespace -std::string InputMap::DefaultConfigPath() { +base::String InputMap::DefaultConfigPath() { #if defined(_WIN32) - if (const char* appdata = std::getenv("APPDATA")) - return std::string(appdata) + "\\rx\\controls.ini"; + if (const char* appdata = ::getenv("APPDATA")) + return base::String(appdata) + "\\rx\\controls.ini"; return ""; #elif defined(__APPLE__) - if (const char* home = std::getenv("HOME")) - return std::string(home) + "/Library/Application Support/rx/controls.ini"; + if (const char* home = ::getenv("HOME")) + return base::String(home) + "/Library/Application Support/rx/controls.ini"; return ""; #else - if (const char* xdg = std::getenv("XDG_CONFIG_HOME")) - return std::string(xdg) + "/rx/controls.ini"; - if (const char* home = std::getenv("HOME")) - return std::string(home) + "/.config/rx/controls.ini"; + if (const char* xdg = ::getenv("XDG_CONFIG_HOME")) + return base::String(xdg) + "/rx/controls.ini"; + if (const char* home = ::getenv("HOME")) + return base::String(home) + "/.config/rx/controls.ini"; return ""; #endif } -bool InputMap::LoadFromIni(const std::string& path) { - std::ifstream in(path); - if (!in) return false; +bool InputMap::LoadFromIni(const base::String& path) { + base::String text; + if (!fs::ReadTextFile(path, &text)) return false; - std::string section, line; - while (std::getline(in, line)) { - std::string t = Trim(line); + base::String section; + LineReader lines(text); + base::StringRef line; + while (lines.Next(&line)) { + base::String t = Trim(base::String(line.data(), line.size())); if (t.empty() || t[0] == '#' || t[0] == ';') continue; - if (t.front() == '[' && t.back() == ']') { + if (t[0] == '[' && t.back() == ']') { section = t.substr(1, t.size() - 2); continue; } size_t eq = t.find('='); - if (eq == std::string::npos) continue; - std::string key = Trim(t.substr(0, eq)); - std::string value = Trim(t.substr(eq + 1)); + if (eq == base::String::npos) continue; + base::String key = Trim(t.substr(0, eq)); + base::String value = Trim(t.substr(eq + 1)); if (section == "options") { - if (key == "look_sens_kbm") look_sens_kbm = std::strtof(value.c_str(), nullptr); - else if (key == "look_sens_pad") look_sens_pad = std::strtof(value.c_str(), nullptr); + if (key == "look_sens_kbm") look_sens_kbm = ::strtof(value.c_str(), nullptr); + else if (key == "look_sens_pad") look_sens_pad = ::strtof(value.c_str(), nullptr); else if (key == "invert_y") invert_y = value == "1" || value == "true"; - else if (key == "stick_deadzone") stick_deadzone = std::strtof(value.c_str(), nullptr); - else if (key == "trigger_threshold") trigger_threshold = std::strtof(value.c_str(), nullptr); + else if (key == "stick_deadzone") stick_deadzone = ::strtof(value.c_str(), nullptr); + else if (key == "trigger_threshold") trigger_threshold = ::strtof(value.c_str(), nullptr); else if (key == "rumble") rumble = value == "1" || value == "true"; else if (key == "adaptive_triggers") adaptive_triggers = value == "1" || value == "true"; else if (key == "led" && value.size() == 6) { - auto hex = [&](int i) { return static_cast(std::strtol(value.substr(i, 2).c_str(), nullptr, 16)); }; + auto hex = [&](int i) { return static_cast(::strtol(value.substr(i, 2).c_str(), nullptr, 16)); }; led_r = hex(0); led_g = hex(2); led_b = hex(4); @@ -429,10 +434,10 @@ bool InputMap::LoadFromIni(const std::string& path) { ActionId a; if (!ActionFromName(key.c_str(), &a)) continue; action_[a].clear(); // file overrides the default for this action - std::stringstream ss(value); - std::string tok; - while (std::getline(ss, tok, ',')) { - tok = Trim(tok); + LineReader tokens(value, ','); + base::StringRef piece; + while (tokens.Next(&piece)) { + base::String tok = Trim(base::String(piece.data(), piece.size())); Binding b; if (!tok.empty() && BindingFromToken(tok.c_str(), &b)) AddActionBinding(a, b); } @@ -440,10 +445,10 @@ bool InputMap::LoadFromIni(const std::string& path) { AxisId ax; if (!AxisFromName(key.c_str(), &ax)) continue; axis_[ax].clear(); - std::stringstream ss(value); - std::string tok; - while (std::getline(ss, tok, ',')) { - tok = Trim(tok); + LineReader tokens(value, ','); + base::StringRef piece; + while (tokens.Next(&piece)) { + base::String tok = Trim(base::String(piece.data(), piece.size())); Binding b; if (!tok.empty() && BindingFromToken(tok.c_str(), &b)) AddAxisBindingId(ax, b); } @@ -452,45 +457,45 @@ bool InputMap::LoadFromIni(const std::string& path) { return true; } -bool InputMap::SaveToIni(const std::string& path) const { - std::ofstream out(path, std::ios::trunc); - if (!out) return false; - - out << "# rx controls. Tokens: key:W mouse:left pad:south padaxis:lefty+\n"; - out << "[options]\n"; - out << "look_sens_kbm=" << look_sens_kbm << "\n"; - out << "look_sens_pad=" << look_sens_pad << "\n"; - out << "invert_y=" << (invert_y ? 1 : 0) << "\n"; - out << "stick_deadzone=" << stick_deadzone << "\n"; - out << "trigger_threshold=" << trigger_threshold << "\n"; - out << "rumble=" << (rumble ? 1 : 0) << "\n"; - out << "adaptive_triggers=" << (adaptive_triggers ? 1 : 0) << "\n"; +bool InputMap::SaveToIni(const base::String& path) const { + base::String out; + out.append("# rx controls. Tokens: key:W mouse:left pad:south padaxis:lefty+\n"); + out.append("[options]\n"); + // {} on a float is printf's %g, which is what the ostream this replaced + // wrote, so existing files round-trip byte for byte. + base::FormatTo(out, "look_sens_kbm={}\n", look_sens_kbm); + base::FormatTo(out, "look_sens_pad={}\n", look_sens_pad); + base::FormatTo(out, "invert_y={}\n", invert_y ? 1 : 0); + base::FormatTo(out, "stick_deadzone={}\n", stick_deadzone); + base::FormatTo(out, "trigger_threshold={}\n", trigger_threshold); + base::FormatTo(out, "rumble={}\n", rumble ? 1 : 0); + base::FormatTo(out, "adaptive_triggers={}\n", adaptive_triggers ? 1 : 0); char led[8]; - std::snprintf(led, sizeof(led), "%02x%02x%02x", led_r, led_g, led_b); - out << "led=" << led << "\n\n"; + ::snprintf(led, sizeof(led), "%02x%02x%02x", led_r, led_g, led_b); + base::FormatTo(out, "led={}\n\n", led); - auto write_tokens = [&out](const std::vector& v) { + auto write_tokens = [&out](const base::Vector& v) { for (size_t i = 0; i < v.size(); ++i) { - if (i) out << ","; - out << BindingToken(v[i]); + if (i) out.append(","); + out.append(BindingToken(v[i])); } }; - out << "[actions]\n"; + out.append("[actions]\n"); for (int a = 0; a < action_count_; ++a) { if (!action_names_[a]) continue; - out << action_names_[a] << "="; + base::FormatTo(out, "{}=", action_names_[a]); write_tokens(action_[a]); - out << "\n"; + out.append("\n"); } - out << "\n[axes]\n"; + out.append("\n[axes]\n"); for (int x = 0; x < axis_count_; ++x) { if (!axis_names_[x]) continue; - out << axis_names_[x] << "="; + base::FormatTo(out, "{}=", axis_names_[x]); write_tokens(axis_[x]); - out << "\n"; + out.append("\n"); } - return static_cast(out); + return fs::WriteTextFile(path, out); } } // namespace rx diff --git a/engine/core/input_bindings.h b/engine/core/input_bindings.h index 4194d3fb..c66dae79 100644 --- a/engine/core/input_bindings.h +++ b/engine/core/input_bindings.h @@ -1,10 +1,10 @@ #ifndef RX_CORE_INPUT_BINDINGS_H_ #define RX_CORE_INPUT_BINDINGS_H_ -#include -#include -#include +#include "base/containers/vector.h" +#include "base/functional/function.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/input.h" #include "core/input_actions.h" @@ -67,7 +67,7 @@ class RX_CORE_EXPORT InputMap { // The application's default-binding routine (it calls AddBinding/AddAxisBinding // for its whole set). Stored so ResetToDefaults() can reapply it, and invoked // immediately to seed the built-in layout. - void SetDefaultsFn(std::function fn); + void SetDefaultsFn(base::Function fn); // Reapplies the registered defaults (bindings only). void LoadDefaults(); @@ -79,11 +79,11 @@ class RX_CORE_EXPORT InputMap { // Rebinding (used by the settings UI) template - const std::vector& bindings(A a) const { + const base::Vector& bindings(A a) const { return action_[static_cast(a)]; } template - const std::vector& axis_bindings(X a) const { + const base::Vector& axis_bindings(X a) const { return axis_[static_cast(a)]; } template @@ -131,12 +131,12 @@ class RX_CORE_EXPORT InputMap { u8 led_r = 0, led_g = 60, led_b = 120; // DualSense lightbar // Persistence - bool LoadFromIni(const std::string& path); - bool SaveToIni(const std::string& path) const; + bool LoadFromIni(const base::String& path); + bool SaveToIni(const base::String& path) const; // Default controls path: $XDG_CONFIG_HOME/rx/controls.ini (or the // platform equivalent). Empty if no home directory can be found. - static std::string DefaultConfigPath(); + static base::String DefaultConfigPath(); private: struct Fold { @@ -162,25 +162,25 @@ class RX_CORE_EXPORT InputMap { bool SourceHeld(const Binding& b, const InputState& kbm, const GamepadState& pad) const; f32 AxisValue(const Binding& b, const GamepadState& pad) const; - std::vector action_[kMaxActions]; - std::vector axis_[kMaxAxes]; + base::Vector action_[kMaxActions]; + base::Vector axis_[kMaxAxes]; bool prev_held_[kMaxActions] = {}; const char* action_names_[kMaxActions] = {}; const char* axis_names_[kMaxAxes] = {}; int action_count_ = 0; // one past the highest registered action id int axis_count_ = 0; // one past the highest registered axis id - std::vector folds_; - std::function defaults_; + base::Vector folds_; + base::Function defaults_; InputDevice last_device_ = InputDevice::kKeyboardMouse; }; // Source token, e.g. "key:W", "mouse:left", "pad:south", "padaxis:lefty+". -std::string BindingToken(const Binding& b); +base::String BindingToken(const Binding& b); bool BindingFromToken(const char* token, Binding* out); // Short label for a binding as shown next to an action in the settings list. -std::string BindingLabel(const Binding& b); +base::String BindingLabel(const Binding& b); } // namespace rx diff --git a/engine/core/job_system.cc b/engine/core/job_system.cc index 9122a72b..69e40588 100644 --- a/engine/core/job_system.cc +++ b/engine/core/job_system.cc @@ -1,58 +1,59 @@ +#include "base/memory/move.h" +#include "base/threading/lock_guard.h" #include "core/job_system.h" -#include - namespace rx { JobSystem::JobSystem(unsigned thread_count) { if (thread_count == 0) { - unsigned hw = std::thread::hardware_concurrency(); + unsigned hw = base::GetProcessorCount(); thread_count = hw > 2 ? hw - 1 : 1; } workers_.reserve(thread_count); for (unsigned i = 0; i < thread_count; ++i) { - workers_.emplace_back([this] { WorkerLoop(); }); + workers_.push_back( + base::MakeUnique("rx-job", [this] { WorkerLoop(); }, /*start_now=*/true)); } } JobSystem::~JobSystem() { { - std::scoped_lock lock(mutex_); + base::LockGuard lock(mutex_); stop_ = true; } - wake_.notify_all(); - for (auto& worker : workers_) worker.join(); + wake_.NotifyAll(); + for (auto& worker : workers_) worker->Join(); } void JobSystem::Submit(JobFn job) { { - std::scoped_lock lock(mutex_); + base::LockGuard lock(mutex_); // SimpleDeque only offers a copying push_back; the closure must be // copy-constructible anyway for StaticFunction. queue_.push_back(job); } - wake_.notify_one(); + wake_.NotifyOne(); } void JobSystem::WaitIdle() { - std::unique_lock lock(mutex_); - idle_.wait(lock, [this] { return queue_.empty() && in_flight_.load() == 0; }); + base::UniqueLock lock(mutex_); + idle_.Wait(lock, [this] { return queue_.empty() && in_flight_.load() == 0; }); } void JobSystem::WorkerLoop() { for (;;) { JobFn job; { - std::unique_lock lock(mutex_); - wake_.wait(lock, [this] { return stop_ || !queue_.empty(); }); + base::UniqueLock lock(mutex_); + wake_.Wait(lock, [this] { return stop_ || !queue_.empty(); }); if (stop_ && queue_.empty()) return; - job = std::move(queue_.front()); + job = base::move(queue_.front()); queue_.pop_front(); in_flight_.fetch_add(1); } job(); in_flight_.fetch_sub(1); - idle_.notify_all(); + idle_.NotifyAll(); } } diff --git a/engine/core/job_system.h b/engine/core/job_system.h index a2756460..6297388b 100644 --- a/engine/core/job_system.h +++ b/engine/core/job_system.h @@ -1,15 +1,15 @@ #ifndef RX_CORE_JOB_SYSTEM_H_ #define RX_CORE_JOB_SYSTEM_H_ -#include -#include -#include -#include - #include #include #include +#include "base/atomic.h" +#include "base/memory/unique_pointer.h" +#include "base/threading/condition_variable.h" +#include "base/threading/mutex.h" +#include "base/threading/thread.h" #include "core/export.h" namespace rx { @@ -33,12 +33,13 @@ class RX_CORE_EXPORT JobSystem { private: void WorkerLoop(); - base::Vector workers_; + // base::Thread is not movable; the vector must not relocate live threads. + base::Vector> workers_; base::SimpleDeque queue_; - std::mutex mutex_; - std::condition_variable wake_; - std::condition_variable idle_; - std::atomic in_flight_{0}; + base::Mutex mutex_; + base::ConditionVariable wake_; + base::ConditionVariable idle_; + base::Atomic in_flight_{0}; bool stop_ = false; }; diff --git a/engine/core/log.cc b/engine/core/log.cc index 7ad81d8f..de3569d8 100644 --- a/engine/core/log.cc +++ b/engine/core/log.cc @@ -1,8 +1,10 @@ +#include "base/atomic.h" +#include "base/strings/string_ref.h" +#include "base/threading/lock_guard.h" +#include "base/threading/mutex.h" #include "core/log.h" -#include -#include -#include +#include #if defined(__ANDROID__) #include @@ -11,8 +13,8 @@ namespace rx { namespace { -std::atomic g_level{LogLevel::kInfo}; -std::mutex g_mutex; +base::Atomic g_level{LogLevel::kInfo}; +base::Mutex g_mutex; const char* LevelTag(LogLevel level) { switch (level) { @@ -31,14 +33,14 @@ void SetLogLevel(LogLevel level) { g_level.store(level); } namespace detail { -void LogMessage(LogLevel level, std::string_view message) { +void LogMessage(LogLevel level, base::StringRef message) { if (level < g_level.load()) return; #if defined(__ANDROID__) __android_log_print(ANDROID_LOG_INFO, "rx", "[%s] %.*s", LevelTag(level), static_cast(message.size()), message.data()); #else - std::scoped_lock lock(g_mutex); - std::fprintf(level >= LogLevel::kWarn ? stderr : stdout, "[%s] %.*s\n", LevelTag(level), + base::LockGuard lock(g_mutex); + ::fprintf(level >= LogLevel::kWarn ? stderr : stdout, "[%s] %.*s\n", LevelTag(level), static_cast(message.size()), message.data()); #endif } diff --git a/engine/core/log.h b/engine/core/log.h index c98865e5..3c7cc423 100644 --- a/engine/core/log.h +++ b/engine/core/log.h @@ -1,24 +1,26 @@ #ifndef RX_CORE_LOG_H_ #define RX_CORE_LOG_H_ -#include -#include +#include "base/memory/move.h" +#include "base/strings/string_ref.h" #include "core/export.h" +#include "core/format.h" namespace rx { enum class LogLevel { kTrace, kDebug, kInfo, kWarn, kError }; namespace detail { -RX_CORE_EXPORT void LogMessage(LogLevel level, std::string_view message); +RX_CORE_EXPORT void LogMessage(LogLevel level, base::StringRef message); } RX_CORE_EXPORT void SetLogLevel(LogLevel level); template -void Log(LogLevel level, std::format_string fmt, Args&&... args) { - detail::LogMessage(level, std::format(fmt, std::forward(args)...)); +void Log(LogLevel level, FormatString::type...> fmt, + const Args&... args) { + detail::LogMessage(level, StrFormat(fmt, args...)); } #define RX_TRACE(...) ::rx::Log(::rx::LogLevel::kTrace, __VA_ARGS__) diff --git a/engine/core/math.h b/engine/core/math.h index 86c7cb40..41c2b4d3 100644 --- a/engine/core/math.h +++ b/engine/core/math.h @@ -1,7 +1,7 @@ #ifndef RX_CORE_MATH_H_ #define RX_CORE_MATH_H_ -#include +#include #include "core/types.h" @@ -49,11 +49,11 @@ inline Vec3 Cross(const Vec3& a, const Vec3& b) { } inline Vec3 Normalize(const Vec3& v) { - f32 length = std::sqrt(Dot(v, v)); + f32 length = ::sqrt(Dot(v, v)); return length > 0 ? Vec3{v.x / length, v.y / length, v.z / length} : v; } -inline f32 Length(const Vec3& v) { return std::sqrt(Dot(v, v)); } +inline f32 Length(const Vec3& v) { return ::sqrt(Dot(v, v)); } inline Vec3 Lerp(const Vec3& a, const Vec3& b, f32 t) { return a + (b - a) * t; } @@ -71,7 +71,7 @@ inline Quat operator*(const Quat& a, const Quat& b) { } inline Quat Normalize(const Quat& q) { - f32 len = std::sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w); + f32 len = ::sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w); if (len <= 0) return {0, 0, 0, 1}; f32 inv = 1.0f / len; return {q.x * inv, q.y * inv, q.z * inv, q.w * inv}; @@ -79,8 +79,8 @@ inline Quat Normalize(const Quat& q) { inline Quat QuatFromAxisAngle(const Vec3& axis, f32 radians) { Vec3 n = Normalize(axis); - f32 s = std::sin(radians * 0.5f); - return {n.x * s, n.y * s, n.z * s, std::cos(radians * 0.5f)}; + f32 s = ::sin(radians * 0.5f); + return {n.x * s, n.y * s, n.z * s, ::cos(radians * 0.5f)}; } inline Quat Conjugate(const Quat& q) { return {-q.x, -q.y, -q.z, q.w}; } @@ -118,10 +118,10 @@ inline Quat Slerp(const Quat& a, Quat b, f32 t) { return Normalize(Quat{a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t}); } - f32 theta = std::acos(cos_theta); - f32 sin_theta = std::sin(theta); - f32 wa = std::sin((1 - t) * theta) / sin_theta; - f32 wb = std::sin(t * theta) / sin_theta; + f32 theta = ::acos(cos_theta); + f32 sin_theta = ::sin(theta); + f32 wa = ::sin((1 - t) * theta) / sin_theta; + f32 wb = ::sin(t * theta) / sin_theta; return {wa * a.x + wb * b.x, wa * a.y + wb * b.y, wa * a.z + wb * b.z, wa * a.w + wb * b.w}; } @@ -259,25 +259,25 @@ inline Quat QuatFromMat4(const Mat4& m) { f32 trace = r00 + r11 + r22; Quat q; if (trace > 0) { - f32 s = 0.5f / std::sqrt(trace + 1.0f); + f32 s = 0.5f / ::sqrt(trace + 1.0f); q.w = 0.25f / s; q.x = (r21 - r12) * s; q.y = (r02 - r20) * s; q.z = (r10 - r01) * s; } else if (r00 > r11 && r00 > r22) { - f32 s = 2.0f * std::sqrt(1.0f + r00 - r11 - r22); + f32 s = 2.0f * ::sqrt(1.0f + r00 - r11 - r22); q.w = (r21 - r12) / s; q.x = 0.25f * s; q.y = (r01 + r10) / s; q.z = (r02 + r20) / s; } else if (r11 > r22) { - f32 s = 2.0f * std::sqrt(1.0f + r11 - r00 - r22); + f32 s = 2.0f * ::sqrt(1.0f + r11 - r00 - r22); q.w = (r02 - r20) / s; q.x = (r01 + r10) / s; q.y = 0.25f * s; q.z = (r12 + r21) / s; } else { - f32 s = 2.0f * std::sqrt(1.0f + r22 - r00 - r11); + f32 s = 2.0f * ::sqrt(1.0f + r22 - r00 - r11); q.w = (r10 - r01) / s; q.x = (r02 + r20) / s; q.y = (r12 + r21) / s; @@ -306,7 +306,7 @@ inline Mat4 LookAt(const Vec3& eye, const Vec3& target, const Vec3& up) { // whole range, which matters for large open-world exteriors. Y is flipped for // vulkan clip space. inline Mat4 PerspectiveReversedZ(f32 fov_y_radians, f32 aspect, f32 near_plane) { - f32 g = 1.0f / std::tan(fov_y_radians * 0.5f); + f32 g = 1.0f / ::tan(fov_y_radians * 0.5f); Mat4 r; r.m[0] = g / aspect; r.m[5] = -g; @@ -320,7 +320,7 @@ inline Mat4 PerspectiveReversedZ(f32 fov_y_radians, f32 aspect, f32 near_plane) // and sampled with the same matrix, so it stays self-consistent without // matching the camera's reversed-infinite clip space. inline Mat4 PerspectiveShadow(f32 fov_y_radians, f32 aspect, f32 near_plane, f32 far_plane) { - f32 g = 1.0f / std::tan(fov_y_radians * 0.5f); + f32 g = 1.0f / ::tan(fov_y_radians * 0.5f); Mat4 r; r.m[0] = g / aspect; r.m[5] = g; diff --git a/engine/core/memory/chunk_pool.cc b/engine/core/memory/chunk_pool.cc index 96b8e416..440ef964 100644 --- a/engine/core/memory/chunk_pool.cc +++ b/engine/core/memory/chunk_pool.cc @@ -2,6 +2,7 @@ #include +#include "base/threading/lock_guard.h" #include "core/memory/memory_tracker.h" namespace rx::mem { @@ -22,7 +23,7 @@ void ChunkPool::AddSlabLocked() { } void* ChunkPool::Acquire() { - std::lock_guard lock(mutex_); + base::LockGuard lock(mutex_); if (free_.empty()) AddSlabLocked(); void* chunk = free_.back(); free_.pop_back(); @@ -31,17 +32,17 @@ void* ChunkPool::Acquire() { void ChunkPool::Release(void* chunk) { if (!chunk) return; - std::lock_guard lock(mutex_); + base::LockGuard lock(mutex_); free_.push_back(chunk); } void ChunkPool::Reserve(size_t chunk_count) { - std::lock_guard lock(mutex_); + base::LockGuard lock(mutex_); while (total_ < chunk_count) AddSlabLocked(); } ChunkPool::Stats ChunkPool::stats() const { - std::lock_guard lock(mutex_); + base::LockGuard lock(mutex_); return Stats{.total_chunks = total_, .free_chunks = free_.size()}; } diff --git a/engine/core/memory/chunk_pool.h b/engine/core/memory/chunk_pool.h index a2f74bd5..371e420b 100644 --- a/engine/core/memory/chunk_pool.h +++ b/engine/core/memory/chunk_pool.h @@ -1,10 +1,10 @@ #ifndef RX_CORE_MEMORY_CHUNK_POOL_H_ #define RX_CORE_MEMORY_CHUNK_POOL_H_ -#include -#include -#include +#include +#include "base/containers/vector.h" +#include "base/threading/mutex.h" #include "core/export.h" #include "core/types.h" @@ -47,9 +47,9 @@ class RX_CORE_EXPORT ChunkPool { void AddSlabLocked(); - mutable std::mutex mutex_; - std::vector slabs_; - std::vector free_; + mutable base::Mutex mutex_; + base::Vector slabs_; + base::Vector free_; size_t total_ = 0; }; diff --git a/engine/core/memory/frame_arena.cc b/engine/core/memory/frame_arena.cc index ef2facbd..74df5d99 100644 --- a/engine/core/memory/frame_arena.cc +++ b/engine/core/memory/frame_arena.cc @@ -1,7 +1,7 @@ #include "core/memory/frame_arena.h" -#include -#include +#include +#include #include namespace rx::mem { diff --git a/engine/core/memory/frame_arena.h b/engine/core/memory/frame_arena.h index a6854932..9665147a 100644 --- a/engine/core/memory/frame_arena.h +++ b/engine/core/memory/frame_arena.h @@ -1,11 +1,11 @@ #ifndef RX_CORE_MEMORY_FRAME_ARENA_H_ #define RX_CORE_MEMORY_FRAME_ARENA_H_ -#include -#include -#include -#include +#include +#include "base/containers/pair.h" +#include "base/containers/vector.h" +#include "base/meta/traits.h" #include "core/export.h" #include "core/types.h" @@ -38,7 +38,7 @@ class RX_CORE_EXPORT FrameArena { // Uninitialized storage for `count` Ts; the arena never runs destructors. template T* AllocArray(size_t count) { - static_assert(std::is_trivially_destructible_v); + static_assert(base::is_trivially_destructible_v); return static_cast(Alloc(count * sizeof(T), alignof(T))); } @@ -59,7 +59,7 @@ class RX_CORE_EXPORT FrameArena { size_t capacity_ = 0; size_t offset_ = 0; size_t high_water_ = 0; - std::vector> overflow_; // block, alignment + base::Vector> overflow_; // block, alignment u64 overflow_allocs_ = 0; size_t overflow_bytes_ = 0; }; diff --git a/engine/core/memory/memory_config.cc b/engine/core/memory/memory_config.cc index 191680ce..3f3ce7b3 100644 --- a/engine/core/memory/memory_config.cc +++ b/engine/core/memory/memory_config.cc @@ -1,25 +1,26 @@ #include "core/memory/memory_config.h" -#include -#include -#include +#include +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/memory/chunk_pool.h" #include "core/memory/frame_arena.h" #include "core/memory/memory_tracker.h" +#include "core/file_system.h" namespace rx::mem { namespace { constexpr u64 kMiB = 1u << 20; -std::string_view Trim(std::string_view s) { +base::StringRef Trim(base::StringRef s) { while (!s.empty() && (s.front() == ' ' || s.front() == '\t')) s.remove_prefix(1); while (!s.empty() && (s.back() == ' ' || s.back() == '\t' || s.back() == '\r')) s.remove_suffix(1); return s; } -bool ParseU64(std::string_view s, u64& out) { +bool ParseU64(base::StringRef s, u64& out) { if (s.empty()) return false; u64 value = 0; for (char c : s) { @@ -30,19 +31,19 @@ bool ParseU64(std::string_view s, u64& out) { return true; } -void SetBudget(MemoryConfig& config, std::string_view name, u64 bytes) { +void SetBudget(MemoryConfig& config, base::StringRef name, u64 bytes) { for (auto& budget : config.budgets) { if (budget.name == name) { budget.bytes = bytes; return; } } - config.budgets.push_back({std::string(name), bytes}); + config.budgets.push_back({base::String(name), bytes}); } } // namespace -MemoryConfig DefaultMemoryConfig(std::string_view preset) { +MemoryConfig DefaultMemoryConfig(base::StringRef preset) { MemoryConfig config; config.preset = preset; if (preset == "steamdeck") { @@ -65,28 +66,28 @@ MemoryConfig DefaultMemoryConfig(std::string_view preset) { return config; } -void ParseMemoryConfigText(std::string_view text, MemoryConfig& config) { - std::string section; +void ParseMemoryConfigText(base::StringRef text, MemoryConfig& config) { + base::String section; size_t start = 0; while (start <= text.size()) { const size_t end = text.find('\n', start); - std::string_view line = text.substr(start, end == std::string_view::npos ? end : end - start); - start = end == std::string_view::npos ? text.size() + 1 : end + 1; + base::StringRef line = text.substr(start, end == base::StringRef::npos ? end : end - start); + start = end == base::StringRef::npos ? text.size() + 1 : end + 1; - if (const size_t comment = line.find_first_of(";#"); comment != std::string_view::npos) { + if (const size_t comment = line.find_first_of(";#"); comment != base::StringRef::npos) { line = line.substr(0, comment); } line = Trim(line); if (line.empty()) continue; if (line.front() == '[' && line.back() == ']') { - section = std::string(Trim(line.substr(1, line.size() - 2))); + section = base::String(Trim(line.substr(1, line.size() - 2))); continue; } const size_t equals = line.find('='); - if (equals == std::string_view::npos) continue; - const std::string_view key = Trim(line.substr(0, equals)); + if (equals == base::StringRef::npos) continue; + const base::StringRef key = Trim(line.substr(0, equals)); u64 value = 0; if (!ParseU64(Trim(line.substr(equals + 1)), value)) continue; @@ -101,16 +102,12 @@ void ParseMemoryConfigText(std::string_view text, MemoryConfig& config) { } MemoryConfig LoadMemoryConfig() { - const char* preset = std::getenv("RX_MEMORY_PRESET"); + const char* preset = ::getenv("RX_MEMORY_PRESET"); MemoryConfig config = DefaultMemoryConfig(preset ? preset : "desktop"); - const char* path = std::getenv("RX_MEMORY_INI"); - std::ifstream file(path ? path : "memory.ini"); - if (file) { - std::ostringstream text; - text << file.rdbuf(); - ParseMemoryConfigText(text.str(), config); - } + const char* path = ::getenv("RX_MEMORY_INI"); + base::String text; + if (fs::ReadTextFile(path ? path : "memory.ini", &text)) ParseMemoryConfigText(text, config); return config; } diff --git a/engine/core/memory/memory_config.h b/engine/core/memory/memory_config.h index 5da6a5c4..35ecc93f 100644 --- a/engine/core/memory/memory_config.h +++ b/engine/core/memory/memory_config.h @@ -1,11 +1,11 @@ #ifndef RX_CORE_MEMORY_MEMORY_CONFIG_H_ #define RX_CORE_MEMORY_MEMORY_CONFIG_H_ -#include -#include -#include -#include +#include +#include "base/containers/vector.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/types.h" @@ -17,18 +17,18 @@ namespace rx::mem { // a memory.ini overlays them, so a shipped game can tune Steam Deck or mobile // footprints without recompiling. struct MemoryConfig { - std::string preset = "desktop"; + base::String preset = "desktop"; size_t frame_arena_bytes = 8u << 20; size_t ecs_chunk_reserve = 256; // 16 KiB chunks (4 MiB) struct Budget { - std::string name; + base::String name; u64 bytes = 0; }; - std::vector budgets; + base::Vector budgets; }; // Preset table: "desktop" (default), "steamdeck", "mobile". -RX_CORE_EXPORT MemoryConfig DefaultMemoryConfig(std::string_view preset); +RX_CORE_EXPORT MemoryConfig DefaultMemoryConfig(base::StringRef preset); // Overlays ini text onto `config`. Format: // preset is chosen before parsing (RX_MEMORY_PRESET); sections: @@ -36,7 +36,7 @@ RX_CORE_EXPORT MemoryConfig DefaultMemoryConfig(std::string_view preset); // [pools] ecs_chunks = 256 // [budgets] ecs = 64 ; MiB, one line per category // Unknown keys are ignored so configs stay forward-compatible. -RX_CORE_EXPORT void ParseMemoryConfigText(std::string_view text, MemoryConfig& config); +RX_CORE_EXPORT void ParseMemoryConfigText(base::StringRef text, MemoryConfig& config); // DefaultMemoryConfig(RX_MEMORY_PRESET or "desktop"), then overlays the file // named by RX_MEMORY_INI (falling back to ./memory.ini when present). diff --git a/engine/core/memory/memory_tracker.cc b/engine/core/memory/memory_tracker.cc index 410efd61..d125ee07 100644 --- a/engine/core/memory/memory_tracker.cc +++ b/engine/core/memory/memory_tracker.cc @@ -1,8 +1,9 @@ +#include "base/atomic.h" +#include "base/threading/lock_guard.h" +#include "base/threading/mutex.h" #include "core/memory/memory_tracker.h" -#include -#include -#include +#include namespace rx::mem { namespace { @@ -15,30 +16,30 @@ struct Slot { // (release/acquire), immutable afterwards; copied so ini-loaded names need // not outlive the call. char name[kMaxCategoryNameLength]{}; - std::atomic current{0}; - std::atomic peak{0}; - std::atomic budget{0}; - std::atomic allocs{0}; + base::Atomic current{0}; + base::Atomic peak{0}; + base::Atomic budget{0}; + base::Atomic allocs{0}; }; constinit Slot g_slots[kMaxCategories]{}; -constinit std::atomic g_count{1}; // slot 0 is kGeneralCategory -constinit std::atomic g_active{false}; -constinit std::mutex g_register_mutex; +constinit base::Atomic g_count{1}; // slot 0 is kGeneralCategory +constinit base::Atomic g_active{false}; +constinit base::Mutex g_register_mutex; thread_local constinit Category t_current{kGeneralCategory}; } // namespace Category RegisterCategory(const char* name) { - std::lock_guard lock(g_register_mutex); - const u32 count = g_count.load(std::memory_order_relaxed); + base::LockGuard lock(g_register_mutex); + const u32 count = g_count.load(base::memory_order_relaxed); for (u32 i = 1; i < count; ++i) { - if (std::strcmp(g_slots[i].name, name) == 0) return static_cast(i); + if (::strcmp(g_slots[i].name, name) == 0) return static_cast(i); } if (count >= kMaxCategories) return kGeneralCategory; - std::strncpy(g_slots[count].name, name, kMaxCategoryNameLength - 1); - g_count.store(count + 1, std::memory_order_release); + ::strncpy(g_slots[count].name, name, kMaxCategoryNameLength - 1); + g_count.store(count + 1, base::memory_order_release); return static_cast(count); } @@ -56,49 +57,49 @@ namespace detail { void TrackAlloc(Category category, size_t bytes) { Slot& slot = g_slots[category]; - const i64 current = slot.current.fetch_add(static_cast(bytes), std::memory_order_relaxed) + + const i64 current = slot.current.fetch_add(static_cast(bytes), base::memory_order_relaxed) + static_cast(bytes); - slot.allocs.fetch_add(1, std::memory_order_relaxed); - u64 peak = slot.peak.load(std::memory_order_relaxed); + slot.allocs.fetch_add(1, base::memory_order_relaxed); + u64 peak = slot.peak.load(base::memory_order_relaxed); while (current > 0 && static_cast(current) > peak && !slot.peak.compare_exchange_weak(peak, static_cast(current), - std::memory_order_relaxed)) { + base::memory_order_relaxed)) { } } void TrackFree(Category category, size_t bytes) { - g_slots[category].current.fetch_sub(static_cast(bytes), std::memory_order_relaxed); + g_slots[category].current.fetch_sub(static_cast(bytes), base::memory_order_relaxed); } } // namespace detail void SetCategoryBudget(const char* name, u64 bytes) { const Category category = RegisterCategory(name); - if (category == kGeneralCategory && std::strcmp(name, "") != 0) return; // table full - g_slots[category].budget.store(bytes, std::memory_order_relaxed); + if (category == kGeneralCategory && ::strcmp(name, "") != 0) return; // table full + g_slots[category].budget.store(bytes, base::memory_order_relaxed); } u32 SnapshotCategories(CategoryStats* out, u32 max) { - const u32 count = g_count.load(std::memory_order_acquire); + const u32 count = g_count.load(base::memory_order_acquire); u32 written = 0; for (u32 i = 0; i < count && written < max; ++i) { const Slot& slot = g_slots[i]; const char* name = i == kGeneralCategory ? "" : slot.name; out[written++] = CategoryStats{ .name = name, - .current_bytes = slot.current.load(std::memory_order_relaxed), - .peak_bytes = slot.peak.load(std::memory_order_relaxed), - .budget_bytes = slot.budget.load(std::memory_order_relaxed), - .alloc_count = slot.allocs.load(std::memory_order_relaxed), + .current_bytes = slot.current.load(base::memory_order_relaxed), + .peak_bytes = slot.peak.load(base::memory_order_relaxed), + .budget_bytes = slot.budget.load(base::memory_order_relaxed), + .alloc_count = slot.allocs.load(base::memory_order_relaxed), }; } return written; } -bool TrackingActive() { return g_active.load(std::memory_order_relaxed); } +bool TrackingActive() { return g_active.load(base::memory_order_relaxed); } namespace detail { -void MarkTrackingActive() { g_active.store(true, std::memory_order_relaxed); } +void MarkTrackingActive() { g_active.store(true, base::memory_order_relaxed); } } // namespace detail } // namespace rx::mem diff --git a/engine/core/memory/memory_tracker.h b/engine/core/memory/memory_tracker.h index 9ad789ef..5e72dde3 100644 --- a/engine/core/memory/memory_tracker.h +++ b/engine/core/memory/memory_tracker.h @@ -1,7 +1,7 @@ #ifndef RX_CORE_MEMORY_MEMORY_TRACKER_H_ #define RX_CORE_MEMORY_MEMORY_TRACKER_H_ -#include +#include #include "core/export.h" #include "core/types.h" diff --git a/engine/core/memory/new_override.cc b/engine/core/memory/new_override.cc index b61b2b91..ca536aec 100644 --- a/engine/core/memory/new_override.cc +++ b/engine/core/memory/new_override.cc @@ -14,12 +14,14 @@ #include -#include -#include -#include -#include +#include +#include +#include +#include #include +#include "base/memory/mem_ops.h" +#include "base/standard_streams.h" #include "core/memory/memory_tracker.h" namespace { @@ -31,35 +33,39 @@ struct TrackingMarker { } g_tracking_marker; struct AllocationFooter { - std::uintptr_t cookie; - std::uintptr_t inverse_cookie; - std::size_t usable_size; + uintptr_t cookie; + uintptr_t inverse_cookie; + size_t usable_size; rx::mem::Category category; }; -constexpr std::uintptr_t kFooterMagic = static_cast(0xd6e8feb86659fd93ULL); +constexpr uintptr_t kFooterMagic = static_cast(0xd6e8feb86659fd93ULL); -std::uintptr_t FooterCookie(void* pointer, std::size_t usable_size, +uintptr_t FooterCookie(void* pointer, size_t usable_size, rx::mem::Category category) { - return kFooterMagic ^ reinterpret_cast(pointer) ^ usable_size ^ category; + return kFooterMagic ^ reinterpret_cast(pointer) ^ usable_size ^ category; } -void* Allocate(std::size_t size, std::size_t alignment, bool aligned, bool nothrow) { - if (size > std::numeric_limits::max() - sizeof(AllocationFooter)) { +// Exceptions are not used, so the throwing forms of operator new end the +// process on exhaustion instead of raising bad_alloc; the nothrow forms return +// null as their contract says. +[[noreturn]] void OutOfMemory() { + static const char kMessage[] = "rx: out of memory in operator new\n"; + base::WriteStandardError(kMessage, sizeof(kMessage) - 1); + ::abort(); +} + +void* Allocate(size_t size, size_t alignment, bool aligned, bool nothrow) { + if (size > SIZE_MAX - sizeof(AllocationFooter)) { if (nothrow) return nullptr; - throw std::bad_alloc(); + OutOfMemory(); } - const std::size_t total = size + sizeof(AllocationFooter); - void* pointer = nullptr; - if (aligned) { - pointer = nothrow ? mi_new_aligned_nothrow(total, alignment) : mi_new_aligned(total, alignment); - } else { - pointer = nothrow ? mi_new_nothrow(total) : mi_new(total); - } + const size_t total = size + sizeof(AllocationFooter); + void* pointer = aligned ? mi_new_aligned_nothrow(total, alignment) : mi_new_nothrow(total); if (!pointer) { if (nothrow) return nullptr; - throw std::bad_alloc(); + OutOfMemory(); } // _FORTIFY_SOURCE sizes the block from mi_new's alloc_size attribute (the @@ -68,16 +74,16 @@ void* Allocate(std::size_t size, std::size_t alignment, bool aligned, bool nothr // provenance so __memcpy_chk cannot derive the too-small bound. __asm__("" : "+r"(pointer)); - const std::size_t usable_size = mi_usable_size(pointer); + const size_t usable_size = mi_usable_size(pointer); const rx::mem::Category category = rx::mem::CurrentCategory(); - const std::uintptr_t cookie = FooterCookie(pointer, usable_size, category); + const uintptr_t cookie = FooterCookie(pointer, usable_size, category); const AllocationFooter footer{ .cookie = cookie, .inverse_cookie = ~cookie, .usable_size = usable_size, .category = category, }; - std::memcpy(static_cast(pointer) + usable_size - sizeof(footer), &footer, + base::MemCopy(static_cast(pointer) + usable_size - sizeof(footer), &footer, sizeof(footer)); rx::mem::detail::TrackAlloc(category, usable_size); return pointer; @@ -85,17 +91,17 @@ void* Allocate(std::size_t size, std::size_t alignment, bool aligned, bool nothr void Deallocate(void* pointer) noexcept { if (!pointer) return; - const std::size_t usable_size = mi_usable_size(pointer); + const size_t usable_size = mi_usable_size(pointer); if (usable_size >= sizeof(AllocationFooter)) { AllocationFooter footer; - std::memcpy(&footer, static_cast(pointer) + usable_size - sizeof(footer), + base::MemCopy(&footer, static_cast(pointer) + usable_size - sizeof(footer), sizeof(footer)); - const std::uintptr_t expected = FooterCookie(pointer, usable_size, footer.category); + const uintptr_t expected = FooterCookie(pointer, usable_size, footer.category); if (footer.usable_size == usable_size && footer.cookie == expected && footer.inverse_cookie == ~expected && footer.category < rx::mem::kMaxCategories) { rx::mem::detail::TrackFree(footer.category, usable_size); const AllocationFooter cleared{}; - std::memcpy(static_cast(pointer) + usable_size - sizeof(cleared), &cleared, + base::MemCopy(static_cast(pointer) + usable_size - sizeof(cleared), &cleared, sizeof(cleared)); } } @@ -104,36 +110,36 @@ void Deallocate(void* pointer) noexcept { } // namespace -void* operator new(std::size_t n) { return Allocate(n, 0, false, false); } -void* operator new[](std::size_t n) { return Allocate(n, 0, false, false); } -void* operator new(std::size_t n, std::align_val_t align) { - return Allocate(n, static_cast(align), true, false); +void* operator new(size_t n) { return Allocate(n, 0, false, false); } +void* operator new[](size_t n) { return Allocate(n, 0, false, false); } +void* operator new(size_t n, std::align_val_t align) { + return Allocate(n, static_cast(align), true, false); } -void* operator new[](std::size_t n, std::align_val_t align) { - return Allocate(n, static_cast(align), true, false); +void* operator new[](size_t n, std::align_val_t align) { + return Allocate(n, static_cast(align), true, false); } -void* operator new(std::size_t n, const std::nothrow_t&) noexcept { +void* operator new(size_t n, const std::nothrow_t&) noexcept { return Allocate(n, 0, false, true); } -void* operator new[](std::size_t n, const std::nothrow_t&) noexcept { +void* operator new[](size_t n, const std::nothrow_t&) noexcept { return Allocate(n, 0, false, true); } -void* operator new(std::size_t n, std::align_val_t align, const std::nothrow_t&) noexcept { - return Allocate(n, static_cast(align), true, true); +void* operator new(size_t n, std::align_val_t align, const std::nothrow_t&) noexcept { + return Allocate(n, static_cast(align), true, true); } -void* operator new[](std::size_t n, std::align_val_t align, const std::nothrow_t&) noexcept { - return Allocate(n, static_cast(align), true, true); +void* operator new[](size_t n, std::align_val_t align, const std::nothrow_t&) noexcept { + return Allocate(n, static_cast(align), true, true); } void operator delete(void* p) noexcept { Deallocate(p); } void operator delete[](void* p) noexcept { Deallocate(p); } -void operator delete(void* p, std::size_t) noexcept { Deallocate(p); } -void operator delete[](void* p, std::size_t) noexcept { Deallocate(p); } +void operator delete(void* p, size_t) noexcept { Deallocate(p); } +void operator delete[](void* p, size_t) noexcept { Deallocate(p); } void operator delete(void* p, std::align_val_t) noexcept { Deallocate(p); } void operator delete[](void* p, std::align_val_t) noexcept { Deallocate(p); } -void operator delete(void* p, std::size_t, std::align_val_t) noexcept { Deallocate(p); } -void operator delete[](void* p, std::size_t, std::align_val_t) noexcept { Deallocate(p); } +void operator delete(void* p, size_t, std::align_val_t) noexcept { Deallocate(p); } +void operator delete[](void* p, size_t, std::align_val_t) noexcept { Deallocate(p); } void operator delete(void* p, const std::nothrow_t&) noexcept { Deallocate(p); } void operator delete[](void* p, const std::nothrow_t&) noexcept { Deallocate(p); } void operator delete(void* p, std::align_val_t, const std::nothrow_t&) noexcept { diff --git a/engine/core/memory/small_vector.h b/engine/core/memory/small_vector.h index 50504b4b..8c23dba8 100644 --- a/engine/core/memory/small_vector.h +++ b/engine/core/memory/small_vector.h @@ -1,12 +1,16 @@ #ifndef RX_CORE_MEMORY_SMALL_VECTOR_H_ #define RX_CORE_MEMORY_SMALL_VECTOR_H_ -#include -#include +#include +#include + +// is core language: placement new and std::align_val_t, the tag the +// aligned operator new is declared with. #include -#include -#include +#include "base/check.h" +#include "base/memory/move.h" +#include "base/meta/traits.h" #include "core/types.h" namespace rx::mem { @@ -24,14 +28,14 @@ class SmallVector { public: SmallVector() = default; - SmallVector(SmallVector&& other) noexcept(std::is_nothrow_move_constructible_v) { - MoveFrom(std::move(other)); + SmallVector(SmallVector&& other) noexcept { + MoveFrom(base::move(other)); } - SmallVector& operator=(SmallVector&& other) noexcept(std::is_nothrow_move_constructible_v) { + SmallVector& operator=(SmallVector&& other) noexcept { if (this != &other) { Destroy(); - MoveFrom(std::move(other)); + MoveFrom(base::move(other)); } return *this; } @@ -60,13 +64,13 @@ class SmallVector { if (capacity > capacity_) Grow(capacity); } - T& push_back(T value) { return emplace_back(std::move(value)); } + T& push_back(T value) { return emplace_back(base::move(value)); } template T& emplace_back(Args&&... args) { - if (size_ == capacity_) return GrowAndEmplace(std::forward(args)...); + if (size_ == capacity_) return GrowAndEmplace(base::forward(args)...); T* slot = data_ + size_; - new (slot) T(std::forward(args)...); + new (slot) T(base::forward(args)...); ++size_; return *slot; } @@ -91,11 +95,11 @@ class SmallVector { bool IsInline() const { return data_ == reinterpret_cast(inline_storage_); } static constexpr size_t MaxCapacity() { - return std::numeric_limits::max() / sizeof(T); + return SIZE_MAX / sizeof(T); } static T* Allocate(size_t capacity) { - if (capacity > MaxCapacity()) throw std::bad_array_new_length(); + BASE_FATAL_CHECK(capacity <= MaxCapacity(), "SmallVector: capacity overflows size_t"); const size_t bytes = capacity * sizeof(T); if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { return static_cast(::operator new(bytes, std::align_val_t{alignof(T)})); @@ -113,7 +117,7 @@ class SmallVector { } size_t GrowthCapacity(size_t requested) const { - if (requested > MaxCapacity()) throw std::bad_array_new_length(); + BASE_FATAL_CHECK(requested <= MaxCapacity(), "SmallVector: capacity overflows size_t"); const size_t doubled = capacity_ > MaxCapacity() / 2 ? MaxCapacity() : capacity_ * 2; return requested < doubled ? doubled : requested; } @@ -121,16 +125,7 @@ class SmallVector { void Grow(size_t new_capacity) { new_capacity = GrowthCapacity(new_capacity); T* block = Allocate(new_capacity); - size_t constructed = 0; - try { - for (; constructed < size_; ++constructed) { - new (block + constructed) T(std::move_if_noexcept(data_[constructed])); - } - } catch (...) { - for (size_t i = 0; i < constructed; ++i) block[i].~T(); - Deallocate(block); - throw; - } + for (size_t i = 0; i < size_; ++i) new (block + i) T(base::move(data_[i])); for (size_t i = 0; i < size_; ++i) data_[i].~T(); if (!IsInline()) Deallocate(data_); data_ = block; @@ -139,29 +134,14 @@ class SmallVector { template T& GrowAndEmplace(Args&&... args) { - if (capacity_ == MaxCapacity()) throw std::bad_array_new_length(); + BASE_FATAL_CHECK(capacity_ < MaxCapacity(), "SmallVector: capacity overflows size_t"); const size_t new_capacity = GrowthCapacity(capacity_ + 1); T* block = Allocate(new_capacity); // Construct the appended value while aliased arguments still refer to the // old storage, then relocate the existing prefix. - try { - new (block + size_) T(std::forward(args)...); - } catch (...) { - Deallocate(block); - throw; - } - size_t constructed = 0; - try { - for (; constructed < size_; ++constructed) { - new (block + constructed) T(std::move_if_noexcept(data_[constructed])); - } - } catch (...) { - for (size_t i = 0; i < constructed; ++i) block[i].~T(); - block[size_].~T(); - Deallocate(block); - throw; - } + new (block + size_) T(base::forward(args)...); + for (size_t i = 0; i < size_; ++i) new (block + i) T(base::move(data_[i])); for (size_t i = 0; i < size_; ++i) data_[i].~T(); if (!IsInline()) Deallocate(data_); @@ -175,13 +155,8 @@ class SmallVector { data_ = reinterpret_cast(inline_storage_); capacity_ = N; size_ = 0; - try { - for (; size_ < other.size_; ++size_) { - new (data_ + size_) T(std::move(other.data_[size_])); - } - } catch (...) { - clear(); - throw; + for (; size_ < other.size_; ++size_) { + new (data_ + size_) T(base::move(other.data_[size_])); } for (size_t i = 0; i < other.size_; ++i) other.data_[i].~T(); other.size_ = 0; diff --git a/engine/core/move_only_function.h b/engine/core/move_only_function.h deleted file mode 100644 index 70746beb..00000000 --- a/engine/core/move_only_function.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef RX_CORE_MOVE_ONLY_FUNCTION_H_ -#define RX_CORE_MOVE_ONLY_FUNCTION_H_ - -#include - -// rx::MoveOnlyFunction is std::move_only_function where the standard -// library provides it (libstdc++), and a minimal stand-in where it does not -// yet (the NDK's libc++, as of LLVM 18). The stand-in matches the subset the -// engine uses: construct from a move-only callable, move (not copy), invoke. -#if defined(__cpp_lib_move_only_function) && __cpp_lib_move_only_function >= 202110L - -#include - -namespace rx { -template -using MoveOnlyFunction = std::move_only_function; -} // namespace rx - -#else - -#include -#include -#include -#include - -namespace rx { - -template -class MoveOnlyFunction; - -template -class MoveOnlyFunction { - public: - MoveOnlyFunction() noexcept = default; - MoveOnlyFunction(std::nullptr_t) noexcept {} - - template , - typename = std::enable_if_t && - std::is_invocable_r_v>> - MoveOnlyFunction(F&& f) : holder_(std::make_unique>(std::forward(f))) {} - - MoveOnlyFunction(MoveOnlyFunction&&) noexcept = default; - MoveOnlyFunction& operator=(MoveOnlyFunction&&) noexcept = default; - MoveOnlyFunction(const MoveOnlyFunction&) = delete; - MoveOnlyFunction& operator=(const MoveOnlyFunction&) = delete; - - explicit operator bool() const noexcept { return holder_ != nullptr; } - - R operator()(Args... args) { return holder_->Call(std::forward(args)...); } - - private: - struct HolderBase { - virtual ~HolderBase() = default; - virtual R Call(Args... args) = 0; - }; - template - struct Holder final : HolderBase { - template - explicit Holder(G&& g) : fn(std::forward(g)) {} - R Call(Args... args) override { return fn(std::forward(args)...); } - F fn; - }; - - std::unique_ptr holder_; -}; - -} // namespace rx - -#endif // __cpp_lib_move_only_function - -#endif // RX_CORE_MOVE_ONLY_FUNCTION_H_ diff --git a/engine/core/paths.cc b/engine/core/paths.cc index 6a88d69d..7182a39a 100644 --- a/engine/core/paths.cc +++ b/engine/core/paths.cc @@ -1,11 +1,14 @@ #include "core/paths.h" -#include - +#include "base/containers/array.h" +#include "base/strings/string_ref.h" +#include "core/file_system.h" #include "core/types.h" #if defined(_WIN32) #include + +#include "base/text/code_convert.h" #elif defined(__APPLE__) #include #else @@ -14,25 +17,32 @@ namespace rx { -std::filesystem::path ExecutableDirectory() { +base::String ExecutableDirectory() { #if defined(_WIN32) - std::array path{}; + base::Array path{}; const DWORD size = GetModuleFileNameW(nullptr, path.data(), static_cast(path.size())); - if (size > 0 && size < path.size()) - return std::filesystem::path(std::wstring_view(path.data(), size)).parent_path(); + if (size > 0 && size < path.size()) { + const base::StringU8 utf8 = base::WideToUTF8(base::StringRefW(path.data(), size)); + const base::StringRef full(reinterpret_cast(utf8.data()), utf8.size()); + return fs::GenericString(fs::ParentPath(full)); + } #elif defined(__APPLE__) - std::array path{}; + base::Array path{}; u32 size = static_cast(path.size()); - if (_NSGetExecutablePath(path.data(), &size) == 0) - return std::filesystem::weakly_canonical(path.data()).parent_path(); + if (_NSGetExecutablePath(path.data(), &size) == 0) { + const base::String canonical = fs::WeaklyCanonical(path.data()); + const base::StringRef parent = fs::ParentPath(base::StringRef(canonical.data(), canonical.size())); + return base::String(parent.data(), parent.size()); + } #else - std::array path{}; + base::Array path{}; const ssize_t size = readlink("/proc/self/exe", path.data(), path.size() - 1); - if (size > 0) return std::filesystem::path(std::string_view(path.data(), size)).parent_path(); + if (size > 0) { + const base::StringRef parent = fs::ParentPath(base::StringRef(path.data(), size)); + return base::String(parent.data(), parent.size()); + } #endif - std::error_code ec; - std::filesystem::path cwd = std::filesystem::current_path(ec); - return ec ? std::filesystem::path(".") : cwd; + return fs::CurrentPath(); } } // namespace rx diff --git a/engine/core/paths.h b/engine/core/paths.h index 23dc239b..abaa6ef4 100644 --- a/engine/core/paths.h +++ b/engine/core/paths.h @@ -1,7 +1,7 @@ #ifndef RX_CORE_PATHS_H_ #define RX_CORE_PATHS_H_ -#include +#include "base/strings/xstring.h" #include "core/export.h" @@ -12,7 +12,7 @@ namespace rx { // carries next to itself: the working directory is whatever the shell or the // launcher happened to be in, and the build-tree paths baked in at compile time // exist only on the machine that did the build. -RX_CORE_EXPORT std::filesystem::path ExecutableDirectory(); +RX_CORE_EXPORT base::String ExecutableDirectory(); } // namespace rx diff --git a/engine/core/scalar.h b/engine/core/scalar.h new file mode 100644 index 00000000..0f5e0123 --- /dev/null +++ b/engine/core/scalar.h @@ -0,0 +1,75 @@ +#ifndef RX_CORE_SCALAR_H_ +#define RX_CORE_SCALAR_H_ + +#include + +namespace rx { + +// Min/Max/Clamp/Lerp with the exact comparison order of std::min, std::max, +// std::clamp and std::lerp, which is what this engine was tuned and captured +// against. base::Min/Max/Clamp compare the other way round, so they disagree +// on NaN (base::Clamp maps NaN to hi, std::clamp passes it through) and on +// which zero wins between -0 and +0. Swapping one for the other is not a +// no-op for the picture or for the physics NaN guards. + +// std::min: the second argument only when it is strictly smaller. +template +constexpr T Min(T a, T b) { + return b < a ? b : a; +} + +// std::max: the second argument only when the first is strictly smaller. +template +constexpr T Max(T a, T b) { + return a < b ? b : a; +} + +// std::min({...}): the first of the smallest. +template +constexpr T Min(std::initializer_list values) { + const T* it = values.begin(); + T result = *it; + for (++it; it != values.end(); ++it) { + if (*it < result) result = *it; + } + return result; +} + +// std::max({...}): the first of the largest. +template +constexpr T Max(std::initializer_list values) { + const T* it = values.begin(); + T result = *it; + for (++it; it != values.end(); ++it) { + if (result < *it) result = *it; + } + return result; +} + +// std::clamp as libstdc++ spells it: NaN in, NaN out, and hi wins when a +// caller passes lo > hi (an empty range clamped to count - 1, say). +template +constexpr T Clamp(T v, T lo, T hi) { + return Min(Max(v, lo), hi); +} + +// std::lerp for floating point: exact at both ends, monotonic in t, and +// bounded by b past t == 1, where the naive a + t * (b - a) is none of those. +template +constexpr T Lerp(T a, T b, T t) { + if ((a <= 0 && b >= 0) || (a >= 0 && b <= 0)) return t * b + (1 - t) * a; + if (t == 1) return b; + const T x = a + t * (b - a); + return (t > 1) == (b > a) ? (b < x ? x : b) : (x < b ? x : b); +} + +// std::bit_cast, on the builtin every supported compiler has. +template +constexpr To BitCast(const From& from) { + static_assert(sizeof(To) == sizeof(From), "BitCast needs equal sizes"); + return __builtin_bit_cast(To, from); +} + +} // namespace rx + +#endif // RX_CORE_SCALAR_H_ diff --git a/engine/core/shared.h b/engine/core/shared.h new file mode 100644 index 00000000..9d58abec --- /dev/null +++ b/engine/core/shared.h @@ -0,0 +1,62 @@ +#ifndef RX_CORE_SHARED_H_ +#define RX_CORE_SHARED_H_ + +#include "base/atomic.h" +#include "base/memory/move.h" +#include "core/types.h" + +namespace rx { + +// Shared ownership of one heap object: copies share it and the last one to go +// destroys it. base has no shared pointer; the engine needs one only where two +// lifetimes cross and either may end first (a synth voice's parameter mailbox, +// shared by the engine thread and the audio thread; a demo's alive flag, held +// by closures a scheduler keeps after the demo is gone). +// +// Counting follows shared_ptr: an acquire is relaxed (a holder already +// exists), a release is acq_rel (every use through any holder happens before +// the delete). The object itself is not synchronized. +template +class Shared { + public: + Shared() = default; + + template + static Shared Make(Args&&... args) { + return Shared(new Block(base::forward(args)...)); + } + + Shared(const Shared& other) : block_(other.block_) { + if (block_) block_->refs.fetch_add(1, base::memory_order_relaxed); + } + Shared(Shared&& other) noexcept : block_(other.block_) { other.block_ = nullptr; } + Shared& operator=(Shared other) noexcept { + Block* old = block_; + block_ = other.block_; + other.block_ = old; // released when `other` goes out of scope + return *this; + } + ~Shared() { + if (block_ && block_->refs.fetch_sub(1, base::memory_order_acq_rel) == 1) delete block_; + } + + T* operator->() const { return &block_->value; } + T& operator*() const { return block_->value; } + explicit operator bool() const { return block_ != nullptr; } + + private: + struct Block { + template + explicit Block(Args&&... args) : value(base::forward(args)...) {} + base::Atomic refs{1}; + T value; + }; + + explicit Shared(Block* block) : block_(block) {} + + Block* block_ = nullptr; +}; + +} // namespace rx + +#endif // RX_CORE_SHARED_H_ diff --git a/engine/core/sort.h b/engine/core/sort.h new file mode 100644 index 00000000..5a01d613 --- /dev/null +++ b/engine/core/sort.h @@ -0,0 +1,74 @@ +#ifndef RX_CORE_SORT_H_ +#define RX_CORE_SORT_H_ + +#include "base/algorithm.h" +#include "base/containers/vector.h" +#include "base/memory/move.h" +#include "core/types.h" + +namespace rx { + +// std::stable_sort: equal elements keep their input order. base::Sort breaks +// ties by its own rules; where the order of equal elements reaches output (draw +// order, which lights win a cut), keeping input order makes the result a +// function of the input alone rather than of any one sort algorithm. +template +void StableSort(T* first, T* last, Less less) { + const mem_size n = static_cast(last - first); + if (n < 2) return; + base::Vector order(n); + for (mem_size i = 0; i < n; ++i) order[i] = static_cast(i); + // Position breaks every tie, so any correct sort of the indices agrees. + base::Sort(order.data(), order.data() + n, [&](u32 a, u32 b) { + if (less(first[a], first[b])) return true; + if (less(first[b], first[a])) return false; + return a < b; + }); + base::Vector sorted; + sorted.reserve(n); + for (u32 index : order) sorted.push_back(base::move(first[index])); + for (mem_size i = 0; i < n; ++i) first[i] = base::move(sorted[i]); +} + +// std::nth_element: *nth ends up where a full sort would put it, nothing after +// it is less, nothing before it greater. Callers that need a reproducible +// split must hand in a total order (ties broken by a unique id): then which +// elements land on each side is fixed by the input, whatever the algorithm. +template +void NthElement(T* first, T* nth, T* last, Less less) { + while (last - first > 16) { + // Median of three moved to the front as the pivot. + T* mid = first + (last - first) / 2; + T* back = last - 1; + if (less(*mid, *first)) base::Swap(*mid, *first); + if (less(*back, *first)) base::Swap(*back, *first); + if (less(*back, *mid)) base::Swap(*back, *mid); + base::Swap(*first, *mid); + // Hoare partition around *first. + T* lo = first + 1; + T* hi = last - 1; + for (;;) { + while (lo <= hi && less(*lo, *first)) ++lo; + while (lo <= hi && less(*first, *hi)) --hi; + if (lo >= hi) break; + base::Swap(*lo, *hi); + ++lo; + --hi; + } + base::Swap(*first, *hi); + if (hi == nth) return; + if (nth < hi) { + last = hi; + } else { + first = hi + 1; + } + } + // Short ranges: insertion sort, which leaves every position final. + for (T* i = first + 1; i < last; ++i) { + for (T* j = i; j > first && less(*j, *(j - 1)); --j) base::Swap(*j, *(j - 1)); + } +} + +} // namespace rx + +#endif // RX_CORE_SORT_H_ diff --git a/engine/core/text_reader.cc b/engine/core/text_reader.cc new file mode 100644 index 00000000..4afd21b9 --- /dev/null +++ b/engine/core/text_reader.cc @@ -0,0 +1,42 @@ +#include "core/text_reader.h" + +#include +#include +#if defined(__APPLE__) +#include +#endif + +namespace rx { +namespace { + +#if defined(_WIN32) +_locale_t CLocale() { + static const _locale_t locale = _create_locale(LC_NUMERIC, "C"); + return locale; +} +#else +locale_t CLocale() { + static const locale_t locale = newlocale(LC_NUMERIC_MASK, "C", static_cast(0)); + return locale; +} +#endif + +} // namespace + +f32 StrToF32(const char* text, char** end) { +#if defined(_WIN32) + return _strtof_l(text, end, CLocale()); +#else + return strtof_l(text, end, CLocale()); +#endif +} + +f64 StrToF64(const char* text, char** end) { +#if defined(_WIN32) + return _strtod_l(text, end, CLocale()); +#else + return strtod_l(text, end, CLocale()); +#endif +} + +} // namespace rx diff --git a/engine/core/text_reader.h b/engine/core/text_reader.h new file mode 100644 index 00000000..dfa12239 --- /dev/null +++ b/engine/core/text_reader.h @@ -0,0 +1,206 @@ +#ifndef RX_CORE_TEXT_READER_H_ +#define RX_CORE_TEXT_READER_H_ + +#include +#include +#include + +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/export.h" +#include "core/types.h" + +namespace rx { + +// strtof / strtod in the "C" locale whatever setlocale says, so a host that +// localizes the process (a comma decimal point) cannot change how engine text +// parses. The std code this replaced was immune the same way: from_chars +// ignores locales, and istream parsed through the C++ locale, which setlocale +// does not touch. +RX_CORE_EXPORT f32 StrToF32(const char* text, char** end); +RX_CORE_EXPORT f64 StrToF64(const char* text, char** end); + +// std::getline over text already in memory: yields the pieces between +// delimiters, keeps a '\r' before '\n', and yields no empty piece after a +// final delimiter. The text must outlive the reader. +class LineReader { + public: + explicit LineReader(base::StringRef text, char delimiter = '\n') + : text_(text), delimiter_(delimiter) {} + + bool Next(base::StringRef* line) { + if (pos_ >= text_.size()) return false; + mem_size end = pos_; + while (end < text_.size() && text_.data()[end] != delimiter_) ++end; + *line = base::StringRef(text_.data() + pos_, end - pos_); + pos_ = end + 1; + return true; + } + + // Offset of the first byte not yet returned, for a format that switches + // from text lines to binary (a PLY header, say). + mem_size position() const { return pos_ < text_.size() ? pos_ : text_.size(); } + + private: + base::StringRef text_; + char delimiter_; + mem_size pos_ = 0; +}; + +// operator>> on an istringstream: whitespace-separated tokens, numbers parsed +// with the strto* functions libstdc++'s num_get ends in. A failed extraction +// zeroes its output and fails every later one, like failbit. The text must +// outlive the reader. +class TokenReader { + public: + explicit TokenReader(base::StringRef text) : text_(text) {} + + bool Next(base::StringRef* token) { + if (failed_) return false; + while (pos_ < text_.size() && IsSpace(text_.data()[pos_])) ++pos_; + if (pos_ == text_.size()) return Fail(); + const mem_size start = pos_; + while (pos_ < text_.size() && !IsSpace(text_.data()[pos_])) ++pos_; + *token = base::StringRef(text_.data() + start, pos_ - start); + return true; + } + + bool Next(base::String* token) { + base::StringRef piece; + if (!Next(&piece)) return false; + *token = base::String(piece.data(), piece.size()); + return true; + } + + bool Next(f32* value) { + return Parse(value, [](const char* s, char** end) { return StrToF32(s, end); }, + /*allow_underflow=*/true); + } + bool Next(f64* value) { + return Parse(value, [](const char* s, char** end) { return StrToF64(s, end); }, + /*allow_underflow=*/true); + } + bool Next(i32* value) { return ParseInt(value, -2147483647 - 1, 2147483647); } + bool Next(u32* value) { return ParseUnsigned(value, 0xffffffffull); } + bool Next(i64* value) { + return Parse(value, [](const char* s, char** end) { return ::strtoll(s, end, 10); }); + } + bool Next(u64* value) { return ParseUnsigned(value, ~0ull); } + + // Whatever is left after the last extraction, leading whitespace included, + // as getline(stream, rest) would return it. + base::StringRef Rest() const { + return base::StringRef(text_.data() + pos_, text_.size() - pos_); + } + + bool ok() const { return !failed_; } + explicit operator bool() const { return !failed_; } + + private: + static bool IsSpace(char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; + } + + bool Fail() { + failed_ = true; + return false; + } + + // num_get accepts a float that underflows (a denormal, or zero) and fails + // one that overflows; for integers any ERANGE fails. + template + bool Parse(T* value, F parse, bool allow_underflow = false) { + base::StringRef token; + if (!Next(&token)) { + *value = 0; + return false; + } + const base::String z(token.data(), token.size()); + char* end = nullptr; + errno = 0; + const auto parsed = parse(z.c_str(), &end); + bool overflow = errno == ERANGE; + if (overflow && allow_underflow) overflow = ::fabs(static_cast(parsed)) > 1.0; + if (end == z.c_str() || overflow) { + *value = 0; + return Fail(); + } + // A partial parse ("1.5x") gives back the unparsed tail for the next + // extraction, as the stream would leave it unread. + pos_ -= token.size() - static_cast(end - z.c_str()); + *value = static_cast(parsed); + return true; + } + + template + bool ParseInt(T* value, i64 lo, i64 hi) { + i64 wide = 0; + if (!Parse(&wide, [](const char* s, char** end) { return ::strtoll(s, end, 10); })) { + *value = 0; + return false; + } + if (wide < lo || wide > hi) { + *value = 0; + return Fail(); + } + *value = static_cast(wide); + return true; + } + + template + bool ParseUnsigned(T* value, u64 hi) { + u64 wide = 0; + if (!Parse(&wide, [](const char* s, char** end) { return ::strtoull(s, end, 10); })) { + *value = 0; + return false; + } + if (wide > hi) { + *value = 0; + return Fail(); + } + *value = static_cast(wide); + return true; + } + + base::StringRef text_; + mem_size pos_ = 0; + bool failed_ = false; +}; + +// std::from_chars over the whole of |text|: nothing before or after the +// number, no '+', no hex. The float goes through strtof, not a double, so it +// is rounded once, to float, as from_chars rounds it. +inline bool ParseWholeF32(base::StringRef text, f32* out) { + if (text.empty()) return false; + const char c = text[0]; + if (!(c == '-' || c == '.' || (c >= '0' && c <= '9') || c == 'i' || c == 'I' || c == 'n' || + c == 'N')) { + return false; + } + for (char ch : text) { + if (ch == 'x' || ch == 'X') return false; + } + const base::String z(text.data(), text.size()); + char* end = nullptr; + errno = 0; + const f32 parsed = StrToF32(z.c_str(), &end); + if (end != z.c_str() + z.size() || errno == ERANGE) return false; + *out = parsed; + return true; +} + +inline bool ParseWholeU32(base::StringRef text, u32* out) { + if (text.empty()) return false; + u64 value = 0; + for (char ch : text) { + if (ch < '0' || ch > '9') return false; + value = value * 10 + static_cast(ch - '0'); + if (value > 0xffffffffull) return false; + } + *out = static_cast(value); + return true; +} + +} // namespace rx + +#endif // RX_CORE_TEXT_READER_H_ diff --git a/engine/core/text_writer.h b/engine/core/text_writer.h new file mode 100644 index 00000000..a530b41f --- /dev/null +++ b/engine/core/text_writer.h @@ -0,0 +1,63 @@ +#ifndef RX_CORE_TEXT_WRITER_H_ +#define RX_CORE_TEXT_WRITER_H_ + +#include "base/memory/move.h" +#include "base/strings/format.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/types.h" + +namespace rx { + +// Appends text the way an ostream with default flags writes it, so a writer +// that used to stream into a file produces the same bytes: bool as 1/0, char +// and u8 as characters, integers in decimal, floating point as printf's %g +// with six significant digits. No manipulators; nothing here used them. +class TextWriter { + public: + TextWriter& operator<<(base::StringRef s) { + out_.append(s.data(), s.size()); + return *this; + } + TextWriter& operator<<(const char* s) { + out_.append(s); + return *this; + } + TextWriter& operator<<(const base::String& s) { + out_.append(s); + return *this; + } + TextWriter& operator<<(char c) { + out_.push_back(c); + return *this; + } + TextWriter& operator<<(signed char c) { return *this << static_cast(c); } + TextWriter& operator<<(unsigned char c) { return *this << static_cast(c); } + TextWriter& operator<<(bool b) { return *this << (b ? '1' : '0'); } + TextWriter& operator<<(short v) { return Number(v); } + TextWriter& operator<<(unsigned short v) { return Number(v); } + TextWriter& operator<<(int v) { return Number(v); } + TextWriter& operator<<(unsigned int v) { return Number(v); } + TextWriter& operator<<(long v) { return Number(v); } + TextWriter& operator<<(unsigned long v) { return Number(v); } + TextWriter& operator<<(long long v) { return Number(v); } + TextWriter& operator<<(unsigned long long v) { return Number(v); } + TextWriter& operator<<(float v) { return Number(static_cast(v)); } + TextWriter& operator<<(double v) { return Number(v); } + + const base::String& str() const { return out_; } + base::String Take() { return base::move(out_); } + + private: + template + TextWriter& Number(T v) { + base::FormatTo(out_, "{}", v); + return *this; + } + + base::String out_; +}; + +} // namespace rx + +#endif // RX_CORE_TEXT_WRITER_H_ diff --git a/engine/core/types.h b/engine/core/types.h index 6fda131f..1d1a0ea2 100644 --- a/engine/core/types.h +++ b/engine/core/types.h @@ -1,28 +1,30 @@ #ifndef RX_CORE_TYPES_H_ #define RX_CORE_TYPES_H_ -#include -#include -#include -#include -#include +#include "base/arch.h" +#include "base/containers/span.h" +#include "base/strings/string_ref.h" namespace rx { -using i8 = std::int8_t; -using i16 = std::int16_t; -using i32 = std::int32_t; -using i64 = std::int64_t; -using u8 = std::uint8_t; -using u16 = std::uint16_t; -using u32 = std::uint32_t; -using u64 = std::uint64_t; -using f32 = float; -using f64 = double; - -using ByteSpan = std::span; - -constexpr u64 Fnv1a(std::string_view str) { +// base's fixed-width types. u64 is unsigned long long there, not the +// unsigned long uint64_t is on LP64: same width, but a distinct type for +// overloads, pointer parameters and printf, so C APIs taking uint64_t* need +// their own variable. +using arch_types::f32; +using arch_types::f64; +using arch_types::i16; +using arch_types::i32; +using arch_types::i64; +using arch_types::i8; +using arch_types::u16; +using arch_types::u32; +using arch_types::u64; +using arch_types::u8; + +using ByteSpan = base::Span; + +inline u64 Fnv1a(base::StringRef str) { u64 hash = 0xcbf29ce484222325ull; for (char c : str) { hash ^= static_cast(c); diff --git a/engine/core/wayland_kde_hdr.cc b/engine/core/wayland_kde_hdr.cc index d4d8701f..18bbec9f 100644 --- a/engine/core/wayland_kde_hdr.cc +++ b/engine/core/wayland_kde_hdr.cc @@ -3,10 +3,13 @@ #include #include -#include #include +#include "base/algorithm.h" +#include "base/containers/vector.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" #include "core/log.h" #include "core/types.h" #include "kde-output-device-v2-client-protocol.h" @@ -86,18 +89,18 @@ struct KdeOutputHdrMonitor::Impl { wl_display* display = nullptr; wl_registry* registry = nullptr; // unique_ptr so the listener user-data pointer stays stable across growth. - std::vector> devices; + base::Vector> devices; static void RegistryGlobal(void* data, wl_registry* registry, u32 id, const char* interface, u32 version) { Impl* impl = static_cast(data); if (strcmp(interface, kde_output_device_v2_interface.name) != 0) return; - auto device = std::make_unique(); + auto device = base::MakeUnique(); u32 bind_version = version < 23u ? version : 23u; // what the vendored glue supports device->proxy = static_cast( wl_registry_bind(registry, id, &kde_output_device_v2_interface, bind_version)); - kde_output_device_v2_add_listener(device->proxy, &kDeviceListener, device.get()); - impl->devices.push_back(std::move(device)); + kde_output_device_v2_add_listener(device->proxy, &kDeviceListener, device.Get_UseOnlyIfYouKnowWhatYouareDoing()); + impl->devices.push_back(base::move(device)); } static void RegistryGlobalRemove(void*, wl_registry*, u32) {} @@ -110,13 +113,13 @@ struct KdeOutputHdrMonitor::Impl { } }; -std::unique_ptr KdeOutputHdrMonitor::Create() { +base::UniquePointer KdeOutputHdrMonitor::Create() { wl_display* display = wl_display_connect(nullptr); if (!display) return nullptr; - auto monitor = std::unique_ptr(new KdeOutputHdrMonitor()); - monitor->impl_ = std::make_unique(); - Impl* impl = monitor->impl_.get(); + auto monitor = base::UniquePointer(new KdeOutputHdrMonitor()); + monitor->impl_ = base::MakeUnique(); + Impl* impl = monitor->impl_.Get_UseOnlyIfYouKnowWhatYouareDoing(); impl->display = display; impl->registry = wl_display_get_registry(display); static constexpr wl_registry_listener kRegistryListener = {Impl::RegistryGlobal, @@ -133,7 +136,7 @@ std::unique_ptr KdeOutputHdrMonitor::Create() { KdeOutputHdrMonitor::~KdeOutputHdrMonitor() = default; bool KdeOutputHdrMonitor::AnyHdrEnabled() { - Impl* impl = impl_.get(); + Impl* impl = impl_.Get_UseOnlyIfYouKnowWhatYouareDoing(); // Non-blocking pump: only read the socket when data is already waiting, so a // per-frame poll never stalls on the compositor. while (wl_display_prepare_read(impl->display) != 0) { @@ -148,7 +151,7 @@ bool KdeOutputHdrMonitor::AnyHdrEnabled() { } wl_display_dispatch_pending(impl->display); - std::erase_if(impl->devices, [](const std::unique_ptr& device) { + base::EraseIf(impl->devices, [](const base::UniquePointer& device) { if (!device->removed) return false; kde_output_device_v2_destroy(device->proxy); return true; diff --git a/engine/core/wayland_kde_hdr.h b/engine/core/wayland_kde_hdr.h index 57781b72..8fa74384 100644 --- a/engine/core/wayland_kde_hdr.h +++ b/engine/core/wayland_kde_hdr.h @@ -1,7 +1,8 @@ #ifndef RX_CORE_WAYLAND_KDE_HDR_H_ #define RX_CORE_WAYLAND_KDE_HDR_H_ -#include +#include "base/memory/unique_pointer.h" + namespace rx { @@ -17,7 +18,7 @@ class KdeOutputHdrMonitor { // Connects and takes the initial output snapshot. Returns null when there is // no wayland display or the compositor is not KWin (no kde_output_device_v2 // globals) - callers then fall back to SDL's property. - static std::unique_ptr Create(); + static base::UniquePointer Create(); ~KdeOutputHdrMonitor(); KdeOutputHdrMonitor(const KdeOutputHdrMonitor&) = delete; @@ -33,7 +34,7 @@ class KdeOutputHdrMonitor { KdeOutputHdrMonitor() = default; struct Impl; - std::unique_ptr impl_; + base::UniquePointer impl_; }; } // namespace rx diff --git a/engine/core/window.cc b/engine/core/window.cc index 1bd59b3e..d2a10ccd 100644 --- a/engine/core/window.cc +++ b/engine/core/window.cc @@ -1,5 +1,6 @@ #include "core/window.h" +#include "base/memory/unique_pointer.h" #include "core/log.h" namespace rx { @@ -24,15 +25,15 @@ class HeadlessWindow final : public Window { } // namespace #if defined(RX_HAS_SDL3) -std::unique_ptr CreateSdl3Window(const WindowDesc& desc); +base::UniquePointer CreateSdl3Window(const WindowDesc& desc); #endif -std::unique_ptr Window::Create(const WindowDesc& desc) { +base::UniquePointer Window::Create(const WindowDesc& desc) { #if defined(RX_HAS_SDL3) if (auto window = CreateSdl3Window(desc)) return window; #endif RX_WARN("no window backend available, running headless"); - return std::make_unique(desc); + return base::MakeUnique(desc); } } // namespace rx diff --git a/engine/core/window.h b/engine/core/window.h index 5db4b2da..59ddb557 100644 --- a/engine/core/window.h +++ b/engine/core/window.h @@ -1,11 +1,12 @@ #ifndef RX_CORE_WINDOW_H_ #define RX_CORE_WINDOW_H_ -#include -#include -#include -#include +#include "base/containers/vector.h" +#include "base/functional/function.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/input.h" #include "core/types.h" @@ -17,7 +18,7 @@ struct ANativeWindow; namespace rx { struct WindowDesc { - std::string title = "rx"; + base::String title = "rx"; u32 width = 1920; u32 height = 1080; bool fullscreen = false; @@ -117,24 +118,24 @@ class RX_CORE_EXPORT Window { // Called for every native event before the window handles it. With the // SDL3 backend the pointer is an SDL_Event. ImGui hooks in here. - void set_event_hook(std::function hook) { - event_hook_ = std::move(hook); + void set_event_hook(base::Function hook) { + event_hook_ = base::move(hook); } // Vulkan glue. Backends that can present return the instance extensions // they need and write a VkSurfaceKHR through the opaque out pointer. // Headless windows return nothing, which tells the renderer to stay off. - virtual std::vector vulkan_instance_extensions() const { return {}; } + virtual base::Vector vulkan_instance_extensions() const { return {}; } virtual bool CreateVulkanSurface(void* vk_instance, void* out_vk_surface) { return false; } // Returns a platform window, or a headless stub when none is available. - static std::unique_ptr Create(const WindowDesc& desc); + static base::UniquePointer Create(const WindowDesc& desc); protected: InputState input_; GamepadState gamepad_; TouchState touch_; - std::function event_hook_; + base::Function event_hook_; }; #if defined(__ANDROID__) @@ -151,7 +152,7 @@ class AndroidWindowBase : public Window { virtual void SetNativeWindow(::ANativeWindow* window) = 0; }; -std::unique_ptr CreateAndroidWindow(::ANativeWindow* window); +base::UniquePointer CreateAndroidWindow(::ANativeWindow* window); #endif } // namespace rx diff --git a/engine/core/window_android.cc b/engine/core/window_android.cc index 0af789de..7f6d7b76 100644 --- a/engine/core/window_android.cc +++ b/engine/core/window_android.cc @@ -1,6 +1,8 @@ #include #include +#include "base/containers/vector.h" +#include "base/memory/unique_pointer.h" #include "core/log.h" #include "core/window.h" @@ -33,7 +35,7 @@ class AndroidWindow final : public AndroidWindowBase { return window_ ? static_cast(ANativeWindow_getHeight(window_)) : 0; } - std::vector vulkan_instance_extensions() const override { + base::Vector vulkan_instance_extensions() const override { return {"VK_KHR_surface", "VK_KHR_android_surface"}; } @@ -75,8 +77,8 @@ class AndroidWindow final : public AndroidWindowBase { } // namespace -std::unique_ptr CreateAndroidWindow(ANativeWindow* window) { - return std::make_unique(window); +base::UniquePointer CreateAndroidWindow(ANativeWindow* window) { + return base::MakeUnique(window); } } // namespace rx diff --git a/engine/core/window_sdl3.cc b/engine/core/window_sdl3.cc index 738177dd..6dbe5cf7 100644 --- a/engine/core/window_sdl3.cc +++ b/engine/core/window_sdl3.cc @@ -1,13 +1,15 @@ #include #include -#include -#include -#include +#include #include "core/log.h" #include "core/window.h" #if defined(RX_HAS_WAYLAND_KDE_HDR) +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "base/memory/unique_pointer.h" +#include "core/scalar.h" #include "core/wayland_kde_hdr.h" #endif @@ -133,11 +135,11 @@ class Sdl3Window final : public Window { input_.wheel = 0; input_.text_len = 0; input_.text[0] = '\0'; - std::memset(input_.pressed, 0, sizeof(input_.pressed)); - std::memset(input_.repeated, 0, sizeof(input_.repeated)); - std::memset(input_.mouse_pressed, 0, sizeof(input_.mouse_pressed)); - std::memset(input_.mouse_released, 0, sizeof(input_.mouse_released)); - std::memset(gamepad_.pressed, 0, sizeof(gamepad_.pressed)); + base::MemSet(input_.pressed, 0, sizeof(input_.pressed)); + base::MemSet(input_.repeated, 0, sizeof(input_.repeated)); + base::MemSet(input_.mouse_pressed, 0, sizeof(input_.mouse_pressed)); + base::MemSet(input_.mouse_released, 0, sizeof(input_.mouse_released)); + base::MemSet(gamepad_.pressed, 0, sizeof(gamepad_.pressed)); touch_.BeginPump(); // SDL reports pointers in the units the desktop lays the window out in, @@ -235,7 +237,7 @@ class Sdl3Window final : public Window { bool trigger = a == GamepadAxis::kLeftTrigger || a == GamepadAxis::kRightTrigger; // Sticks span the full i16 range; triggers run 0..32767. f32 v = trigger ? event.gaxis.value / 32767.0f : event.gaxis.value / 32768.0f; - gamepad_.axes[static_cast(a)] = std::clamp(v, -1.0f, 1.0f); + gamepad_.axes[static_cast(a)] = rx::Clamp(v, -1.0f, 1.0f); break; } default: @@ -293,7 +295,7 @@ class Sdl3Window final : public Window { void SetRumble(f32 low_freq, f32 high_freq, u32 duration_ms) override { if (!pad_) return; - auto scale = [](f32 v) { return static_cast(std::clamp(v, 0.0f, 1.0f) * 65535.0f); }; + auto scale = [](f32 v) { return static_cast(rx::Clamp(v, 0.0f, 1.0f) * 65535.0f); }; SDL_RumbleGamepad(pad_, scale(low_freq), scale(high_freq), duration_ms); } @@ -354,7 +356,7 @@ class Sdl3Window final : public Window { f32 pixel_density() const override { return SDL_GetWindowPixelDensity(window_); } - std::vector vulkan_instance_extensions() const override { + base::Vector vulkan_instance_extensions() const override { Uint32 count = 0; const char* const* extensions = SDL_Vulkan_GetInstanceExtensions(&count); if (!extensions) return {}; @@ -423,8 +425,8 @@ class Sdl3Window final : public Window { // 0x04 = right trigger, 0x08 = left trigger. Uint8 report[48] = {}; report[0] = static_cast((right ? 0x04 : 0) | (left ? 0x08 : 0)); - if (right) std::memcpy(&report[10], right_fx, sizeof(right_fx)); - if (left) std::memcpy(&report[21], left_fx, sizeof(left_fx)); + if (right) base::MemCopy(&report[10], right_fx, sizeof(right_fx)); + if (left) base::MemCopy(&report[21], left_fx, sizeof(left_fx)); SDL_SendGamepadEffect(pad_, report, sizeof(report)); } @@ -435,13 +437,13 @@ class Sdl3Window final : public Window { #if defined(RX_HAS_WAYLAND_KDE_HDR) // Lazily created by hdr_enabled() (a const query), hence mutable. mutable bool kde_hdr_checked_ = false; - mutable std::unique_ptr kde_hdr_; + mutable base::UniquePointer kde_hdr_; #endif }; } // namespace -std::unique_ptr CreateSdl3Window(const WindowDesc& desc) { +base::UniquePointer CreateSdl3Window(const WindowDesc& desc) { // Must be set before the video subsystem starts synthesizing. Off means the // panel only feeds Window::touch(), so mouse look cannot be dragged by a // thumb resting on the screen. @@ -472,7 +474,7 @@ std::unique_ptr CreateSdl3Window(const WindowDesc& desc) { // Non-fatal: keyboard/mouse still work, gamepads just won't be seen. RX_WARN("sdl gamepad subsystem init failed: {}", SDL_GetError()); } - return std::make_unique(window); + return base::MakeUnique(window); } } // namespace rx diff --git a/engine/core/world_clock.cc b/engine/core/world_clock.cc index 938abf92..33c0e904 100644 --- a/engine/core/world_clock.cc +++ b/engine/core/world_clock.cc @@ -1,5 +1,7 @@ #include "core/world_clock.h" +#include + namespace rx { namespace { @@ -22,8 +24,8 @@ SkyLighting ComputeSkyLighting(f32 hour) { // 18 -> pi (sunset, due west). A small constant south lean (the -0.25 z) keeps // shadows off the world axes for a more natural look. const f32 a = (hour - 6.0f) / 12.0f * kPi; - const f32 ca = std::cos(a); - const f32 elev = std::sin(a); // sun elevation: > 0 day, < 0 night + const f32 ca = ::cos(a); + const f32 elev = ::sin(a); // sun elevation: > 0 day, < 0 night // Daylight weight, with a soft twilight band as the sun crosses the horizon. const f32 day = Smoothstep(-0.12f, 0.12f, elev); diff --git a/engine/core/world_clock.h b/engine/core/world_clock.h index 793c6843..f1db6458 100644 --- a/engine/core/world_clock.h +++ b/engine/core/world_clock.h @@ -1,9 +1,9 @@ #ifndef RX_CORE_WORLD_CLOCK_H_ #define RX_CORE_WORLD_CLOCK_H_ -#include -#include +#include +#include "base/atomic.h" #include "core/export.h" #include "core/math.h" #include "core/types.h" @@ -26,54 +26,54 @@ class WorldClock { // start_hour: time of day at start (0..24). timescale: game min / real min // (the application supplies its world's rate; 1 keeps game time == wall time). void Configure(f32 start_hour, f32 timescale) { - game_days_.store(static_cast(Wrap24(start_hour)) / 24.0, std::memory_order_relaxed); + game_days_.store(static_cast(Wrap24(start_hour)) / 24.0, base::memory_order_relaxed); set_timescale(timescale); - real_seconds_.store(0.0, std::memory_order_relaxed); + real_seconds_.store(0.0, base::memory_order_relaxed); } // Advances game (and wall-clock) time by one real frame. Main thread only. void Advance(f64 real_seconds) { if (real_seconds <= 0) return; - real_seconds_.store(real_seconds_.load(std::memory_order_relaxed) + real_seconds, - std::memory_order_relaxed); - const f64 game_seconds = real_seconds * timescale_.load(std::memory_order_relaxed); - game_days_.store(game_days_.load(std::memory_order_relaxed) + game_seconds / 86400.0, - std::memory_order_relaxed); + real_seconds_.store(real_seconds_.load(base::memory_order_relaxed) + real_seconds, + base::memory_order_relaxed); + const f64 game_seconds = real_seconds * timescale_.load(base::memory_order_relaxed); + game_days_.store(game_days_.load(base::memory_order_relaxed) + game_seconds / 86400.0, + base::memory_order_relaxed); } // Fractional days since start (the whole part counts elapsed days). - f64 game_days() const { return game_days_.load(std::memory_order_relaxed); } - void set_game_days(f64 days) { game_days_.store(days, std::memory_order_relaxed); } + f64 game_days() const { return game_days_.load(base::memory_order_relaxed); } + void set_game_days(f64 days) { game_days_.store(days, base::memory_order_relaxed); } // Hour of day, 0..24. f32 hour() const { const f64 d = game_days(); - return static_cast((d - std::floor(d)) * 24.0); + return static_cast((d - ::floor(d)) * 24.0); } // Sets the time of day, keeping the whole-day count. void set_hour(f32 h) { const f64 d = game_days(); - set_game_days(std::floor(d) + static_cast(Wrap24(h)) / 24.0); + set_game_days(::floor(d) + static_cast(Wrap24(h)) / 24.0); } - f32 timescale() const { return timescale_.load(std::memory_order_relaxed); } - void set_timescale(f32 t) { timescale_.store(t < 0 ? 0 : t, std::memory_order_relaxed); } + f32 timescale() const { return timescale_.load(base::memory_order_relaxed); } + void set_timescale(f32 t) { timescale_.store(t < 0 ? 0 : t, base::memory_order_relaxed); } // Real wall-clock hours elapsed since Configure. f32 real_hours() const { - return static_cast(real_seconds_.load(std::memory_order_relaxed) / 3600.0); + return static_cast(real_seconds_.load(base::memory_order_relaxed) / 3600.0); } private: static f32 Wrap24(f32 h) { - h = std::fmod(h, 24.0f); + h = ::fmod(h, 24.0f); return h < 0 ? h + 24.0f : h; } - std::atomic game_days_{0.0}; + base::Atomic game_days_{0.0}; // Neutral default: game time tracks wall time 1:1 until Configure sets the // application's rate. The clock is game-agnostic; callers pass their timescale. - std::atomic timescale_{1.0f}; - std::atomic real_seconds_{0.0}; + base::Atomic timescale_{1.0f}; + base::Atomic real_seconds_{0.0}; }; // The directional light and sky tint for a time of day, fed straight into the diff --git a/engine/ecs/archetype.cc b/engine/ecs/archetype.cc index f7c5cee9..d4961b9c 100644 --- a/engine/ecs/archetype.cc +++ b/engine/ecs/archetype.cc @@ -1,8 +1,10 @@ #include "ecs/archetype.h" -#include +#include #include +#include "base/algorithm.h" +#include "base/memory/move.h" #include "core/memory/chunk_pool.h" namespace rx::ecs { @@ -13,7 +15,7 @@ u32 AlignUp(u32 value, u32 align) { return (value + align - 1) & ~(align - 1); } } // namespace -Archetype::Archetype(Signature signature) : signature_(std::move(signature)) { +Archetype::Archetype(Signature signature) : signature_(base::move(signature)) { columns_.reserve(signature_.size()); u32 total_stride = 0; u32 max_align = 1; @@ -133,7 +135,7 @@ u32 Archetype::ChunkRowCount(u32 chunk) const { } int Archetype::ColumnIndex(ComponentId id) const { - auto it = std::lower_bound(signature_.begin(), signature_.end(), id); + const ComponentId* it = base::LowerBound(signature_.begin(), signature_.end(), id); if (it == signature_.end() || *it != id) return -1; return static_cast(it - signature_.begin()); } diff --git a/engine/ecs/archetype.h b/engine/ecs/archetype.h index 48eef6e3..fb84a124 100644 --- a/engine/ecs/archetype.h +++ b/engine/ecs/archetype.h @@ -1,12 +1,13 @@ #ifndef RX_ECS_ARCHETYPE_H_ #define RX_ECS_ARCHETYPE_H_ -#include -#include +#include #include #include +#include "base/algorithm.h" +#include "base/memory/mem_ops.h" #include "ecs/component.h" #include "ecs/entity.h" @@ -17,21 +18,31 @@ using Signature = base::Vector; inline Signature MakeSignature(std::initializer_list ids) { Signature sig(ids); - std::sort(sig.begin(), sig.end()); + // A signature's ids are distinct, so any correct sort agrees. + base::Sort(sig.data(), sig.data() + sig.size()); return sig; } inline bool SignatureContains(const Signature& sig, ComponentId id) { - return std::binary_search(sig.begin(), sig.end(), id); + const ComponentId* at = base::LowerBound(sig.begin(), sig.end(), id); + return at != sig.end() && !(id < *at); } -inline bool SignatureContainsAll(const Signature& sig, const Signature& subset) { - return std::includes(sig.begin(), sig.end(), subset.begin(), subset.end()); +// Allocation-free variant for queries (`ids` must be sorted): std::includes. +inline bool SignatureContainsAll(const Signature& sig, const ComponentId* ids, size_t count) { + const ComponentId* have = sig.begin(); + const ComponentId* want = ids; + const ComponentId* want_end = ids + count; + while (want != want_end) { + if (have == sig.end() || *want < *have) return false; + if (!(*have < *want)) ++want; + ++have; + } + return true; } -// Allocation-free variant for queries (`ids` must be sorted). -inline bool SignatureContainsAll(const Signature& sig, const ComponentId* ids, size_t count) { - return std::includes(sig.begin(), sig.end(), ids, ids + count); +inline bool SignatureContainsAll(const Signature& sig, const Signature& subset) { + return SignatureContainsAll(sig, subset.data(), subset.size()); } // Functors for hashing signatures as raw bytes (sorted ids make this stable). @@ -46,7 +57,7 @@ struct SignatureEqual { bool operator()(const Signature& a, const Signature& b) const { return a.size() == b.size() && (a.empty() || - std::memcmp(a.data(), b.data(), a.size() * sizeof(ComponentId)) == 0); + base::MemCompare(a.data(), b.data(), a.size() * sizeof(ComponentId)) == 0); } }; diff --git a/engine/ecs/component.h b/engine/ecs/component.h index 1ec10cf2..48b16ed6 100644 --- a/engine/ecs/component.h +++ b/engine/ecs/component.h @@ -1,10 +1,11 @@ #ifndef RX_ECS_COMPONENT_H_ #define RX_ECS_COMPONENT_H_ -#include #include +#include "base/memory/move.h" +#include "base/meta/traits.h" #include "core/export.h" #include "core/types.h" @@ -46,7 +47,7 @@ consteval u64 ComponentTypeKey() { template ComponentId ComponentIdFor() { - static_assert(std::is_move_constructible_v); + static_assert(base::is_move_constructible_v); static const ComponentId id = [] { return ResolveComponentId( ComponentTypeKey(), @@ -54,9 +55,9 @@ ComponentId ComponentIdFor() { .size = sizeof(T), .align = alignof(T), .move_construct = [](void* dst, - void* src) { new (dst) T(std::move(*static_cast(src))); }, + void* src) { new (dst) T(base::move(*static_cast(src))); }, .destruct = [](void* ptr) { static_cast(ptr)->~T(); }, - .trivially_copyable = std::is_trivially_copyable_v, + .trivially_copyable = base::is_trivially_copyable_v, }); }(); return id; @@ -66,7 +67,7 @@ ComponentId ComponentIdFor() { template ComponentId GetComponentId() { - return detail::ComponentIdFor>(); + return detail::ComponentIdFor>(); } RX_ECS_EXPORT const ComponentInfo& GetComponentInfo(ComponentId id); diff --git a/engine/ecs/scheduler.cc b/engine/ecs/scheduler.cc index d58c1620..9e420f0f 100644 --- a/engine/ecs/scheduler.cc +++ b/engine/ecs/scheduler.cc @@ -1,9 +1,10 @@ +#include "base/memory/move.h" #include "ecs/scheduler.h" namespace rx::ecs { void Scheduler::AddSystem(Stage stage, base::NameString name, SystemFn fn) { - stages_[static_cast(stage)].push_back({std::move(name), std::move(fn)}); + stages_[static_cast(stage)].push_back({base::move(name), base::move(fn)}); } void Scheduler::RunStage(Stage stage, World& world, f32 dt) { diff --git a/engine/ecs/world.cc b/engine/ecs/world.cc index 7e92b557..5fbe6578 100644 --- a/engine/ecs/world.cc +++ b/engine/ecs/world.cc @@ -1,11 +1,14 @@ #include "ecs/world.h" -#include -#include +#include #include +#include "base/algorithm.h" +#include "base/threading/lock_guard.h" +#include "base/threading/mutex.h" #include "core/memory/memory_tracker.h" +#include "core/scalar.h" namespace rx::ecs { @@ -19,7 +22,7 @@ struct Registry { // Generous ceiling; the engine + a large game register on the order of a few // hundred component types. Overflow is a hard error, not silent corruption. static constexpr u32 kMaxComponents = 4096; - std::mutex mutex; + base::Mutex mutex; u32 count = 0; u64 keys[kMaxComponents]{}; ComponentInfo infos[kMaxComponents]{}; @@ -34,7 +37,7 @@ Registry& TheRegistry() { ComponentId ResolveComponentId(u64 type_key, const ComponentInfo& info) { Registry& registry = TheRegistry(); - std::lock_guard lock(registry.mutex); + base::LockGuard lock(registry.mutex); for (u32 id = 0; id < registry.count; ++id) { if (registry.keys[id] != type_key) continue; BASE_BUGCHECK(registry.infos[id].size == info.size && registry.infos[id].align == info.align, @@ -54,7 +57,7 @@ ComponentId ResolveComponentId(u64 type_key, const ComponentInfo& info) { const ComponentInfo& GetComponentInfo(ComponentId id) { detail::Registry& registry = detail::TheRegistry(); #ifndef NDEBUG - std::lock_guard lock(registry.mutex); + base::LockGuard lock(registry.mutex); assert(id < registry.count && "unregistered component id"); #endif return registry.infos[id]; @@ -108,7 +111,7 @@ void* EntityBatch::Column(ComponentId id, u32 row, u32* run) const { if (run) { // The run ends at whichever comes first: the end of this chunk's rows or // the end of the batch. - *run = std::min(archetype_->ChunkRowCount(chunk) - in_chunk, count_ - row); + *run = rx::Min(archetype_->ChunkRowCount(chunk) - in_chunk, count_ - row); } return static_cast(base) + static_cast(in_chunk) * GetComponentInfo(id).size; } @@ -119,8 +122,9 @@ EntityBatch World::BeginBatch(const Signature& signature, u32 count) { if (count == 0) return batch; Signature sorted(signature); - std::sort(sorted.begin(), sorted.end()); - sorted.erase(std::unique(sorted.begin(), sorted.end()), sorted.end()); + // Equal ids are indistinguishable, so any correct sort agrees. + base::Sort(sorted.begin(), sorted.end()); + sorted.erase(base::Unique(sorted.begin(), sorted.end()), sorted.end()); Archetype* archetype = GetOrCreateArchetype(sorted); batch.archetype_ = archetype; @@ -172,7 +176,7 @@ void* World::AddRaw(Entity entity, ComponentId id) { return existing; } Signature signature = record.archetype->signature(); - signature.insert(std::lower_bound(signature.begin(), signature.end(), id), id); + signature.insert(base::LowerBound(signature.begin(), signature.end(), id), id); MoveEntity(entity, record, GetOrCreateArchetype(signature)); return record.archetype->ComponentAt(id, record.row); } @@ -182,7 +186,7 @@ void World::RemoveRaw(Entity entity, ComponentId id) { EntityRecord& record = records_[entity.index]; if (!SignatureContains(record.archetype->signature(), id)) return; Signature signature = record.archetype->signature(); - signature.erase(std::lower_bound(signature.begin(), signature.end(), id)); + signature.erase(base::LowerBound(signature.begin(), signature.end(), id)); MoveEntity(entity, record, GetOrCreateArchetype(signature)); } diff --git a/engine/ecs/world.h b/engine/ecs/world.h index c38f6439..7b5c7670 100644 --- a/engine/ecs/world.h +++ b/engine/ecs/world.h @@ -1,19 +1,31 @@ #ifndef RX_ECS_WORLD_H_ #define RX_ECS_WORLD_H_ -#include -#include - #include #include #include +#include "base/algorithm.h" +#include "base/memory/move.h" +#include "base/meta/traits.h" #include "ecs/archetype.h" #include "ecs/component.h" #include "ecs/entity.h" namespace rx::ecs { +namespace detail { +// Position of T in Us. Each's component types are distinct, as the query +// that fetches one column per type requires. +template +constexpr mem_size IndexOf() { + mem_size index = 0; + bool found = false; + ((found = found || base::is_same_v, index += found ? 0 : 1), ...); + return index; +} +} // namespace detail + // A run of freshly appended rows in one archetype, handed to a // World::CreateBatch callback. Component memory is uninitialized, exactly as // Archetype::AddRow leaves it: the callback must construct every column of @@ -49,7 +61,7 @@ class RX_ECS_EXPORT World { template T& Add(Entity entity, T value) { void* slot = AddRaw(entity, GetComponentId()); - return *new (slot) T(std::move(value)); + return *new (slot) T(base::move(value)); } template @@ -75,7 +87,8 @@ class RX_ECS_EXPORT World { void Each(Fn&& fn) { static_assert(sizeof...(Ts) > 0); ComponentId required[sizeof...(Ts)] = {GetComponentId()...}; - std::sort(std::begin(required), std::end(required)); + // Component ids in one query are distinct, so any correct sort agrees. + base::Sort(required, required + sizeof...(Ts)); // Archetypes themselves are stable, but structural changes in a callback // may reallocate archetypes_ or shrink the current chunk. Iterate the // original archetype set by pointer and revalidate the row each time. @@ -91,11 +104,9 @@ class RX_ECS_EXPORT World { row < initial_row_count && row < archetype->row_count(); ++row) { const u32 chunk = row / rows_per_chunk; const u32 in_chunk = row % rows_per_chunk; - std::tuple columns = [&](std::index_sequence) { - return std::tuple{ - static_cast(archetype->ChunkColumnData(chunk, indices[Is]))...}; - }(std::index_sequence_for{}); - fn(archetype->entity_at(row), std::get(columns)[in_chunk]...); + fn(archetype->entity_at(row), + static_cast(archetype->ChunkColumnData( + chunk, indices[detail::IndexOf()]))[in_chunk]...); } } } diff --git a/engine/edit/hierarchy.cc b/engine/edit/hierarchy.cc index d5c1db31..eebcdec0 100644 --- a/engine/edit/hierarchy.cc +++ b/engine/edit/hierarchy.cc @@ -1,13 +1,17 @@ #include "edit/hierarchy.h" -#include +#include "base/external/xoshiro256ss/xoshiro256ss.h" +#include "base/random/random.h" namespace rx::edit { namespace { u64 RandomGuid() { - static thread_local std::mt19937_64 rng{std::random_device{}()}; + // Guids only need to be unique, not reproducible: a per-thread 64-bit + // generator seeded once from the OS keeps every draw full width without a + // syscall per id. + static thread_local base::xoshiro256ss rng{base::SourceTrueRandomSeed()}; u64 v = 0; while (v == 0) v = rng(); // never hand out the 0 sentinel return v; diff --git a/engine/edit/reflect.cc b/engine/edit/reflect.cc index 4e78aa06..921a17e5 100644 --- a/engine/edit/reflect.cc +++ b/engine/edit/reflect.cc @@ -1,10 +1,16 @@ #include "edit/reflect.h" -#include -#include -#include -#include - +#include + +#include "base/containers/span.h" +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "base/threading/lock_guard.h" +#include "base/threading/mutex.h" #include "core/log.h" #include "scene/components.h" @@ -12,16 +18,17 @@ namespace rx::edit { namespace detail { -// A component's registration record. Names live in a deque so their c_str() -// pointers (handed out as PropDesc::name / ComponentDesc::name) stay stable as -// more props are added; the PropDesc vector may reallocate freely because -// ComponentDesc::props is recomputed from it on every query. +// A component's registration record. Names live in their own heap blocks so +// their c_str() pointers (handed out as PropDesc::name / ComponentDesc::name) +// stay stable as more props are added (an inline base::String would move); the +// PropDesc vector may reallocate freely because ComponentDesc::props is +// recomputed from it on every query. struct RegEntry { - std::string name; + base::String name; ecs::ComponentId id = 0; void (*default_construct)(void*) = nullptr; - std::deque prop_names; - std::vector props; + base::Vector> prop_names; + base::Vector props; ComponentDesc desc{}; // rebuilt (props pointer) in Finalize }; @@ -34,11 +41,11 @@ using detail::RegEntry; void RegisterBuiltins(); struct Registry { - std::mutex mutex; - std::vector> entries; + base::Mutex mutex; + base::Vector> entries; // Recomputed view handed out by AllComponents(); stable once builtins and any // app components are registered. - std::vector view; + base::Vector view; bool builtins_done = false; }; @@ -64,19 +71,19 @@ void Finalize(Registry& reg) { void EnsureBuiltins() { Registry& reg = TheRegistry(); { - std::lock_guard lock(reg.mutex); + base::LockGuard lock(reg.mutex); if (reg.builtins_done) return; reg.builtins_done = true; // set first: RegisterBuiltins re-enters via ReflectComponent } RegisterBuiltins(); - std::lock_guard lock(reg.mutex); + base::LockGuard lock(reg.mutex); Finalize(reg); } RegEntry* FindEntry(ecs::ComponentId id) { Registry& reg = TheRegistry(); for (auto& entry : reg.entries) - if (entry->id == id) return entry.get(); + if (entry->id == id) return entry.Get_UseOnlyIfYouKnowWhatYouareDoing(); return nullptr; } @@ -86,31 +93,31 @@ namespace detail { RegEntry* CreateEntry(const char* name, ecs::ComponentId id, void (*default_construct)(void*)) { Registry& reg = TheRegistry(); - std::lock_guard lock(reg.mutex); + base::LockGuard lock(reg.mutex); for (auto& entry : reg.entries) { - if (entry->id == id) return entry.get(); // already registered + if (entry->id == id) return entry.Get_UseOnlyIfYouKnowWhatYouareDoing(); // already registered } - auto entry = std::make_unique(); + auto entry = base::MakeUnique(); entry->name = name; entry->id = id; entry->default_construct = default_construct; - RegEntry* raw = entry.get(); - reg.entries.push_back(std::move(entry)); + RegEntry* raw = entry.Get_UseOnlyIfYouKnowWhatYouareDoing(); + reg.entries.push_back(base::move(entry)); return raw; } void AddProp(RegEntry* entry, const char* name, PropType type, u32 offset) { if (!entry) return; Registry& reg = TheRegistry(); - std::lock_guard lock(reg.mutex); - entry->prop_names.emplace_back(name); - entry->props.push_back(PropDesc{entry->prop_names.back().c_str(), type, offset}); + base::LockGuard lock(reg.mutex); + entry->prop_names.push_back(base::MakeUnique(name)); + entry->props.push_back(PropDesc{entry->prop_names.back()->c_str(), type, offset}); } void SetRange(RegEntry* entry, f32 min, f32 max) { if (!entry || entry->props.empty()) return; Registry& reg = TheRegistry(); - std::lock_guard lock(reg.mutex); + base::LockGuard lock(reg.mutex); if (entry->props.empty()) return; entry->props.back().min = min; entry->props.back().max = max; @@ -119,12 +126,12 @@ void SetRange(RegEntry* entry, f32 min, f32 max) { void SetHint(RegEntry* entry, const char* hint) { if (!entry || !hint || entry->props.empty()) return; Registry& reg = TheRegistry(); - std::lock_guard lock(reg.mutex); + base::LockGuard lock(reg.mutex); if (entry->props.empty()) return; - // Parked in the same deque the prop names use, so the handed-out pointer + // Parked with the prop names, so the handed-out pointer // survives a caller that passed a temporary. - entry->prop_names.emplace_back(hint); - entry->props.back().hint = entry->prop_names.back().c_str(); + entry->prop_names.push_back(base::MakeUnique(hint)); + entry->props.back().hint = entry->prop_names.back()->c_str(); } } // namespace detail @@ -168,18 +175,18 @@ const char* PropTypeName(PropType type) { return "?"; } -std::span AllComponents() { +base::Span AllComponents() { EnsureBuiltins(); Registry& reg = TheRegistry(); - std::lock_guard lock(reg.mutex); + base::LockGuard lock(reg.mutex); Finalize(reg); // pick up any components registered after builtins - return std::span(reg.view.data(), reg.view.size()); + return base::Span(reg.view.data(), reg.view.size()); } const ComponentDesc* FindComponent(ecs::ComponentId id) { EnsureBuiltins(); Registry& reg = TheRegistry(); - std::lock_guard lock(reg.mutex); + base::LockGuard lock(reg.mutex); for (auto& entry : reg.entries) { if (entry->id == id) { entry->desc.props = entry->props.data(); @@ -192,10 +199,10 @@ const ComponentDesc* FindComponent(ecs::ComponentId id) { return nullptr; } -const ComponentDesc* FindComponentByName(std::string_view name) { +const ComponentDesc* FindComponentByName(base::StringRef name) { EnsureBuiltins(); Registry& reg = TheRegistry(); - std::lock_guard lock(reg.mutex); + base::LockGuard lock(reg.mutex); for (auto& entry : reg.entries) { if (entry->name == name) { entry->desc.props = entry->props.data(); @@ -208,8 +215,8 @@ const ComponentDesc* FindComponentByName(std::string_view name) { return nullptr; } -std::vector ComponentsOn(ecs::World& world, ecs::Entity entity) { - std::vector out; +base::Vector ComponentsOn(ecs::World& world, ecs::Entity entity) { + base::Vector out; for (const ComponentDesc* desc : AllComponents()) { if (world.HasRaw(entity, desc->id)) out.push_back(desc); } @@ -254,7 +261,7 @@ bool GetProp(ecs::World& world, ecs::Entity entity, const ComponentDesc& comp, c break; } case PropType::kString: - *out = PropValue::String(*reinterpret_cast(field)); + *out = PropValue::String(*reinterpret_cast(field)); break; case PropType::kAssetId: *out = PropValue::AssetIdV(reinterpret_cast(field)->hash); @@ -278,17 +285,17 @@ bool SetProp(ecs::World& world, ecs::Entity entity, const ComponentDesc& comp, c case PropType::kU64: *reinterpret_cast(field) = value.u; break; case PropType::kF32: *reinterpret_cast(field) = value.f[0]; break; case PropType::kVec2: - std::memcpy(field, value.f, 2 * sizeof(f32)); + base::MemCopy(field, value.f, 2 * sizeof(f32)); break; case PropType::kVec3: - std::memcpy(field, value.f, 3 * sizeof(f32)); + base::MemCopy(field, value.f, 3 * sizeof(f32)); break; case PropType::kVec4: case PropType::kQuat: case PropType::kColor: - std::memcpy(field, value.f, 4 * sizeof(f32)); + base::MemCopy(field, value.f, 4 * sizeof(f32)); break; - case PropType::kString: *reinterpret_cast(field) = value.s; break; + case PropType::kString: *reinterpret_cast(field) = value.s; break; case PropType::kAssetId: reinterpret_cast(field)->hash = value.u; break; case PropType::kEntity: *reinterpret_cast(field) = value.e; break; } @@ -300,7 +307,7 @@ bool AddComponentByDesc(ecs::World& world, ecs::Entity entity, const ComponentDe RegEntry* entry = nullptr; { Registry& reg = TheRegistry(); - std::lock_guard lock(reg.mutex); + base::LockGuard lock(reg.mutex); entry = FindEntry(comp.id); } if (!entry || !entry->default_construct) { diff --git a/engine/edit/reflect.h b/engine/edit/reflect.h index a528aa5e..90731210 100644 --- a/engine/edit/reflect.h +++ b/engine/edit/reflect.h @@ -1,13 +1,16 @@ #ifndef RX_EDIT_REFLECT_H_ #define RX_EDIT_REFLECT_H_ -#include -#include -#include -#include -#include +#include #include "asset/asset_id.h" +#include "base/containers/span.h" +#include "base/containers/vector.h" +#include "base/memory/cxx_lifetime.h" +#include "base/memory/move.h" +#include "base/meta/traits.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/math.h" #include "core/types.h" @@ -50,7 +53,7 @@ struct PropValue { i64 i = 0; u64 u = 0; f32 f[4] = {}; - std::string s; + base::String s; ecs::Entity e{}; static PropValue Bool(bool v) { PropValue p; p.type = PropType::kBool; p.b = v; return p; } @@ -73,8 +76,8 @@ struct PropValue { static PropValue Color(f32 r, f32 g, f32 b, f32 a) { PropValue p; p.type = PropType::kColor; p.f[0] = r; p.f[1] = g; p.f[2] = b; p.f[3] = a; return p; } - static PropValue String(std::string v) { - PropValue p; p.type = PropType::kString; p.s = std::move(v); return p; + static PropValue String(base::String v) { + PropValue p; p.type = PropType::kString; p.s = base::move(v); return p; } static PropValue AssetIdV(u64 hash) { PropValue p; p.type = PropType::kAssetId; p.u = hash; return p; } static PropValue EntityV(ecs::Entity v) { PropValue p; p.type = PropType::kEntity; p.e = v; return p; } @@ -121,37 +124,37 @@ u32 MemberOffset(M T::* member) { } // Maps a member's C++ type to a PropType. Handles the engine math structs, the -// scalar types, std::string, asset::AssetId, ecs::Entity, and raw f32[N] arrays +// scalar types, base::String, asset::AssetId, ecs::Entity, and raw f32[N] arrays // (the engine Transform stores position/rotation as arrays). template constexpr PropType DeducePropType() { - using T = std::remove_cvref_t; - if constexpr (std::is_array_v) { - constexpr size_t n = std::extent_v; - static_assert(std::is_same_v, f32>, "only f32[N] arrays are reflectable"); + using T = base::remove_cvref_t; + if constexpr (base::is_array_v) { + constexpr size_t n = base::extent_v; + static_assert(base::is_same_v, f32>, "only f32[N] arrays are reflectable"); static_assert(n >= 2 && n <= 4, "only f32[2..4] arrays are reflectable"); if constexpr (n == 2) return PropType::kVec2; else if constexpr (n == 3) return PropType::kVec3; else return PropType::kVec4; - } else if constexpr (std::is_same_v) { + } else if constexpr (base::is_same_v) { return PropType::kBool; - } else if constexpr (std::is_same_v) { + } else if constexpr (base::is_same_v) { return PropType::kI32; - } else if constexpr (std::is_same_v) { + } else if constexpr (base::is_same_v) { return PropType::kU32; - } else if constexpr (std::is_same_v) { + } else if constexpr (base::is_same_v) { return PropType::kU64; - } else if constexpr (std::is_same_v) { + } else if constexpr (base::is_same_v) { return PropType::kF32; - } else if constexpr (std::is_same_v) { + } else if constexpr (base::is_same_v) { return PropType::kVec3; - } else if constexpr (std::is_same_v) { + } else if constexpr (base::is_same_v) { return PropType::kQuat; - } else if constexpr (std::is_same_v) { + } else if constexpr (base::is_same_v) { return PropType::kString; - } else if constexpr (std::is_same_v) { + } else if constexpr (base::is_same_v) { return PropType::kAssetId; - } else if constexpr (std::is_same_v) { + } else if constexpr (base::is_same_v) { return PropType::kEntity; } else { static_assert(sizeof(T) == 0, "unreflectable member type"); @@ -208,7 +211,7 @@ ComponentReflector& ReflectComponent(const char* name) { // constructor is captured when available (AddComponentByDesc needs it); a // component without one is still reflectable, just not addable by desc. constexpr auto default_ctor = [] { - if constexpr (std::is_default_constructible_v) + if constexpr (base::is_default_constructible_v) return +[](void* slot) { new (slot) T(); }; else return static_cast(nullptr); @@ -219,11 +222,11 @@ ComponentReflector& ReflectComponent(const char* name) { } // Every registered component (builtins are registered on first use). -RX_EDIT_EXPORT std::span AllComponents(); +RX_EDIT_EXPORT base::Span AllComponents(); RX_EDIT_EXPORT const ComponentDesc* FindComponent(ecs::ComponentId id); -RX_EDIT_EXPORT const ComponentDesc* FindComponentByName(std::string_view name); +RX_EDIT_EXPORT const ComponentDesc* FindComponentByName(base::StringRef name); // Reflected components present on `entity`, in registration order. -RX_EDIT_EXPORT std::vector ComponentsOn(ecs::World& world, ecs::Entity entity); +RX_EDIT_EXPORT base::Vector ComponentsOn(ecs::World& world, ecs::Entity entity); // Reads/writes a single field. False if the component is absent on the entity. RX_EDIT_EXPORT bool GetProp(ecs::World& world, ecs::Entity entity, const ComponentDesc& comp, diff --git a/engine/edit/scene_io.cc b/engine/edit/scene_io.cc index 67393549..d310d52d 100644 --- a/engine/edit/scene_io.cc +++ b/engine/edit/scene_io.cc @@ -1,37 +1,39 @@ #include "edit/scene_io.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include #include "asset/asset_id.h" -#include +#include "base/containers/unordered_map.h" +#include "base/containers/unordered_set.h" +#include "base/containers/vector.h" +#include "base/memory/move.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/format.h" +#include "core/file_system.h" #include "core/log.h" +#include "core/text_reader.h" +#include "core/text_writer.h" #include "edit/hierarchy.h" #include "edit/reflect.h" #include "scene/components.h" +#include "core/sort.h" namespace rx::edit { namespace { constexpr int kSceneVersion = 1; -std::string Trim(std::string_view s) { +base::String Trim(base::StringRef s) { size_t a = s.find_first_not_of(" \t\r\n"); - if (a == std::string_view::npos) return {}; + if (a == base::StringRef::npos) return {}; size_t b = s.find_last_not_of(" \t\r\n"); - return std::string(s.substr(a, b - a + 1)); + return base::String(s.substr(a, b - a + 1)); } -std::string QuoteString(std::string_view s) { - std::string out = "\""; +base::String QuoteString(base::StringRef s) { + base::String out = "\""; for (char c : s) { switch (c) { case '\\': out += "\\\\"; break; @@ -45,11 +47,11 @@ std::string QuoteString(std::string_view s) { return out; } -std::string Unquote(std::string_view raw) { - std::string s = Trim(raw); - std::string_view sv = s; +base::String Unquote(base::StringRef raw) { + base::String s = Trim(raw); + base::StringRef sv = s; if (sv.size() >= 2 && sv.front() == '"' && sv.back() == '"') sv = sv.substr(1, sv.size() - 2); - std::string out; + base::String out; for (size_t i = 0; i < sv.size(); ++i) { if (sv[i] == '\\' && i + 1 < sv.size()) { char n = sv[++i]; @@ -65,7 +67,7 @@ std::string Unquote(std::string_view raw) { return out; } -std::string FloatStr(f32 v) { return std::format("{}", v); } +base::String FloatStr(f32 v) { return rx::StrFormat("{}", v); } // How many of PropValue::f a prop type uses; 0 for the types that carry no // float. Keeps the number handling generic over the registry instead of @@ -91,11 +93,11 @@ u32 FloatLanes(PropType type) { // `scale` and another in `position`. Neither may accept a non-finite value: it // poisons every matrix and lighting term it reaches, and FloatStr cannot write // one back out in a form this reader would take. -const char* ReadFloat(const std::string& token, f32* out) { +const char* ReadFloat(const base::String& token, f32* out) { char* end = nullptr; - const f32 v = std::strtof(token.c_str(), &end); + const f32 v = ::strtof(token.c_str(), &end); if (end == token.c_str() || *end != '\0') return "is not a number"; - if (!std::isfinite(v)) return "is not finite"; + if (!::isfinite(v)) return "is not finite"; *out = v; return nullptr; } @@ -112,11 +114,11 @@ const char* ReadFloat(const std::string& token, f32* out) { // was one nobody had authored, and no load, no --validate and no warning said // so. A short list pads because the format asks it to; a long one is the author // believing in a prop that is not there. -std::vector ParseFloats(std::string_view s, size_t n, std::string* error = nullptr) { - std::vector out; - std::istringstream in{std::string(s)}; - std::string token; - while (out.size() < n && (in >> token)) { +base::Vector ParseFloats(base::StringRef s, size_t n, base::String* error = nullptr) { + base::Vector out; + TokenReader in(s); + base::String token; + while (out.size() < n && in.Next(&token)) { f32 v = 0.f; if (const char* why = ReadFloat(token, &v)) { if (error) *error = "'" + token + "' " + why; @@ -126,9 +128,9 @@ std::vector ParseFloats(std::string_view s, size_t n, std::string* error = } // Only reached with n lanes in hand, so a token left over is surplus rather // than the tail of a list that stopped early on a bad number. - if (out.size() == n && (in >> token)) { + if (out.size() == n && in.Next(&token)) { if (error) - *error = std::format("takes {} value{} and was given more, starting at '{}'", n, + *error = rx::StrFormat("takes {} value{} and was given more, starting at '{}'", n, n == 1 ? "" : "s", token); } out.resize(n, 0.f); @@ -142,7 +144,7 @@ std::vector ParseFloats(std::string_view s, size_t n, std::string* error = // 0 0 0" pads to, since a short list pads with 0 and not with an identity w - // yields a zero 3x3 and the mesh vanishes. The authoring boundary is the only // place this can be caught without putting a square root in that hot path. -std::string QuatProblem(const std::vector& v) { +base::String QuatProblem(const base::Vector& v) { const f32 length_sq = v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3]; if (length_sq < 1e-8f) return "the zero quaternion collapses the mesh to a point (identity is 0 0 0 1)"; @@ -150,25 +152,25 @@ std::string QuatProblem(const std::vector& v) { // (0.7 0 0 0.7 is only 1% short) and well inside a visible mis-scale. Same // tolerance as --validate's non_unit_rotation, so the two agree on what is // merely rounded and what is wrong. - const f32 length = std::sqrt(length_sq); - if (std::abs(length - 1.0f) > 0.05f) - return std::format("length {} scales the mesh by that on top of Transform.scale", length); + const f32 length = ::sqrt(length_sq); + if (::abs(length - 1.0f) > 0.05f) + return rx::StrFormat("length {} scales the mesh by that on top of Transform.scale", length); return {}; } -u64 ParseHexOrDec(std::string_view s) { - std::string t = Trim(s); - return static_cast(std::strtoull(t.c_str(), nullptr, 0)); // base 0: 0x -> hex +u64 ParseHexOrDec(base::StringRef s) { + base::String t = Trim(s); + return static_cast(::strtoull(t.c_str(), nullptr, 0)); // base 0: 0x -> hex } // Renders one field of a component to a literal. Entity/AssetId need world/db // context, handled by the caller before falling through here. -std::string FormatValue(const PropValue& v) { +base::String FormatValue(const PropValue& v) { switch (v.type) { case PropType::kBool: return v.b ? "true" : "false"; - case PropType::kI32: return std::format("{}", static_cast(v.i)); - case PropType::kU32: return std::format("{}", static_cast(v.u)); - case PropType::kU64: return std::format("0x{:016x}", v.u); + case PropType::kI32: return rx::StrFormat("{}", static_cast(v.i)); + case PropType::kU32: return rx::StrFormat("{}", static_cast(v.u)); + case PropType::kU64: return rx::StrFormat("0x{:016x}", v.u); case PropType::kF32: return FloatStr(v.f[0]); case PropType::kVec2: return FloatStr(v.f[0]) + " " + FloatStr(v.f[1]); case PropType::kVec3: @@ -182,7 +184,7 @@ std::string FormatValue(const PropValue& v) { case PropType::kAssetId: { if (v.u == 0) return "\"\""; if (auto path = asset::LookupAssetPath(asset::AssetId{v.u})) return QuoteString(*path); - return std::format("hash:0x{:016x}", v.u); + return rx::StrFormat("hash:0x{:016x}", v.u); } case PropType::kEntity: return "null"; // resolved by caller } @@ -193,20 +195,20 @@ u64 PackKey(ecs::Entity e) { return static_cast(e.generation) << 32 | e.ind // Names an entity in a save error the way the author would look for it. Only // called after EnsureGuid, so the guid fallback is always there. -std::string EntityLabel(ecs::World& world, ecs::Entity e) { +base::String EntityLabel(ecs::World& world, ecs::Entity e) { if (const scene::Name* name = world.Get(e)) return "'" + name->value + "'"; - return std::format("guid:0x{:016x}", world.Get(e)->value); + return rx::StrFormat("guid:0x{:016x}", world.Get(e)->value); } } // namespace -bool SaveScene(ecs::World& world, const std::string& file_path, std::string* error) { +bool SaveScene(ecs::World& world, const base::String& file_path, base::String* error) { // Union of identity-bearing entities (Guid, Name or Transform). - std::unordered_set seen; - std::vector entities; + base::UnorderedSet seen; + base::Vector entities; auto collect = [&](ecs::Entity e) { if (world.Has(e)) return; - if (seen.insert(PackKey(e)).second) entities.push_back(e); + if (seen.insert(PackKey(e))) entities.push_back(e); }; world.Each([&](ecs::Entity e, scene::Guid&) { collect(e); }); world.Each([&](ecs::Entity e, scene::Name&) { collect(e); }); @@ -215,9 +217,11 @@ bool SaveScene(ecs::World& world, const std::string& file_path, std::string* err // Every saved entity needs a guid so references resolve on reload; sort by it // for a stable, diff-friendly ordering. for (ecs::Entity e : entities) EnsureGuid(world, e); - std::sort(entities.begin(), entities.end(), [&](ecs::Entity a, ecs::Entity b) { - return world.Get(a)->value < world.Get(b)->value; - }); + // Stable, as a loaded scene may repeat a guid: tied entities keep walk order. + rx::StableSort(entities.data(), entities.data() + entities.size(), + [&](ecs::Entity a, ecs::Entity b) { + return world.Get(a)->value < world.Get(b)->value; + }); // Refuse before the file is touched, so a rejected save leaves whatever was // on disk intact rather than a truncated document. There is no literal for @@ -234,9 +238,9 @@ bool SaveScene(ecs::World& world, const std::string& file_path, std::string* err PropValue value; if (!GetProp(world, e, *comp, prop, &value)) continue; for (u32 lane = 0; lane < lanes; ++lane) { - if (std::isfinite(value.f[lane])) continue; + if (::isfinite(value.f[lane])) continue; if (error) - *error = std::format("{}.{} on entity {} is {}; the scene format has no literal for " + *error = rx::StrFormat("{}.{} on entity {} is {}; the scene format has no literal for " "it, so nothing was written", comp->name, prop.name, EntityLabel(world, e), value.f[lane]); return false; @@ -245,11 +249,7 @@ bool SaveScene(ecs::World& world, const std::string& file_path, std::string* err } } - std::ofstream out(file_path, std::ios::binary); - if (!out) { - if (error) *error = "cannot open '" + file_path + "' for writing"; - return false; - } + TextWriter out; out << "rxscene " << kSceneVersion << "\n"; for (ecs::Entity e : entities) { @@ -263,11 +263,11 @@ bool SaveScene(ecs::World& world, const std::string& file_path, std::string* err const PropDesc& prop = comp->props[i]; PropValue value; if (!GetProp(world, e, *comp, prop, &value)) continue; - std::string literal; + base::String literal; if (prop.type == PropType::kEntity) { if (value.e && world.IsAlive(value.e)) { u64 g = EnsureGuid(world, value.e); - literal = std::format("guid:0x{:016x}", g); + literal = rx::StrFormat("guid:0x{:016x}", g); } else { literal = "null"; } @@ -278,7 +278,11 @@ bool SaveScene(ecs::World& world, const std::string& file_path, std::string* err } } } - return static_cast(out); + if (!fs::WriteTextFile(file_path, out.str())) { + if (error) *error = "cannot open '" + file_path + "' for writing"; + return false; + } + return true; } namespace { @@ -286,25 +290,25 @@ namespace { // `line` is the 1-based source line, carried only so strict mode can point at // the offending assignment. struct Assign { - std::string comp; - std::string prop; - std::string raw; + base::String comp; + base::String prop; + base::String raw; int line = 0; }; struct BareComp { - std::string name; + base::String name; int line = 0; }; struct ParsedEntity { u64 guid = 0; - std::vector assigns; - std::vector bare_comps; + base::Vector assigns; + base::Vector bare_comps; }; // Resolves a prop by name within a component, or null. -const PropDesc* FindProp(const ComponentDesc& comp, std::string_view name) { +const PropDesc* FindProp(const ComponentDesc& comp, base::StringRef name) { for (u32 p = 0; p < comp.prop_count; ++p) { if (name == comp.props[p].name) return &comp.props[p]; } @@ -313,41 +317,44 @@ const PropDesc* FindProp(const ComponentDesc& comp, std::string_view name) { } // namespace -bool LoadScene(ecs::World& world, asset::AssetDatabase& db, const std::string& file_path, - std::string* error, bool strict) { - std::ifstream in(file_path, std::ios::binary); - if (!in) { +bool LoadScene(ecs::World& world, asset::AssetDatabase& db, const base::String& file_path, + base::String* error, bool strict) { + base::String text; + if (!fs::ReadTextFile(file_path, &text)) { if (error) *error = "cannot open '" + file_path + "' for reading"; return false; } - std::string line; - if (!std::getline(in, line)) { + LineReader in(text); + base::StringRef line; + if (!in.Next(&line)) { if (error) *error = "empty scene file"; return false; } { - std::istringstream header{Trim(line)}; - std::string magic; + const base::String trimmed = Trim(line); + TokenReader header(trimmed); + base::String magic; int version = 0; - header >> magic >> version; + header.Next(&magic); + header.Next(&version); if (magic != "rxscene") { if (error) *error = "not an rxscene file"; return false; } if (version > kSceneVersion) { - if (error) *error = std::format("scene version {} newer than supported {}", version, + if (error) *error = rx::StrFormat("scene version {} newer than supported {}", version, kSceneVersion); return false; } } - std::vector parsed; + base::Vector parsed; ParsedEntity* current = nullptr; int line_no = 1; // the header consumed line 1 - while (std::getline(in, line)) { + while (in.Next(&line)) { ++line_no; - std::string t = Trim(line); + base::String t = Trim(line); if (t.empty() || t[0] == '#' || t[0] == ';') continue; if (t == "entity") { parsed.emplace_back(); @@ -357,17 +364,17 @@ bool LoadScene(ecs::World& world, asset::AssetDatabase& db, const std::string& f if (!current) continue; // stray line before first entity size_t eq = t.find('='); - if (eq == std::string::npos) { + if (eq == base::String::npos) { current->bare_comps.push_back({t, line_no}); // tag component continue; } - std::string key = Trim(t.substr(0, eq)); - std::string raw = Trim(t.substr(eq + 1)); + base::String key = Trim(t.substr(0, eq)); + base::String raw = Trim(t.substr(eq + 1)); size_t dot = key.find('.'); - if (dot == std::string::npos) { + if (dot == base::String::npos) { if (strict) { if (error) - *error = std::format("{}:{}: '{}' is not a Component.prop assignment", file_path, + *error = rx::StrFormat("{}:{}: '{}' is not a Component.prop assignment", file_path, line_no, key); return false; } @@ -375,7 +382,7 @@ bool LoadScene(ecs::World& world, asset::AssetDatabase& db, const std::string& f } Assign a{key.substr(0, dot), key.substr(dot + 1), raw, line_no}; if (a.comp == "Guid" && a.prop == "value") current->guid = ParseHexOrDec(raw); - current->assigns.push_back(std::move(a)); + current->assigns.push_back(base::move(a)); } // Strict mode resolves every name BEFORE anything is created, so a typo @@ -385,7 +392,7 @@ bool LoadScene(ecs::World& world, asset::AssetDatabase& db, const std::string& f for (const BareComp& bare : pe.bare_comps) { if (!FindComponentByName(bare.name)) { if (error) - *error = std::format("{}:{}: unknown component '{}'", file_path, bare.line, + *error = rx::StrFormat("{}:{}: unknown component '{}'", file_path, bare.line, bare.name); return false; } @@ -394,13 +401,13 @@ bool LoadScene(ecs::World& world, asset::AssetDatabase& db, const std::string& f const ComponentDesc* comp = FindComponentByName(a.comp); if (!comp) { if (error) - *error = std::format("{}:{}: unknown component '{}'", file_path, a.line, a.comp); + *error = rx::StrFormat("{}:{}: unknown component '{}'", file_path, a.line, a.comp); return false; } const PropDesc* prop = FindProp(*comp, a.prop); if (!prop) { if (error) - *error = std::format("{}:{}: component '{}' has no prop '{}'", file_path, a.line, + *error = rx::StrFormat("{}:{}: component '{}' has no prop '{}'", file_path, a.line, a.comp, a.prop); return false; } @@ -411,12 +418,12 @@ bool LoadScene(ecs::World& world, asset::AssetDatabase& db, const std::string& f // from an authored one. const u32 lanes = FloatLanes(prop->type); if (lanes == 0) continue; - std::string why; - const std::vector v = ParseFloats(a.raw, lanes, &why); + base::String why; + const base::Vector v = ParseFloats(a.raw, lanes, &why); if (why.empty() && prop->type == PropType::kQuat) why = QuatProblem(v); if (!why.empty()) { if (error) - *error = std::format("{}:{}: {}.{} = {}: {}", file_path, a.line, a.comp, a.prop, + *error = rx::StrFormat("{}:{}: {}.{} = {}: {}", file_path, a.line, a.comp, a.prop, a.raw, why); return false; } @@ -425,9 +432,9 @@ bool LoadScene(ecs::World& world, asset::AssetDatabase& db, const std::string& f } // Pass 1: create entities and map guids. - std::vector created; + base::Vector created; created.reserve(parsed.size()); - std::unordered_map by_guid; + base::UnorderedMap by_guid; for (ParsedEntity& pe : parsed) { ecs::Entity e = world.Create(); created.push_back(e); @@ -466,8 +473,8 @@ bool LoadScene(ecs::World& world, asset::AssetDatabase& db, const std::string& f // for the lenient callers (the editor), which keep the zero-padded // remainder and would otherwise get no signal at all. auto floats = [&](size_t n) { - std::string why; - std::vector v = ParseFloats(a.raw, n, &why); + base::String why; + base::Vector v = ParseFloats(a.raw, n, &why); if (why.empty() && prop->type == PropType::kQuat) why = QuatProblem(v); if (!why.empty()) RX_WARN("rxscene: {}:{}: {}.{} = {}: {}", file_path, a.line, a.comp, a.prop, a.raw, why); @@ -478,7 +485,7 @@ bool LoadScene(ecs::World& world, asset::AssetDatabase& db, const std::string& f value.type = prop->type; switch (prop->type) { case PropType::kBool: value = PropValue::Bool(a.raw == "true"); break; - case PropType::kI32: value = PropValue::I32(static_cast(std::strtol(a.raw.c_str(), nullptr, 0))); break; + case PropType::kI32: value = PropValue::I32(static_cast(::strtol(a.raw.c_str(), nullptr, 0))); break; case PropType::kU32: value = PropValue::U32(static_cast(ParseHexOrDec(a.raw))); break; case PropType::kU64: value = PropValue::U64(ParseHexOrDec(a.raw)); break; case PropType::kF32: value = PropValue::F32(floats(1)[0]); break; @@ -510,9 +517,9 @@ bool LoadScene(ecs::World& world, asset::AssetDatabase& db, const std::string& f case PropType::kString: value = PropValue::String(Unquote(a.raw)); break; case PropType::kAssetId: { if (a.raw.rfind("hash:", 0) == 0) { - value = PropValue::AssetIdV(ParseHexOrDec(std::string_view(a.raw).substr(5))); + value = PropValue::AssetIdV(ParseHexOrDec(base::StringRef(a.raw).substr(5))); } else { - std::string path = asset::NormalizePath(Unquote(a.raw)); + base::String path = asset::NormalizePath(Unquote(a.raw)); asset::AssetId id = asset::MakeAssetId(path); if (!path.empty()) { asset::RecordAssetPath(id, path); @@ -525,9 +532,8 @@ bool LoadScene(ecs::World& world, asset::AssetDatabase& db, const std::string& f case PropType::kEntity: { ecs::Entity ref = ecs::kInvalidEntity; if (a.raw.rfind("guid:", 0) == 0) { - u64 g = ParseHexOrDec(std::string_view(a.raw).substr(5)); - auto it = by_guid.find(g); - if (it != by_guid.end()) ref = it->second; + u64 g = ParseHexOrDec(base::StringRef(a.raw).substr(5)); + if (const ecs::Entity* found = by_guid.find(g)) ref = *found; } value = PropValue::EntityV(ref); break; diff --git a/engine/edit/scene_io.h b/engine/edit/scene_io.h index e5ace3f2..70e1c17f 100644 --- a/engine/edit/scene_io.h +++ b/engine/edit/scene_io.h @@ -1,9 +1,9 @@ #ifndef RX_EDIT_SCENE_IO_H_ #define RX_EDIT_SCENE_IO_H_ -#include #include "asset/asset_database.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "ecs/world.h" @@ -21,8 +21,8 @@ namespace rx::edit { // or on a nan/inf float: the format has no literal for one that reads back, so // it is refused (before the file is opened) rather than written as a value that // silently reloads as 0. -RX_EDIT_EXPORT bool SaveScene(ecs::World& world, const std::string& file_path, - std::string* error = nullptr); +RX_EDIT_EXPORT bool SaveScene(ecs::World& world, const base::String& file_path, + base::String* error = nullptr); // Loads a scene into `world`, creating fresh entities, remapping Guid-based // references and resolving Renderable paths through `db`. Existing entities in @@ -37,7 +37,7 @@ RX_EDIT_EXPORT bool SaveScene(ecs::World& world, const std::string& file_path, // rejected strict load creates no entities. The editor stays lenient so a scene // saved by a build that had extra components still opens. RX_EDIT_EXPORT bool LoadScene(ecs::World& world, asset::AssetDatabase& db, - const std::string& file_path, std::string* error = nullptr, + const base::String& file_path, base::String* error = nullptr, bool strict = false); } // namespace rx::edit diff --git a/engine/edit/selection.cc b/engine/edit/selection.cc index 65ef945e..0ca18baf 100644 --- a/engine/edit/selection.cc +++ b/engine/edit/selection.cc @@ -1,6 +1,6 @@ #include "edit/selection.h" -#include +#include "base/algorithm.h" namespace rx::edit { @@ -27,7 +27,7 @@ void Selection::Add(ecs::Entity entity) { void Selection::Toggle(ecs::Entity entity) { if (!entity) return; - auto it = std::find(entities_.begin(), entities_.end(), entity); + auto it = base::Find(entities_.begin(), entities_.end(), entity); if (it != entities_.end()) { entities_.erase(it); if (primary_ == entity) primary_ = entities_.empty() ? ecs::kInvalidEntity : entities_.back(); @@ -38,7 +38,7 @@ void Selection::Toggle(ecs::Entity entity) { } bool Selection::Contains(ecs::Entity entity) const { - return std::find(entities_.begin(), entities_.end(), entity) != entities_.end(); + return base::Find(entities_.begin(), entities_.end(), entity) != entities_.end(); } } // namespace rx::edit diff --git a/engine/edit/selection.h b/engine/edit/selection.h index a9108321..9a6bf81b 100644 --- a/engine/edit/selection.h +++ b/engine/edit/selection.h @@ -1,9 +1,9 @@ #ifndef RX_EDIT_SELECTION_H_ #define RX_EDIT_SELECTION_H_ -#include -#include +#include "base/containers/span.h" +#include "base/containers/vector.h" #include "core/export.h" #include "ecs/entity.h" @@ -25,12 +25,12 @@ class RX_EDIT_EXPORT Selection { bool Contains(ecs::Entity entity) const; ecs::Entity primary() const { return primary_; } - std::span entities() const { return entities_; } + base::Span entities() const { return entities_; } bool empty() const { return entities_.empty(); } size_t size() const { return entities_.size(); } private: - std::vector entities_; + base::Vector entities_; ecs::Entity primary_{}; }; diff --git a/engine/edit/undo.cc b/engine/edit/undo.cc index f51652b7..333e730e 100644 --- a/engine/edit/undo.cc +++ b/engine/edit/undo.cc @@ -1,8 +1,13 @@ #include "edit/undo.h" -#include -#include - +#include "base/containers/pair.h" +#include "base/containers/vector.h" +#include "base/external/xoshiro256ss/xoshiro256ss.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/random/random.h" +#include "base/strings/xstring.h" +#include "core/format.h" #include "core/math.h" #include "edit/hierarchy.h" #include "scene/components.h" @@ -11,7 +16,10 @@ namespace rx::edit { namespace { u64 RandomGuid() { - static thread_local std::mt19937_64 rng{std::random_device{}()}; + // Guids only need to be unique, not reproducible: a per-thread 64-bit + // generator seeded once from the OS keeps every draw full width without a + // syscall per id. + static thread_local base::xoshiro256ss rng{base::SourceTrueRandomSeed()}; u64 v = 0; while (v == 0) v = rng(); @@ -23,11 +31,11 @@ u64 RandomGuid() { // they survive the target being destroyed and recreated. struct CompSnap { const ComponentDesc *comp; - std::vector> props; + base::Vector> props; }; -std::vector SnapshotEntity(ecs::World &world, ecs::Entity entity) { - std::vector out; +base::Vector SnapshotEntity(ecs::World &world, ecs::Entity entity) { + base::Vector out; for (const ComponentDesc *comp : ComponentsOn(world, entity)) { CompSnap snap{comp, {}}; for (u32 i = 0; i < comp->prop_count; ++i) { @@ -39,15 +47,15 @@ std::vector SnapshotEntity(ecs::World &world, ecs::Entity entity) { v.u = (v.e && world.IsAlive(v.e)) ? EnsureGuid(world, v.e) : 0; // stash target guid } - snap.props.emplace_back(&prop, std::move(v)); + snap.props.emplace_back(&prop, base::move(v)); } - out.push_back(std::move(snap)); + out.push_back(base::move(snap)); } return out; } void RestoreComponents(ecs::World &world, ecs::Entity entity, - const std::vector &snaps) { + const base::Vector &snaps) { for (const CompSnap &snap : snaps) { AddComponentByDesc(world, entity, *snap.comp); for (const auto &[prop, value] : snap.props) { @@ -89,10 +97,10 @@ class SetPropCommand : public Command { SetPropCommand(ecs::World &world, ecs::Entity entity, const ComponentDesc &comp, const PropDesc &prop, PropValue new_value) - : comp_(&comp), prop_(&prop), new_value_(std::move(new_value)) { + : comp_(&comp), prop_(&prop), new_value_(base::move(new_value)) { guid_ = EnsureGuid(world, entity); GetProp(world, entity, comp, prop, &old_value_); - label_ = std::format("Set {}.{}", comp.name, prop.name); + label_ = rx::StrFormat("Set {}.{}", comp.name, prop.name); } void Apply(ecs::World &world) override { if (ecs::Entity e = FindByGuid(world, guid_)) @@ -110,18 +118,18 @@ class SetPropCommand : public Command { const PropDesc *prop_; PropValue old_value_; PropValue new_value_; - std::string label_; + base::String label_; }; class CreateEntityCommand : public Command { public: CreateEntityCommand( - std::vector< - std::pair>>> + base::Vector< + base::Pair>>> initial, ecs::Entity *out) - : initial_(std::move(initial)), out_(out), guid_(RandomGuid()) {} + : initial_(base::move(initial)), out_(out), guid_(RandomGuid()) {} void Apply(ecs::World &world) override { ecs::Entity e = world.Create(); @@ -147,8 +155,8 @@ class CreateEntityCommand : public Command { const char *label() const override { return "Create entity"; } private: - std::vector>>> + base::Vector>>> initial_; ecs::Entity *out_; u64 guid_; @@ -173,7 +181,7 @@ class DestroyEntityCommand : public Command { private: u64 guid_; - std::vector snapshot_; + base::Vector snapshot_; }; class ReparentCommand : public Command { @@ -239,7 +247,7 @@ class AddComponentCommand : public Command { : comp_(&comp) { guid_ = EnsureGuid(world, entity); existed_ = world.HasRaw(entity, comp.id); - label_ = std::format("Add {}", comp.name); + label_ = rx::StrFormat("Add {}", comp.name); } void Apply(ecs::World &world) override { if (existed_) @@ -259,7 +267,7 @@ class AddComponentCommand : public Command { u64 guid_; const ComponentDesc *comp_; bool existed_; - std::string label_; + base::String label_; }; class RemoveComponentCommand : public Command { @@ -277,10 +285,10 @@ class RemoveComponentCommand : public Command { continue; if (prop.type == PropType::kEntity) v.u = (v.e && world.IsAlive(v.e)) ? EnsureGuid(world, v.e) : 0; - props_.emplace_back(&prop, std::move(v)); + props_.emplace_back(&prop, base::move(v)); } } - label_ = std::format("Remove {}", comp.name); + label_ = rx::StrFormat("Remove {}", comp.name); } void Apply(ecs::World &world) override { if (!existed_) @@ -308,15 +316,15 @@ class RemoveComponentCommand : public Command { u64 guid_; const ComponentDesc *comp_; bool existed_; - std::vector> props_; - std::string label_; + base::Vector> props_; + base::String label_; }; class CompositeCommand : public Command { public: - CompositeCommand(std::vector> children, - std::string label) - : children_(std::move(children)), label_(std::move(label)) {} + CompositeCommand(base::Vector> children, + base::String label) + : children_(base::move(children)), label_(base::move(label)) {} void Apply(ecs::World &world) override { for (auto &c : children_) c->Apply(world); @@ -328,26 +336,26 @@ class CompositeCommand : public Command { const char *label() const override { return label_.c_str(); } private: - std::vector> children_; - std::string label_; + base::Vector> children_; + base::String label_; }; } // namespace -void UndoStack::Push(ecs::World &world, std::unique_ptr cmd) { +void UndoStack::Push(ecs::World &world, base::UniquePointer cmd) { if (!cmd) return; cmd->Apply(world); - RecordApplied(std::move(cmd)); + RecordApplied(base::move(cmd)); } -void UndoStack::RecordApplied(std::unique_ptr cmd) { +void UndoStack::RecordApplied(base::UniquePointer cmd) { if (!cmd) return; if (group_depth_ > 0) { - group_buffer_.push_back(std::move(cmd)); + group_buffer_.push_back(base::move(cmd)); } else { - undo_.push_back(std::move(cmd)); + undo_.push_back(base::move(cmd)); redo_.clear(); } } @@ -355,20 +363,20 @@ void UndoStack::RecordApplied(std::unique_ptr cmd) { bool UndoStack::Undo(ecs::World &world) { if (undo_.empty()) return false; - std::unique_ptr cmd = std::move(undo_.back()); + base::UniquePointer cmd = base::move(undo_.back()); undo_.pop_back(); cmd->Revert(world); - redo_.push_back(std::move(cmd)); + redo_.push_back(base::move(cmd)); return true; } bool UndoStack::Redo(ecs::World &world) { if (redo_.empty()) return false; - std::unique_ptr cmd = std::move(redo_.back()); + base::UniquePointer cmd = base::move(redo_.back()); redo_.pop_back(); cmd->Apply(world); - undo_.push_back(std::move(cmd)); + undo_.push_back(base::move(cmd)); return true; } @@ -383,7 +391,7 @@ void UndoStack::EndGroup() { if (group_depth_ == 0) return; if (--group_depth_ == 0 && !group_buffer_.empty()) { - undo_.push_back(std::make_unique(std::move(group_buffer_), + undo_.push_back(base::MakeUnique(base::move(group_buffer_), group_label_)); group_buffer_.clear(); redo_.clear(); @@ -399,41 +407,41 @@ void UndoStack::Clear() { // Factories -std::unique_ptr MakeSetProp(ecs::World &world, ecs::Entity entity, +base::UniquePointer MakeSetProp(ecs::World &world, ecs::Entity entity, const ComponentDesc &comp, const PropDesc &prop, PropValue new_value) { - return std::make_unique(world, entity, comp, prop, - std::move(new_value)); + return base::MakeUnique(world, entity, comp, prop, + base::move(new_value)); } -std::unique_ptr MakeCreateEntity( - std::vector>>> +base::UniquePointer MakeCreateEntity( + base::Vector>>> initial, ecs::Entity *out_entity) { - return std::make_unique(std::move(initial), out_entity); + return base::MakeUnique(base::move(initial), out_entity); } -std::unique_ptr MakeDestroyEntity(ecs::World &world, +base::UniquePointer MakeDestroyEntity(ecs::World &world, ecs::Entity entity) { - return std::make_unique(world, entity); + return base::MakeUnique(world, entity); } -std::unique_ptr MakeReparent(ecs::World &world, ecs::Entity entity, +base::UniquePointer MakeReparent(ecs::World &world, ecs::Entity entity, ecs::Entity new_parent) { - return std::make_unique(world, entity, new_parent); + return base::MakeUnique(world, entity, new_parent); } -std::unique_ptr MakeAddComponent(ecs::World &world, ecs::Entity entity, +base::UniquePointer MakeAddComponent(ecs::World &world, ecs::Entity entity, const ComponentDesc &comp) { - return std::make_unique(world, entity, comp); + return base::MakeUnique(world, entity, comp); } -std::unique_ptr MakeRemoveComponent(ecs::World &world, +base::UniquePointer MakeRemoveComponent(ecs::World &world, ecs::Entity entity, const ComponentDesc &comp) { - return std::make_unique(world, entity, comp); + return base::MakeUnique(world, entity, comp); } } // namespace rx::edit diff --git a/engine/edit/undo.h b/engine/edit/undo.h index 87d85fd5..ba5c3e5f 100644 --- a/engine/edit/undo.h +++ b/engine/edit/undo.h @@ -1,11 +1,10 @@ #ifndef RX_EDIT_UNDO_H_ #define RX_EDIT_UNDO_H_ -#include -#include -#include -#include - +#include "base/containers/pair.h" +#include "base/containers/vector.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "ecs/entity.h" #include "ecs/world.h" @@ -30,11 +29,11 @@ class RX_EDIT_EXPORT UndoStack { public: // Applies `cmd` immediately, then records it (clearing the redo stack). // Inside a group the command is applied and buffered into the group. - void Push(ecs::World &world, std::unique_ptr cmd); + void Push(ecs::World &world, base::UniquePointer cmd); // Records an interaction that already updated its target live (for example a // terrain brush stroke). Undo calls Revert; redo calls Apply as usual. - void RecordApplied(std::unique_ptr cmd); + void RecordApplied(base::UniquePointer cmd); bool Undo(ecs::World &world); bool Redo(ecs::World &world); @@ -58,15 +57,15 @@ class RX_EDIT_EXPORT UndoStack { size_t size() const { return undo_.size(); } private: - std::vector> undo_; - std::vector> redo_; - std::vector> group_buffer_; - std::string group_label_; + base::Vector> undo_; + base::Vector> redo_; + base::Vector> group_buffer_; + base::String group_label_; int group_depth_ = 0; }; // Sets a single reflected field. Captures the current value for Revert. -RX_EDIT_EXPORT std::unique_ptr +RX_EDIT_EXPORT base::UniquePointer MakeSetProp(ecs::World &world, ecs::Entity entity, const ComponentDesc &comp, const PropDesc &prop, PropValue new_value); @@ -74,30 +73,30 @@ MakeSetProp(ecs::World &world, ecs::Entity entity, const ComponentDesc &comp, // entity's Guid is stable across undo/redo; *out_entity (may be null) receives // the live handle from the initial Apply inside UndoStack::Push only; a stack // address is safe, later undo/redo never writes through it. -RX_EDIT_EXPORT std::unique_ptr MakeCreateEntity( - std::vector>>> +RX_EDIT_EXPORT base::UniquePointer MakeCreateEntity( + base::Vector>>> initial, ecs::Entity *out_entity); // Destroys an entity; snapshots all reflected components and their props (and // its Guid) so Revert recreates it faithfully. -RX_EDIT_EXPORT std::unique_ptr MakeDestroyEntity(ecs::World &world, +RX_EDIT_EXPORT base::UniquePointer MakeDestroyEntity(ecs::World &world, ecs::Entity entity); // Reparents an entity, preserving its world transform. An invalid new_parent // unparents it. -RX_EDIT_EXPORT std::unique_ptr +RX_EDIT_EXPORT base::UniquePointer MakeReparent(ecs::World &world, ecs::Entity entity, ecs::Entity new_parent); // Adds a default-constructed component (no-op if already present). Revert // removes it (only if this command added it). -RX_EDIT_EXPORT std::unique_ptr +RX_EDIT_EXPORT base::UniquePointer MakeAddComponent(ecs::World &world, ecs::Entity entity, const ComponentDesc &comp); // Removes a component; snapshots its props so Revert restores them. -RX_EDIT_EXPORT std::unique_ptr +RX_EDIT_EXPORT base::UniquePointer MakeRemoveComponent(ecs::World &world, ecs::Entity entity, const ComponentDesc &comp); diff --git a/engine/http/http.cc b/engine/http/http.cc index eaaa8421..8d52584e 100644 --- a/engine/http/http.cc +++ b/engine/http/http.cc @@ -1,9 +1,9 @@ #include "http/http.h" -#include -#include +#include #include +#include "base/memory/unique_pointer.h" #include "http/stream.h" namespace rx::http { @@ -58,7 +58,7 @@ base::String Trim(const base::String& text) { base::String Decimal(u64 value) { char buffer[24] = {}; - std::snprintf(buffer, sizeof(buffer), "%llu", static_cast(value)); + ::snprintf(buffer, sizeof(buffer), "%llu", static_cast(value)); return base::String(buffer); } @@ -267,7 +267,7 @@ Response Exchange(const Request& request, const StreamLimits& limits) { Response response; - std::unique_ptr stream; + base::UniquePointer stream; if (url.tls) { TlsOptions options; options.ca_file = request.ca_file; @@ -551,8 +551,7 @@ Response Fetch(const Request& request) { ? 1u : (request.timeout_ms > 3600000u ? 3600000u : request.timeout_ms); if (request.total_timeout_ms > 0) { - limits.deadline = std::chrono::steady_clock::now() + - std::chrono::milliseconds(request.total_timeout_ms); + limits.deadline = base::TimeTicks::Now() + base::Milliseconds(request.total_timeout_ms); } limits.cancel = request.cancel; diff --git a/engine/http/http.h b/engine/http/http.h index 4bb66996..44a13629 100644 --- a/engine/http/http.h +++ b/engine/http/http.h @@ -4,8 +4,8 @@ #include #include -#include +#include "base/atomic.h" #include "core/export.h" #include "core/types.h" #include "http/url.h" @@ -51,7 +51,7 @@ struct RX_HTTP_EXPORT Request { // second, including mid-handshake and mid-body. The flag has to outlive the // call. This is how a worker thread is made joinable on demand: without it, // "run it on a worker" means "wait for the timeout to quit". - const std::atomic* cancel = nullptr; + const base::Atomic* cancel = nullptr; // A response larger than this fails instead of growing the heap. Raise it // deliberately for an endpoint known to answer with more. u32 max_body_bytes = 4u * 1024 * 1024; diff --git a/engine/http/stream.h b/engine/http/stream.h index fc5f9eca..c1caa877 100644 --- a/engine/http/stream.h +++ b/engine/http/stream.h @@ -3,10 +3,9 @@ #include -#include -#include -#include - +#include "base/atomic.h" +#include "base/memory/unique_pointer.h" +#include "base/time/time.h" #include "core/types.h" namespace rx::http { @@ -19,18 +18,18 @@ struct StreamLimits { // Idle timeout: how long the peer may say NOTHING before the exchange fails. // A server that keeps sending keeps the call alive; a stalled one does not. u32 idle_ms = 10'000; - // Wall-clock end of the whole exchange, connect and body included. Default - // constructed (the epoch) means no overall bound, only the idle timeout. - std::chrono::steady_clock::time_point deadline{}; + // Wall-clock end of the whole exchange, connect and body included. Null + // (default constructed) means no overall bound, only the idle timeout. + base::TimeTicks deadline; // Raised by the caller to abandon the exchange. Owned by the caller, read // from this thread, so it has to outlive the call. - const std::atomic* cancel = nullptr; + const base::Atomic* cancel = nullptr; bool has_deadline() const { - return deadline != std::chrono::steady_clock::time_point{}; + return !deadline.is_null(); } bool cancelled() const { - return cancel != nullptr && cancel->load(std::memory_order_relaxed); + return cancel != nullptr && cancel->load(base::memory_order_relaxed); } // Milliseconds left before the deadline, or -1 when there is none. 0 means // it has passed. @@ -76,11 +75,11 @@ struct TlsOptions { bool verify_peer = true; }; -std::unique_ptr MakeTcpStream(); +base::UniquePointer MakeTcpStream(); // Null when the build has no TLS backend compiled in (RX_HTTP_TLS off), which // is what turns an https URL into a clear error instead of a silent downgrade. -std::unique_ptr MakeTlsStream(const TlsOptions& options); +base::UniquePointer MakeTlsStream(const TlsOptions& options); } // namespace rx::http diff --git a/engine/http/tcp_stream.cc b/engine/http/tcp_stream.cc index 2d3f96e2..4ae588a1 100644 --- a/engine/http/tcp_stream.cc +++ b/engine/http/tcp_stream.cc @@ -1,3 +1,4 @@ +#include "base/memory/unique_pointer.h" #include "http/stream.h" #ifdef _WIN32 @@ -16,20 +17,16 @@ #include #endif -#include -#include -#include -#include +#include +#include namespace rx::http { i64 StreamLimits::remaining_ms() const { if (!has_deadline()) return -1; - const auto left = std::chrono::duration_cast( - deadline - std::chrono::steady_clock::now()) - .count(); - return left > 0 ? static_cast(left) : 0; + const i64 left = (deadline - base::TimeTicks::Now()).InMilliseconds(); + return left > 0 ? left : 0; } namespace { @@ -84,11 +81,12 @@ void SetTimeouts(SocketHandle s, u32 timeout_ms) { // in rx guarantees it ran: zetanet does its own, and rx::http builds without // zetanet. void EnsureSocketLibrary() { - static std::once_flag once; - std::call_once(once, [] { + static const bool started = [] { WSADATA data{}; ::WSAStartup(MAKEWORD(2, 2), &data); - }); + return true; + }(); + (void)started; } #else using SocketHandle = int; @@ -154,10 +152,10 @@ base::String SocketError(const char* what, int err) { if (text[i] == '\r' || text[i] == '\n') text[i] = ' '; } - std::snprintf(buffer, sizeof(buffer), "%s: %s (%d)", what, + ::snprintf(buffer, sizeof(buffer), "%s: %s (%d)", what, written != 0 ? text : "socket error", err); #else - std::snprintf(buffer, sizeof(buffer), "%s: %s (%d)", what, std::strerror(err), err); + ::snprintf(buffer, sizeof(buffer), "%s: %s (%d)", what, ::strerror(err), err); #endif return base::String(buffer); } @@ -179,7 +177,7 @@ class TcpStream final : public Stream { return false; char service[8] = {}; - std::snprintf(service, sizeof(service), "%u", static_cast(port)); + ::snprintf(service, sizeof(service), "%u", static_cast(port)); addrinfo hints{}; hints.ai_family = AF_UNSPEC; @@ -189,7 +187,7 @@ class TcpStream final : public Stream { const int rc = ::getaddrinfo(host.c_str(), service, &hints, &resolved); if (rc != 0 || resolved == nullptr) { char buffer[256] = {}; - std::snprintf(buffer, sizeof(buffer), "cannot resolve %s: %s", host.c_str(), + ::snprintf(buffer, sizeof(buffer), "cannot resolve %s: %s", host.c_str(), rc != 0 ? ::gai_strerror(rc) : "the resolver returned no addresses"); *error = base::String(buffer); if (resolved != nullptr) @@ -217,7 +215,7 @@ class TcpStream final : public Stream { } ::freeaddrinfo(resolved); char buffer[320] = {}; - std::snprintf(buffer, sizeof(buffer), "cannot connect to %s:%u (%s)", host.c_str(), + ::snprintf(buffer, sizeof(buffer), "cannot connect to %s:%u (%s)", host.c_str(), static_cast(port), last.c_str()); *error = base::String(buffer); return false; @@ -304,11 +302,10 @@ class TcpStream final : public Stream { return false; } - void MarkProgress() { last_progress_ = std::chrono::steady_clock::now(); } + void MarkProgress() { last_progress_ = base::TimeTicks::Now(); } u32 Idle() const { - const auto since = std::chrono::steady_clock::now() - last_progress_; - const auto ms = std::chrono::duration_cast(since).count(); + const i64 ms = (base::TimeTicks::Now() - last_progress_).InMilliseconds(); return ms < 0 ? 0u : static_cast(ms); } @@ -352,8 +349,7 @@ class TcpStream final : public Stream { // cover it), so a process with a periodic timer would see a spurious // "connect failed". Wait in ticks instead, which also gives the cancel // flag and the deadline a look in between. - const auto give_up = - std::chrono::steady_clock::now() + std::chrono::milliseconds(limits_.idle_ms); + const base::TimeTicks give_up = base::TimeTicks::Now() + base::Milliseconds(limits_.idle_ms); int ready = 0; for (;;) { ready = PollWritable(sock, static_cast(tick_ms)); @@ -367,7 +363,7 @@ class TcpStream final : public Stream { CloseSocket(sock); return false; } - if (std::chrono::steady_clock::now() >= give_up) { + if (base::TimeTicks::Now() >= give_up) { ready = 0; break; } @@ -397,14 +393,14 @@ class TcpStream final : public Stream { SocketHandle socket_ = kInvalidSocket; StreamLimits limits_; - std::chrono::steady_clock::time_point last_progress_ = std::chrono::steady_clock::now(); + base::TimeTicks last_progress_ = base::TimeTicks::Now(); bool cancelled_ = false; }; } // namespace -std::unique_ptr MakeTcpStream() { - return std::make_unique(); +base::UniquePointer MakeTcpStream() { + return base::MakeUnique(); } } // namespace rx::http diff --git a/engine/http/tls_stream.cc b/engine/http/tls_stream.cc index 8d769302..c06d0835 100644 --- a/engine/http/tls_stream.cc +++ b/engine/http/tls_stream.cc @@ -25,11 +25,11 @@ // clang-format on #endif -#include -#include -#include -#include +#include +#include +#include +#include "base/memory/unique_pointer.h" #include "core/log.h" namespace rx::http { @@ -48,19 +48,20 @@ void TraceMbed(void*, int level, const char* file, int line, const char* message // application has to start exactly once before the first handshake. Harmless // on a 3.x build that only negotiates 1.2. void EnsurePsaCrypto() { - static std::once_flag once; - std::call_once(once, [] { + static const bool started = [] { const psa_status_t status = psa_crypto_init(); if (status != PSA_SUCCESS) RX_WARN("http: psa_crypto_init failed ({}), tls handshakes may fail", int(status)); - }); + return true; + }(); + (void)started; } base::String MbedError(const char* what, int code) { char text[128] = {}; mbedtls_strerror(code, text, sizeof(text)); char buffer[256] = {}; - std::snprintf(buffer, sizeof(buffer), "%s: %s (-0x%04x)", what, text, + ::snprintf(buffer, sizeof(buffer), "%s: %s (-0x%04x)", what, text, static_cast(-code)); return base::String(buffer); } @@ -133,8 +134,9 @@ bool LoadTrustStore(mbedtls_x509_crt* chain, // request. Falling through to the system store would hand an operator who // pinned one CA the full public root set instead, with a warning as the only // sign of it. - for (const char* key : {"RX_HTTP_CA_FILE", "SSL_CERT_FILE"}) { - const char* env = std::getenv(key); + static const char* const kCaFileKeys[] = {"RX_HTTP_CA_FILE", "SSL_CERT_FILE"}; + for (const char* key : kCaFileKeys) { + const char* env = ::getenv(key); if (env == nullptr || *env == '\0') continue; if (load(chain, env)) @@ -202,7 +204,7 @@ class TlsStream final : public Stream { const int seeded = mbedtls_ctr_drbg_seed( &drbg_, mbedtls_entropy_func, &entropy_, reinterpret_cast(personalization), - std::strlen(personalization)); + ::strlen(personalization)); if (seeded != 0) { *error = MbedError("cannot seed the rng", seeded); return false; @@ -260,7 +262,7 @@ class TlsStream final : public Stream { char info[512] = {}; mbedtls_x509_crt_verify_info(info, sizeof(info), "", flags); char buffer[640] = {}; - std::snprintf(buffer, sizeof(buffer), "certificate rejected for %s: %s", + ::snprintf(buffer, sizeof(buffer), "certificate rejected for %s: %s", host.c_str(), info); *error = base::String(buffer); return false; @@ -338,7 +340,7 @@ class TlsStream final : public Stream { } TlsOptions options_; - std::unique_ptr inner_; + base::UniquePointer inner_; base::String inner_error_; // the socket's reason for the last BIO failure bool handshaked_ = false; mbedtls_ssl_context ssl_{}; @@ -352,8 +354,8 @@ class TlsStream final : public Stream { } // namespace -std::unique_ptr MakeTlsStream(const TlsOptions& options) { - return std::make_unique(options); +base::UniquePointer MakeTlsStream(const TlsOptions& options) { + return base::MakeUnique(options); } } // namespace rx::http @@ -362,7 +364,7 @@ std::unique_ptr MakeTlsStream(const TlsOptions& options) { namespace rx::http { -std::unique_ptr MakeTlsStream(const TlsOptions&) { +base::UniquePointer MakeTlsStream(const TlsOptions&) { return nullptr; } diff --git a/engine/inventory/byte_io.h b/engine/inventory/byte_io.h index 4931948b..b7c24135 100644 --- a/engine/inventory/byte_io.h +++ b/engine/inventory/byte_io.h @@ -1,9 +1,10 @@ #ifndef RX_INVENTORY_BYTE_IO_H_ #define RX_INVENTORY_BYTE_IO_H_ -#include -#include +#include +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" #include "core/types.h" // Minimal explicit-little-endian byte writer/reader shared by the serializers. @@ -11,24 +12,24 @@ namespace rx::inventory::detail { -inline void PutU8(std::vector& b, u8 v) { b.push_back(v); } +inline void PutU8(base::Vector& b, u8 v) { b.push_back(v); } -inline void PutU16(std::vector& b, u16 v) { +inline void PutU16(base::Vector& b, u16 v) { b.push_back(u8(v)); b.push_back(u8(v >> 8)); } -inline void PutU32(std::vector& b, u32 v) { +inline void PutU32(base::Vector& b, u32 v) { for (int i = 0; i < 4; ++i) b.push_back(u8(v >> (8 * i))); } -inline void PutU64(std::vector& b, u64 v) { +inline void PutU64(base::Vector& b, u64 v) { for (int i = 0; i < 8; ++i) b.push_back(u8(v >> (8 * i))); } -inline void PutF32(std::vector& b, f32 v) { +inline void PutF32(base::Vector& b, f32 v) { u32 u; - std::memcpy(&u, &v, 4); + base::MemCopy(&u, &v, 4); PutU32(b, u); } @@ -39,7 +40,7 @@ struct Reader { const u8* end; bool ok = true; - explicit Reader(const std::vector& b) : p(b.data()), end(b.data() + b.size()) {} + explicit Reader(const base::Vector& b) : p(b.data()), end(b.data() + b.size()) {} // Bytes not yet consumed. Lets callers bound a serialized count against the // input before reserving/allocating, and check for full consumption. @@ -84,7 +85,7 @@ struct Reader { f32 F32() { u32 u = U32(); f32 v; - std::memcpy(&v, &u, 4); + base::MemCopy(&v, &u, 4); return v; } }; diff --git a/engine/inventory/components.h b/engine/inventory/components.h index eaf7e423..d263397a 100644 --- a/engine/inventory/components.h +++ b/engine/inventory/components.h @@ -1,8 +1,8 @@ #ifndef RX_INVENTORY_COMPONENTS_H_ #define RX_INVENTORY_COMPONENTS_H_ -#include +#include "base/containers/vector.h" #include "core/types.h" #include "inventory/item_catalog.h" @@ -23,7 +23,7 @@ struct InventoryEntry { // capacity and the revision counter stay consistent. Non-POD (owns a vector); // the ECS relocates it through its move constructor, which is supported. struct Inventory { - std::vector entries; + base::Vector entries; f32 max_weight = 0; // 0 == unlimited u32 max_entries = 0; // 0 == unlimited (max distinct non-empty stacks) u32 revision = 0; // bumped on every change; UIs diff against it @@ -44,7 +44,7 @@ struct EquipmentSlot { // created on demand by Equip; games may also pre-populate `slots` with the tags // they support. struct Equipment { - std::vector slots; + base::Vector slots; u32 revision = 0; }; diff --git a/engine/inventory/item_catalog.cc b/engine/inventory/item_catalog.cc index e15886e0..bd4d8382 100644 --- a/engine/inventory/item_catalog.cc +++ b/engine/inventory/item_catalog.cc @@ -2,23 +2,36 @@ namespace rx::inventory { +namespace { +// Overwrites in place, so a pointer from an earlier Find stays valid. +void Store(base::UnorderedMap>& defs, ItemDefId id, + const ItemDef& def) { + base::UniquePointer& slot = defs[id]; + if (slot) { + *slot = def; + } else { + slot = base::MakeUnique(def); + } +} +} // namespace + ItemDefId ItemCatalog::Register(const ItemDef& def) { while (next_id_ == kInvalidItemDef || defs_.count(next_id_) != 0) ++next_id_; ItemDefId id = next_id_++; - defs_[id] = def; + Store(defs_, id, def); return id; } ItemDefId ItemCatalog::Register(ItemDefId id, const ItemDef& def) { - defs_[id] = def; + Store(defs_, id, def); if (id != kInvalidItemDef && id >= next_id_) next_id_ = id + 1; return id; } const ItemDef* ItemCatalog::Find(ItemDefId id) const { if (id == kInvalidItemDef) return nullptr; - auto it = defs_.find(id); - return it == defs_.end() ? nullptr : &it->second; + const base::UniquePointer* found = defs_.find(id); + return found ? &**found : nullptr; } void ItemCatalog::Remove(ItemDefId id) { defs_.erase(id); } diff --git a/engine/inventory/item_catalog.h b/engine/inventory/item_catalog.h index 2da8c2d9..54c01400 100644 --- a/engine/inventory/item_catalog.h +++ b/engine/inventory/item_catalog.h @@ -1,9 +1,9 @@ #ifndef RX_INVENTORY_ITEM_CATALOG_H_ #define RX_INVENTORY_ITEM_CATALOG_H_ -#include - #include "asset/asset_id.h" +#include "base/containers/unordered_map.h" +#include "base/memory/unique_pointer.h" #include "core/types.h" #include "inventory/export.h" #include "physics/shape_desc.h" @@ -56,7 +56,9 @@ class RX_INVENTORY_EXPORT ItemCatalog { size_t size() const { return defs_.size(); } private: - std::unordered_map defs_; + // Boxed: Find promises a pointer that outlives later inserts, and the map + // moves its values when it grows. + base::UnorderedMap> defs_; ItemDefId next_id_ = 1; }; diff --git a/engine/inventory/serialize.cc b/engine/inventory/serialize.cc index 07460128..e4eae32e 100644 --- a/engine/inventory/serialize.cc +++ b/engine/inventory/serialize.cc @@ -1,7 +1,8 @@ #include "inventory/serialize.h" -#include - +#include "base/containers/unordered_map.h" +#include "base/containers/vector.h" +#include "base/memory/move.h" #include "ecs/world.h" #include "inventory/byte_io.h" #include "inventory/components.h" @@ -23,7 +24,7 @@ constexpr u32 kInventoryEntryBytes = 4 + 4 + 8; // item, count, payload constexpr u32 kEquipmentSlotBytes = 4 + 4 + 8 + 1; // tag, item, payload, occupied constexpr u32 kInventoryRecordMinBytes = 8 + 16 + 1; // guid, inventory header, has_eq -void WriteInventory(std::vector& b, const Inventory& inv) { +void WriteInventory(base::Vector& b, const Inventory& inv) { PutF32(b, inv.max_weight); PutU32(b, inv.max_entries); PutU32(b, inv.revision); @@ -61,7 +62,7 @@ Inventory ReadInventory(Reader& r) { return inv; } -void WriteEquipment(std::vector& b, const Equipment& eq) { +void WriteEquipment(base::Vector& b, const Equipment& eq) { PutU32(b, eq.revision); PutU32(b, u32(eq.slots.size())); for (const auto& s : eq.slots) { @@ -94,14 +95,14 @@ Equipment ReadEquipment(Reader& r) { } // namespace -std::vector SaveInventories(ecs::World& world) { +base::Vector SaveInventories(ecs::World& world) { struct Record { u64 guid; Inventory inv; bool has_eq; Equipment eq; }; - std::vector records; + base::Vector records; world.Each([&](ecs::Entity e, scene::Guid& guid, Inventory& inv) { Record rec; rec.guid = guid.value; @@ -109,10 +110,10 @@ std::vector SaveInventories(ecs::World& world) { Equipment* eq = world.Get(e); rec.has_eq = eq != nullptr; if (eq) rec.eq = *eq; - records.push_back(std::move(rec)); + records.push_back(base::move(rec)); }); - std::vector b; + base::Vector b; b.push_back(kMagic0); b.push_back(kMagic1); b.push_back(kMagic2); @@ -128,7 +129,7 @@ std::vector SaveInventories(ecs::World& world) { return b; } -bool LoadInventories(ecs::World& world, const std::vector& blob) { +bool LoadInventories(ecs::World& world, const base::Vector& blob) { Reader r(blob); if (r.U8() != kMagic0 || r.U8() != kMagic1 || r.U8() != kMagic2 || r.U8() != kMagic3) return false; if (r.U32() != kVersion) return false; @@ -145,7 +146,7 @@ bool LoadInventories(ecs::World& world, const std::vector& blob) { bool has_eq; Equipment eq; }; - std::vector parsed; + base::Vector parsed; parsed.reserve(count); for (u32 i = 0; i < count && r.ok; ++i) { Parsed p; @@ -154,32 +155,31 @@ bool LoadInventories(ecs::World& world, const std::vector& blob) { p.has_eq = r.U8() != 0; if (p.has_eq) p.eq = ReadEquipment(r); if (!r.ok) break; - parsed.push_back(std::move(p)); + parsed.push_back(base::move(p)); } if (!r.ok || r.Remaining() != 0) return false; - std::unordered_map by_guid; + base::UnorderedMap by_guid; world.Each([&](ecs::Entity e, scene::Guid& g) { by_guid[g.value] = e; }); for (auto& rec : parsed) { ecs::Entity e; - auto it = by_guid.find(rec.guid); - if (it != by_guid.end()) { - e = it->second; + if (const ecs::Entity* found = by_guid.find(rec.guid)) { + e = *found; } else { e = world.Create(); world.Add(e, scene::Guid{rec.guid}); by_guid[rec.guid] = e; } if (world.Has(e)) - *world.Get(e) = std::move(rec.inv); + *world.Get(e) = base::move(rec.inv); else - world.Add(e, std::move(rec.inv)); + world.Add(e, base::move(rec.inv)); if (rec.has_eq) { if (world.Has(e)) - *world.Get(e) = std::move(rec.eq); + *world.Get(e) = base::move(rec.eq); else - world.Add(e, std::move(rec.eq)); + world.Add(e, base::move(rec.eq)); } } return true; diff --git a/engine/inventory/serialize.h b/engine/inventory/serialize.h index 6b26f262..f327e471 100644 --- a/engine/inventory/serialize.h +++ b/engine/inventory/serialize.h @@ -1,8 +1,8 @@ #ifndef RX_INVENTORY_SERIALIZE_H_ #define RX_INVENTORY_SERIALIZE_H_ -#include +#include "base/containers/vector.h" #include "core/types.h" #include "inventory/export.h" @@ -23,11 +23,11 @@ namespace rx::inventory { // its Equipment, if present) into a versioned blob, keyed by Guid so the data // survives ECS handle reuse across a save/load. Entities without a Guid are // skipped (a scene needs a stable identity to reattach state to). -RX_INVENTORY_EXPORT std::vector SaveInventories(ecs::World& world); +RX_INVENTORY_EXPORT base::Vector SaveInventories(ecs::World& world); // Restores a SaveInventories blob. Each record attaches to the live entity // whose scene::Guid matches; if none exists, a new entity is created carrying // that Guid. Returns false on a corrupt or unsupported blob. -RX_INVENTORY_EXPORT bool LoadInventories(ecs::World& world, const std::vector& blob); +RX_INVENTORY_EXPORT bool LoadInventories(ecs::World& world, const base::Vector& blob); } // namespace rx::inventory diff --git a/engine/inventory/world_item.cc b/engine/inventory/world_item.cc index 033cd8c0..7f872276 100644 --- a/engine/inventory/world_item.cc +++ b/engine/inventory/world_item.cc @@ -1,8 +1,10 @@ #include "inventory/world_item.h" -#include -#include +#include +#include "base/atomic.h" +#include "base/containers/vector.h" +#include "base/functional/function.h" #include "ecs/world.h" #include "inventory/byte_io.h" #include "inventory/inventory.h" @@ -16,7 +18,7 @@ using namespace detail; // The one piece of process-stateful module state: monotonic persistent-id // minting. Kept off the functional path (systems still take no globals) and // reconciled against loaded ids via ReserveWorldItemId. -std::atomic g_next_world_item_id{1}; +base::Atomic g_next_world_item_id{1}; // The physics layer exposes no body sleep-state or velocity query, so rest is // inferred from how little the body moves between syncs. A settled/sleeping @@ -79,7 +81,7 @@ constexpr u32 kTransformBytes = 12 + 16 + 4; // pos, rot, sc constexpr u32 kLiveRecordBytes = 8 + 4 + 4 + 8 + kTransformBytes + 1; // + at_rest flag constexpr u32 kDormantRecordBytes = 8 + 4 + 4 + 8 + kTransformBytes; -void WriteTransform(std::vector& b, const scene::Transform& t) { +void WriteTransform(base::Vector& b, const scene::Transform& t) { for (int i = 0; i < 3; ++i) PutF32(b, t.position[i]); for (int i = 0; i < 4; ++i) PutF32(b, t.rotation[i]); PutF32(b, t.scale); @@ -104,21 +106,9 @@ void ReserveWorldItemId(u64 seen_id) { } } -size_t WorldItemStore::CellKeyHash::operator()(const CellKey& key) const { - u64 h = 1469598103934665603ull; // FNV-1a 64 offset basis - auto mix = [&](i32 v) { - h ^= u32(v); - h *= 1099511628211ull; - }; - mix(key.x); - mix(key.y); - mix(key.z); - return size_t(h); -} - WorldItemStore::CellKey WorldItemStore::CellOf(const Vec3& p) const { - return CellKey{i32(std::floor(p.x / cell_size_)), i32(std::floor(p.y / cell_size_)), - i32(std::floor(p.z / cell_size_))}; + return CellKey{i32(::floor(p.x / cell_size_)), i32(::floor(p.y / cell_size_)), + i32(::floor(p.z / cell_size_))}; } void WorldItemStore::Insert(const WorldItemRecord& record) { @@ -128,25 +118,26 @@ void WorldItemStore::Insert(const WorldItemRecord& record) { ++count_; } -std::vector WorldItemStore::TakeNear(const Vec3& center, f32 radius) { - std::vector out; - const i32 span = i32(std::ceil(radius / cell_size_)); +base::Vector WorldItemStore::TakeNear(const Vec3& center, f32 radius) { + base::Vector out; + const i32 span = i32(::ceil(radius / cell_size_)); const CellKey c = CellOf(center); for (i32 z = c.z - span; z <= c.z + span; ++z) { for (i32 y = c.y - span; y <= c.y + span; ++y) { for (i32 x = c.x - span; x <= c.x + span; ++x) { - auto it = cells_.find(CellKey{x, y, z}); - if (it == cells_.end()) continue; - for (const auto& rec : it->second) out.push_back(rec); - count_ -= it->second.size(); - cells_.erase(it); + const CellKey key{x, y, z}; + const base::Vector* bucket = cells_.Find(key); + if (!bucket) continue; + for (const auto& rec : *bucket) out.push_back(rec); + count_ -= bucket->size(); + cells_.erase(key); } } } return out; } -void WorldItemStore::ForEach(const std::function& fn) const { +void WorldItemStore::ForEach(const base::Function& fn) const { for (const auto& [key, bucket] : cells_) for (const auto& rec : bucket) fn(rec); } @@ -225,7 +216,7 @@ void SyncWorldItems(ecs::World& world, physics::PhysicsWorld& physics) { // between the two unit quaternions (|dot| handles the double-cover sign). const f32 rdot = t.rotation[0] * rot[0] + t.rotation[1] * rot[1] + t.rotation[2] * rot[2] + t.rotation[3] * rot[3]; - const f32 ang_delta = 1.0f - std::fabs(rdot); + const f32 ang_delta = 1.0f - ::fabs(rdot); t.position[0] = pos.x; t.position[1] = pos.y; @@ -253,8 +244,8 @@ void HibernateDistantWorldItems(ecs::World& world, physics::PhysicsWorld& physic WorldItemStore& store, const Vec3& center, f32 radius) { const f32 r2 = radius * radius; // Collect first: mutating the ECS (Destroy) mid-Each can skip/revisit rows. - std::vector victims; - std::vector records; + base::Vector victims; + base::Vector records; world.Each([&](ecs::Entity e, scene::Transform& t, WorldItem& wi) { if (!wi.at_rest) return; const f32 dx = t.position[0] - center.x; @@ -278,7 +269,7 @@ void HibernateDistantWorldItems(ecs::World& world, physics::PhysicsWorld& physic void WakeWorldItemsNear(ecs::World& world, physics::PhysicsWorld& physics, const ItemCatalog& catalog, WorldItemStore& store, const Vec3& center, f32 radius) { - std::vector woken = store.TakeNear(center, radius); + base::Vector woken = store.TakeNear(center, radius); for (const auto& rec : woken) { const ItemDef* def = catalog.Find(rec.item); ecs::Entity e = MaterializeWorldItem(world, physics, def, rec.transform, rec.item, rec.count, @@ -292,7 +283,7 @@ void WakeWorldItemsNear(ecs::World& world, physics::PhysicsWorld& physics, // persistence (world items) -std::vector SaveWorldItems(ecs::World& world, const WorldItemStore& store) { +base::Vector SaveWorldItems(ecs::World& world, const WorldItemStore& store) { struct Live { scene::Transform transform; ItemDefId item; @@ -301,14 +292,14 @@ std::vector SaveWorldItems(ecs::World& world, const WorldItemStore& store) { u64 persistent_id; u8 at_rest; }; - std::vector live; + base::Vector live; world.Each([&](ecs::Entity, scene::Transform& t, WorldItem& wi) { live.push_back(Live{t, wi.item, wi.count, wi.payload, wi.persistent_id, u8(wi.at_rest ? 1 : 0)}); }); - std::vector dormant; + base::Vector dormant; store.ForEach([&](const WorldItemRecord& rec) { dormant.push_back(rec); }); - std::vector b; + base::Vector b; b.push_back(kMagic0); b.push_back(kMagic1); b.push_back(kMagic2); @@ -336,7 +327,7 @@ std::vector SaveWorldItems(ecs::World& world, const WorldItemStore& store) { } bool LoadWorldItems(ecs::World& world, physics::PhysicsWorld& physics, const ItemCatalog& catalog, - WorldItemStore& store, const std::vector& blob) { + WorldItemStore& store, const base::Vector& blob) { Reader r(blob); if (r.U8() != kMagic0 || r.U8() != kMagic1 || r.U8() != kMagic2 || r.U8() != kMagic3) return false; if (r.U32() != kVersion) return false; @@ -347,7 +338,7 @@ bool LoadWorldItems(ecs::World& world, physics::PhysicsWorld& physics, const Ite // leave the world and store untouched, not half-loaded. u32 live = r.U32(); if (!r.ok || live > r.Remaining() / kLiveRecordBytes) return false; - std::vector live_recs; + base::Vector live_recs; live_recs.reserve(live); for (u32 i = 0; i < live && r.ok; ++i) { WorldItemRecord rec; @@ -363,7 +354,7 @@ bool LoadWorldItems(ecs::World& world, physics::PhysicsWorld& physics, const Ite u32 dormant = r.U32(); if (!r.ok || dormant > r.Remaining() / kDormantRecordBytes) return false; - std::vector dorm_recs; + base::Vector dorm_recs; dorm_recs.reserve(dormant); for (u32 i = 0; i < dormant && r.ok; ++i) { WorldItemRecord rec; diff --git a/engine/inventory/world_item.h b/engine/inventory/world_item.h index 4c6b6e79..fac4653d 100644 --- a/engine/inventory/world_item.h +++ b/engine/inventory/world_item.h @@ -1,10 +1,9 @@ #ifndef RX_INVENTORY_WORLD_ITEM_H_ #define RX_INVENTORY_WORLD_ITEM_H_ -#include -#include -#include - +#include "base/containers/map.h" +#include "base/containers/vector.h" +#include "base/functional/function.h" #include "core/math.h" #include "core/types.h" #include "ecs/entity.h" @@ -52,9 +51,9 @@ class RX_INVENTORY_WORLD_EXPORT WorldItemStore { // Removes and returns every record whose cell overlaps the query sphere. The // cell granularity means a few records just outside `radius` may come back; // that is harmless (they re-hibernate next sweep). - std::vector TakeNear(const Vec3& center, f32 radius); + base::Vector TakeNear(const Vec3& center, f32 radius); // Visits every record (for serialization). The callback must not mutate. - void ForEach(const std::function& fn) const; + void ForEach(const base::Function& fn) const; size_t size() const { return count_; } void Clear(); @@ -62,13 +61,17 @@ class RX_INVENTORY_WORLD_EXPORT WorldItemStore { struct CellKey { i32 x = 0, y = 0, z = 0; bool operator==(const CellKey&) const = default; - }; - struct CellKeyHash { - size_t operator()(const CellKey& key) const; + bool operator<(const CellKey& o) const { + if (x != o.x) return x < o.x; + if (y != o.y) return y < o.y; + return z < o.z; + } }; CellKey CellOf(const Vec3& p) const; - std::unordered_map, CellKeyHash> cells_; + // Ordered, so ForEach (and the save it feeds) walks cells in key order: the + // bytes then follow from the records alone, not from a hash table's layout. + base::Map> cells_; f32 cell_size_ = 32.0f; size_t count_ = 0; }; @@ -133,13 +136,13 @@ RX_INVENTORY_WORLD_EXPORT void ReserveWorldItemId(u64 seen_id); // Serializes every live WorldItem entity AND every record in `store` into one // versioned little-endian blob; live/dormant status is preserved. -RX_INVENTORY_WORLD_EXPORT std::vector SaveWorldItems(ecs::World& world, +RX_INVENTORY_WORLD_EXPORT base::Vector SaveWorldItems(ecs::World& world, const WorldItemStore& store); // Restores a SaveWorldItems blob: live records become entities + bodies, // dormant records go into `store`. Returns false on a corrupt/unsupported blob. RX_INVENTORY_WORLD_EXPORT bool LoadWorldItems(ecs::World& world, physics::PhysicsWorld& physics, const ItemCatalog& catalog, WorldItemStore& store, - const std::vector& blob); + const base::Vector& blob); } // namespace rx::inventory diff --git a/engine/locomotion/controller.cc b/engine/locomotion/controller.cc index 668b13b3..c1b22e11 100644 --- a/engine/locomotion/controller.cc +++ b/engine/locomotion/controller.cc @@ -15,11 +15,11 @@ #include "locomotion/controller.h" -#include -#include -#include -#include +#include +#include +#include +#include "core/scalar.h" #include "locomotion/internal_math.h" #include "locomotion/whole_body.h" #include "physics/physics_world.h" @@ -30,7 +30,7 @@ namespace { // Angle (rad) between two directions, both assumed roughly unit-length. f32 AngleBetween(const Vec3& a, const Vec3& b) { - return std::acos(Clampf(Dot(a, b), -1.0f, 1.0f)); + return ::acos(Clampf(Dot(a, b), -1.0f, 1.0f)); } } // namespace @@ -69,7 +69,7 @@ void LocomotionController::ApplyJointDrive(RigJoint j, f32 torque) { // SetJointDrive walks Jolt constraint settings, so re-issue it only when the // effective torque budget moved > 1%. const f32 ref = applied_torque_[idx] > 1e-3f ? applied_torque_[idx] : 1.0f; - if (std::fabs(torque - applied_torque_[idx]) > 0.01f * ref) { + if (::fabs(torque - applied_torque_[idx]) > 0.01f * ref) { rig_.SetJointDrive(*physics_, j, params_.joint_frequency, params_.joint_damping, torque); applied_torque_[idx] = torque; } @@ -175,9 +175,9 @@ void LocomotionController::Tick(const LocomotionIntent& intent, const PhysicalMo if (FiniteV(requested_facing) && Length(requested_facing) > 1e-4f) { requested_facing = Normalize(requested_facing); const f32 turn_error = - std::atan2(Cross(controlled_facing_, requested_facing).y, + ::atan2(Cross(controlled_facing_, requested_facing).y, Clampf(Dot(controlled_facing_, requested_facing), -1.0f, 1.0f)); - const f32 turn_rate = FiniteScalar(params_.max_turn_rate) ? std::max(params_.max_turn_rate, 0.0f) + const f32 turn_rate = FiniteScalar(params_.max_turn_rate) ? rx::Max(params_.max_turn_rate, 0.0f) : 0.0f; const f32 turn_step = Clampf(turn_error, -turn_rate * dt, turn_rate * dt); controlled_facing_ = @@ -222,7 +222,7 @@ void LocomotionController::Tick(const LocomotionIntent& intent, const PhysicalMo // a lead of ~desired_velocity/omega ahead of the support (that IS forward // locomotion, not a fall); subtracting that lead keeps `margin` near zero while // walking and only grows on genuine, uncommanded imbalance. - const f32 omega = std::sqrt(m.gravity / com_height); + const f32 omega = ::sqrt(m.gravity / com_height); const Vec3 expected_lead = Planar(resolved_intent.desired_velocity) * (1.0f / omega); const f32 margin = PlanarLength(Planar(cp) - Planar(contacts_.support_center) - expected_lead); @@ -514,9 +514,9 @@ void LocomotionController::Tick(const LocomotionIntent& intent, const PhysicalMo debug_.mode_time = mode_time_; // Optional env-gated trace (cheap, matches the debug-visibility requirement). - static const bool kTrace = std::getenv("RX_LOCO_DEBUG") != nullptr; + static const bool kTrace = ::getenv("RX_LOCO_DEBUG") != nullptr; if (kTrace && tick_count_ % 10 == 0) { - std::fprintf(stderr, + ::fprintf(stderr, "[loco] t=%llu mode=%d blend=%.2f pelvis_y=%.3f com=(%.2f,%.2f,%.2f) vz=%.2f " "cp_margin=%.3f tilt=%.2f phase=%.2f sup=%u ph0=%.2f/%d ph1=%.2f/%d " "f0z=%.2f tgt0z=%.2f f1z=%.2f tgt1z=%.2f\n", diff --git a/engine/locomotion/controller.h b/engine/locomotion/controller.h index 5afdcb87..e089ecc4 100644 --- a/engine/locomotion/controller.h +++ b/engine/locomotion/controller.h @@ -57,7 +57,7 @@ class RX_LOCOMOTION_EXPORT LocomotionController { private: // Downward terrain probe wrapping physics_->Raycast, passed to the footstep // planner. `context` is the controller (this), so it reaches physics_ without - // a std::function or captured state. + // a base::Function or captured state. static bool GroundProbe(void* context, const Vec3& probe_start, f32 max_depth, GroundHit* out); // True when `watched` (pelvis/torso) touches something that is not part of the diff --git a/engine/locomotion/estimator.cc b/engine/locomotion/estimator.cc index 766e89b6..7c444ea2 100644 --- a/engine/locomotion/estimator.cc +++ b/engine/locomotion/estimator.cc @@ -4,7 +4,8 @@ #include "locomotion/estimator.h" -#include +#include +#include #include "locomotion/internal_math.h" @@ -129,11 +130,11 @@ void StateEstimator::Measure(const physics::PhysicsWorld& physics, const BipedRi bool finite = FiniteV(out->root_position) && FiniteQ(out->root_rotation) && FiniteV(out->root_linear_velocity) && FiniteV(out->root_angular_velocity) && FiniteV(out->com_position) && FiniteV(out->com_velocity) && - FiniteV(out->ground_normal) && std::isfinite(out->estimated_body_height); + FiniteV(out->ground_normal) && ::isfinite(out->estimated_body_height); for (u32 f = 0; f < kFootCount; ++f) { const FootMeasurement& fm = out->foot[f]; finite = finite && FiniteV(fm.position) && FiniteV(fm.velocity) && FiniteV(fm.contact_normal) && - std::isfinite(fm.contact_impulse) && std::isfinite(fm.slip_speed); + ::isfinite(fm.contact_impulse) && ::isfinite(fm.slip_speed); } if (ok && finite) { @@ -166,7 +167,7 @@ void ContactEstimator::Update(const CharacterMeasurements& m, f32 dt) { if (!fm.in_contact) { raw = FootPhase::kSwinging; } else if (fm.slip_speed >= 0.6f || fm.contact_normal.y <= 0.6f || - std::abs(fm.velocity.y) >= 0.35f) { + ::abs(fm.velocity.y) >= 0.35f) { raw = FootPhase::kSliding; } else { raw = FootPhase::kSupporting; diff --git a/engine/locomotion/footstep.cc b/engine/locomotion/footstep.cc index f4c1c941..1d53d8d9 100644 --- a/engine/locomotion/footstep.cc +++ b/engine/locomotion/footstep.cc @@ -4,7 +4,7 @@ #include "locomotion/footstep.h" -#include +#include #include "locomotion/gait.h" #include "locomotion/internal_math.h" @@ -31,7 +31,7 @@ Vec3 ResolveFacing(const LocomotionIntent& intent, const Quat& root_rotation) { // rolls the ankle past its limit). Quat FootOrientation(const Vec3& normal, const Vec3& facing) { Vec3 n = Length(normal) > 1e-5f ? Normalize(normal) : kUp; - const f32 angle = std::acos(Clampf(n.y, -1, 1)); + const f32 angle = ::acos(Clampf(n.y, -1, 1)); Quat tilt{0, 0, 0, 1}; if (angle > 1e-4f) { Vec3 axis = Cross(kUp, n); @@ -46,7 +46,7 @@ Quat FootOrientation(const Vec3& normal, const Vec3& facing) { Vec3 CapturePoint(const Vec3& com_position, const Vec3& com_velocity, f32 gravity, f32 com_height) { const f32 h = com_height < 0.1f ? 0.1f : com_height; - const f32 omega = std::sqrt((gravity > 0 ? gravity : 9.81f) / h); + const f32 omega = ::sqrt((gravity > 0 ? gravity : 9.81f) / h); if (!(omega > 0)) return com_position; return com_position + com_velocity * (1.0f / omega); } @@ -72,7 +72,7 @@ void FootstepPlanner::Update(const CharacterMeasurements& m, const ContactEstima (void)dt; // the planner is memoryless per tick beyond the lift-off latch const Vec3 facing = ResolveFacing(intent, m.root_rotation); const Vec3 right = Normalize(Cross(facing, kUp)); // character right (+X at yaw 0) - const f32 cos_slope = std::cos(params.max_ground_slope); + const f32 cos_slope = ::cos(params.max_ground_slope); for (u32 foot = 0; foot < kFootCount; ++foot) { FootPlan& p = plan_[foot]; diff --git a/engine/locomotion/footstep.h b/engine/locomotion/footstep.h index 8251f472..74bdc6cc 100644 --- a/engine/locomotion/footstep.h +++ b/engine/locomotion/footstep.h @@ -30,7 +30,7 @@ struct GroundHit { // Terrain probe supplied by the controller (wraps a physics raycast). Probes // straight down from `probe_start` for up to `max_depth` metres; returns true // and fills `*out` on a hit, false when nothing is within reach. `context` is -// the opaque pointer passed to FootstepPlanner::Update; no std::function, no +// the opaque pointer passed to FootstepPlanner::Update; no base::Function, no // captured state, so the planner stays allocation- and heap-free. using GroundProbeFn = bool (*)(void* context, const Vec3& probe_start, f32 max_depth, GroundHit* out); diff --git a/engine/locomotion/gait.cc b/engine/locomotion/gait.cc index 3ffab46a..1345b4a8 100644 --- a/engine/locomotion/gait.cc +++ b/engine/locomotion/gait.cc @@ -4,7 +4,7 @@ #include "locomotion/gait.h" -#include +#include #include "locomotion/internal_math.h" @@ -18,8 +18,8 @@ constexpr f32 kSpeedRatioTau = 0.15f; } // namespace f32 Wrap01(f32 x) { - if (!std::isfinite(x)) return 0; - x -= std::floor(x); + if (!::isfinite(x)) return 0; + x -= ::floor(x); if (x < 0) x += 1; // guard tiny-negative rounding if (x >= 1) x -= 1; // guard rounding up to exactly 1 return x; diff --git a/engine/locomotion/internal_math.h b/engine/locomotion/internal_math.h index ae5d9f1d..199deece 100644 --- a/engine/locomotion/internal_math.h +++ b/engine/locomotion/internal_math.h @@ -8,7 +8,7 @@ // copy of a routine that used to be re-implemented, with drifting names, in // several files. Behaviour is bit-identical to those originals. -#include +#include #include "core/math.h" #include "core/types.h" @@ -16,12 +16,12 @@ namespace rx::locomotion::internal { // Finiteness predicates. -inline bool FiniteScalar(f32 x) { return std::isfinite(x); } +inline bool FiniteScalar(f32 x) { return ::isfinite(x); } inline bool FiniteV(const Vec3& v) { - return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z); + return ::isfinite(v.x) && ::isfinite(v.y) && ::isfinite(v.z); } inline bool FiniteQ(const Quat& q) { - return std::isfinite(q.x) && std::isfinite(q.y) && std::isfinite(q.z) && std::isfinite(q.w); + return ::isfinite(q.x) && ::isfinite(q.y) && ::isfinite(q.z) && ::isfinite(q.w); } // Clamp a scalar to [lo, hi]. @@ -34,7 +34,7 @@ inline f32 Lerpf(f32 a, f32 b, f32 t) { return a + (b - a) * t; } inline Vec3 Planar(const Vec3& v) { return {v.x, 0.0f, v.z}; } // Planar (x,z) magnitude of a world vector, ignoring the vertical component. -inline f32 PlanarLength(const Vec3& v) { return std::sqrt(v.x * v.x + v.z * v.z); } +inline f32 PlanarLength(const Vec3& v) { return ::sqrt(v.x * v.x + v.z * v.z); } // Clamp a vector's length to `max_len` (no-op below it; safe for the zero vec). inline Vec3 ClampLength(const Vec3& v, f32 max_len) { @@ -47,7 +47,7 @@ inline Vec3 ClampLength(const Vec3& v, f32 max_len) { // 0 for dt <= 0 or tau <= 0 (holds the current value); -> 1 for large dt. inline f32 SmoothingAlpha(f32 dt, f32 tau) { if (!(dt > 0) || !(tau > 0)) return 0; - return 1.0f - std::exp(-dt / tau); + return 1.0f - ::exp(-dt / tau); } } // namespace rx::locomotion::internal diff --git a/engine/locomotion/rig.cc b/engine/locomotion/rig.cc index 144be5fb..6c486af4 100644 --- a/engine/locomotion/rig.cc +++ b/engine/locomotion/rig.cc @@ -17,7 +17,7 @@ #include "locomotion/rig.h" -#include +#include #include "core/math.h" @@ -208,8 +208,8 @@ bool BipedRig::Build(physics::PhysicsWorld& physics, const ControllerParameters& // Yaw about -Y, matching rx::character::HeadingQuat(yaw) = // QuatFromAxisAngle({0, -1, 0}, yaw) = {0, -sin(yaw/2), 0, cos(yaw/2)}. - const f32 hy = std::sin(yaw * 0.5f); - const f32 hcw = std::cos(yaw * 0.5f); + const f32 hy = ::sin(yaw * 0.5f); + const f32 hcw = ::cos(yaw * 0.5f); const Quat yawq{0, -hy, 0, hcw}; const f32 rot[4] = {0, -hy, 0, hcw}; auto world = [&](const Vec3& local) { return feet_position + Rotate(yawq, local); }; diff --git a/engine/locomotion/whole_body.cc b/engine/locomotion/whole_body.cc index 9cd5a1cc..91beb913 100644 --- a/engine/locomotion/whole_body.cc +++ b/engine/locomotion/whole_body.cc @@ -5,7 +5,7 @@ #include "locomotion/whole_body.h" -#include +#include #include "locomotion/internal_math.h" @@ -20,7 +20,7 @@ constexpr f32 kPi = 3.14159265358979f; Quat ClampCone(Quat q, f32 max_angle) { q = Normalize(q); if (q.w < 0.0f) q = {-q.x, -q.y, -q.z, -q.w}; // pick the shortest hemisphere - f32 angle = 2.0f * std::acos(Clampf(q.w, -1.0f, 1.0f)); + f32 angle = 2.0f * ::acos(Clampf(q.w, -1.0f, 1.0f)); if (angle <= max_angle) return q; Vec3 axis{q.x, q.y, q.z}; if (Length(axis) < 1e-6f) return {0, 0, 0, 1}; @@ -72,7 +72,7 @@ LegIkResult SolveLegIk(const Vec3& hip_to_sole, const Vec3& sole_normal_parent, const f32 u = upper_length; const f32 l = lower_length; const f32 eps = 1e-4f; - const f32 d_min = std::fabs(u - l) + eps; + const f32 d_min = ::fabs(u - l) + eps; const f32 d_max = u + l - eps; f32 d = raw; @@ -91,7 +91,7 @@ LegIkResult SolveLegIk(const Vec3& hip_to_sole, const Vec3& sole_normal_parent, // folded (d = |u-l|) -> pi. Never negative. f32 cos_knee = (u * u + l * l - d * d) / (2.0f * u * l); cos_knee = Clampf(cos_knee, -1.0f, 1.0f); - r.knee_flexion = kPi - std::acos(cos_knee); + r.knee_flexion = kPi - ::acos(cos_knee); if (r.knee_flexion < 0.0f) r.knee_flexion = 0.0f; // Triangle angle at the hip between the thigh (bind -Y) and the hip->ankle @@ -104,7 +104,7 @@ LegIkResult SolveLegIk(const Vec3& hip_to_sole, const Vec3& sole_normal_parent, // where V0 is the bent chain's ankle in the pre-hip frame. f32 cos_alpha = (u * u + d * d - l * l) / (2.0f * u * d); cos_alpha = Clampf(cos_alpha, -1.0f, 1.0f); - const f32 alpha = std::acos(cos_alpha); + const f32 alpha = ::acos(cos_alpha); r.hip = QuatBetween(down_y, a_hat) * QuatFromAxisAngle(knee_axis, -alpha); // Ankle: after hip+knee, rotate the foot's bind normal (+Y) onto n. Solve in @@ -200,7 +200,7 @@ void BuildWholeBodyTargets(const CharacterMeasurements& m, const ContactEstimate solved_forward = Normalize(solved_forward); desired_forward = Normalize(desired_forward); const f32 yaw_error = - std::atan2(Cross(solved_forward, desired_forward).y, + ::atan2(Cross(solved_forward, desired_forward).y, Clampf(Dot(solved_forward, desired_forward), -1.0f, 1.0f)); ankle = ClampCone(ankle * QuatFromAxisAngle(up_y, yaw_error), 0.5f); } @@ -229,8 +229,8 @@ void BuildWholeBodyTargets(const CharacterMeasurements& m, const ContactEstimate // 5. Arms: gait-phase counter-swing (arm opposes the same-side leg) const f32 speed_ratio = Clampf(gait.speed_ratio, 0.0f, 4.0f); - const f32 swing_l = 0.45f * speed_ratio * std::cos(2.0f * kPi * GaitClock::FootPhase(gait, 1)); - const f32 swing_r = 0.45f * speed_ratio * std::cos(2.0f * kPi * GaitClock::FootPhase(gait, 0)); + const f32 swing_l = 0.45f * speed_ratio * ::cos(2.0f * kPi * GaitClock::FootPhase(gait, 1)); + const f32 swing_r = 0.45f * speed_ratio * ::cos(2.0f * kPi * GaitClock::FootPhase(gait, 0)); const f32 elbow_flex = 0.25f + 0.25f * speed_ratio; out->joint_target[static_cast(RigJoint::kShoulderL)] = QuatFromAxisAngle({1, 0, 0}, swing_l); out->joint_target[static_cast(RigJoint::kShoulderR)] = QuatFromAxisAngle({1, 0, 0}, swing_r); @@ -275,7 +275,7 @@ void BuildWholeBodyTargets(const CharacterMeasurements& m, const ContactEstimate const Vec3 up_target = Rotate(q_d, up_y); const Vec3 cross = Cross(up_world, up_target); const f32 sin_a = Length(cross); - const f32 angle = std::acos(Clampf(Dot(up_world, up_target), -1.0f, 1.0f)); + const f32 angle = ::acos(Clampf(Dot(up_world, up_target), -1.0f, 1.0f)); const Vec3 axis = sin_a > 1e-6f ? cross * (1.0f / sin_a) : Vec3{0, 0, 0}; assist_torque = axis * (params.torso_orientation_gain * angle) - m.root_angular_velocity * params.torso_angular_damping; diff --git a/engine/nav/agent.cc b/engine/nav/agent.cc index ce7adb3b..428efca5 100644 --- a/engine/nav/agent.cc +++ b/engine/nav/agent.cc @@ -1,7 +1,8 @@ #include "nav/agent.h" -#include +#include +#include "core/scalar.h" #include "scene/components.h" namespace rx::nav { @@ -14,7 +15,7 @@ Vec3 ReadPosition(const scene::Transform& t) { f32 PlanarDist(const Vec3& a, const Vec3& b) { const f32 dx = a.x - b.x; const f32 dz = a.z - b.z; - return std::sqrt(dx * dx + dz * dz); + return ::sqrt(dx * dx + dz * dz); } // Face the move direction: yaw-only quaternion around +Y. The engine is @@ -22,11 +23,11 @@ f32 PlanarDist(const Vec3& a, const Vec3& b) { void FaceVelocity(scene::Transform& t, const Vec3& velocity) { const f32 planar = velocity.x * velocity.x + velocity.z * velocity.z; if (planar < 1e-6f) return; - const f32 yaw = std::atan2(velocity.x, velocity.z); + const f32 yaw = ::atan2(velocity.x, velocity.z); t.rotation[0] = 0; - t.rotation[1] = std::sin(yaw * 0.5f); + t.rotation[1] = ::sin(yaw * 0.5f); t.rotation[2] = 0; - t.rotation[3] = std::cos(yaw * 0.5f); + t.rotation[3] = ::cos(yaw * 0.5f); } } // namespace @@ -94,12 +95,12 @@ void UpdateAgents(ecs::World& world, NavMesh& mesh, const AgentUpdateConfig& con } agent.corner = corner; const Vec3 to_corner{corner.x - pos.x, 0, corner.z - pos.z}; - const f32 dist = std::sqrt(Dot(to_corner, to_corner)); + const f32 dist = ::sqrt(Dot(to_corner, to_corner)); if (dist < 1e-4f) { agent.status = AgentStatus::kMoving; return; } - const f32 step_speed = std::min(agent.speed, dist / std::max(dt, 1e-4f)); + const f32 step_speed = rx::Min(agent.speed, dist / rx::Max(dt, 1e-4f)); agent.velocity = to_corner * (step_speed / dist); agent.status = AgentStatus::kMoving; diff --git a/engine/nav/nav_debug.cc b/engine/nav/nav_debug.cc index a77ea24d..a63ed438 100644 --- a/engine/nav/nav_debug.cc +++ b/engine/nav/nav_debug.cc @@ -1,6 +1,6 @@ #include "nav/nav_debug.h" -#include +#include namespace rx::nav { namespace { @@ -26,7 +26,7 @@ u32 AreaColor(AreaId area) { return kAreaColors[area & 7]; } void AppendNavMeshLines(const NavMesh& mesh, const Vec3& center, f32 radius, base::Vector* out) { const f32 cs = mesh.config().cell_size; - const i32 r = static_cast(std::ceil(radius / cs)); + const i32 r = static_cast(::ceil(radius / cs)); const CellRef mid = mesh.CellAt(center); // Line endpoints take the interpolated surface height so edges hug slopes // instead of burying their downhill half in the ground. diff --git a/engine/nav/navmesh.cc b/engine/nav/navmesh.cc index d703883f..9229bebe 100644 --- a/engine/nav/navmesh.cc +++ b/engine/nav/navmesh.cc @@ -1,7 +1,9 @@ +#include "core/scalar.h" #include "nav/navmesh.h" +#include "core/sort.h" -#include -#include +#include +#include namespace rx::nav { @@ -14,8 +16,8 @@ NavMesh::NavMesh(const NavMeshConfig& config) : config_(config) { void NavMesh::SetAreaCost(AreaId area, f32 traverse_multiplier, f32 entry_cost) { if (area >= kMaxAreas) return; - traverse_mult_[area] = std::max(traverse_multiplier, 0.01f); - entry_cost_[area] = std::max(entry_cost, 0.0f); + traverse_mult_[area] = rx::Max(traverse_multiplier, 0.01f); + entry_cost_[area] = rx::Max(entry_cost, 0.0f); } bool NavMesh::BuildTile(i32 tx, i32 tz, SampleFn& sample) { @@ -43,10 +45,10 @@ bool NavMesh::BuildTile(i32 tx, i32 tz, SampleFn& sample) { u32 NavMesh::EnsureBubble(const Vec3& center, f32 radius, SampleFn& sample, u32 max_tiles) { const f32 tile_m = config_.cell_size * static_cast(config_.tile_cells); - const i32 t0x = static_cast(std::floor((center.x - radius) / tile_m)); - const i32 t1x = static_cast(std::floor((center.x + radius) / tile_m)); - const i32 t0z = static_cast(std::floor((center.z - radius) / tile_m)); - const i32 t1z = static_cast(std::floor((center.z + radius) / tile_m)); + const i32 t0x = static_cast(::floor((center.x - radius) / tile_m)); + const i32 t1x = static_cast(::floor((center.x + radius) / tile_m)); + const i32 t0z = static_cast(::floor((center.z - radius) / tile_m)); + const i32 t1z = static_cast(::floor((center.z + radius) / tile_m)); // Missing tiles, then nearest-first so the ground under the agents exists // before the bubble rim does. @@ -66,8 +68,9 @@ u32 NavMesh::EnsureBubble(const Vec3& center, f32 radius, SampleFn& sample, u32 missing.push_back({tx, tz, d2}); } } - std::sort(missing.begin(), missing.end(), - [](const Want& a, const Want& b) { return a.dist2 < b.dist2; }); + // Stable: symmetric tiles tie, and max_tiles cuts the list. + rx::StableSort(missing.data(), missing.data() + missing.size(), + [](const Want& a, const Want& b) { return a.dist2 < b.dist2; }); u32 built = 0; for (const Want& w : missing) { if (built >= max_tiles) break; @@ -93,10 +96,10 @@ u32 NavMesh::RemoveTilesBeyond(const Vec3& center, f32 radius) { void NavMesh::PaintDisc(const Vec3& center, f32 radius, AreaId area) { if (area >= kMaxAreas) return; const f32 cs = config_.cell_size; - const i32 c0x = static_cast(std::floor((center.x - radius) / cs)); - const i32 c1x = static_cast(std::floor((center.x + radius) / cs)); - const i32 c0z = static_cast(std::floor((center.z - radius) / cs)); - const i32 c1z = static_cast(std::floor((center.z + radius) / cs)); + const i32 c0x = static_cast(::floor((center.x - radius) / cs)); + const i32 c1x = static_cast(::floor((center.x + radius) / cs)); + const i32 c0z = static_cast(::floor((center.z - radius) / cs)); + const i32 c1z = static_cast(::floor((center.z + radius) / cs)); const i32 n = static_cast(config_.tile_cells); u64 last_tile = ~0ull; Tile* tile = nullptr; @@ -170,13 +173,13 @@ u32 NavMesh::TileVersionByKey(u64 key) const { CellRef NavMesh::CellAt(const Vec3& pos) const { const f32 cs = config_.cell_size; - return {static_cast(std::floor(pos.x / cs)), static_cast(std::floor(pos.z / cs))}; + return {static_cast(::floor(pos.x / cs)), static_cast(::floor(pos.z / cs))}; } Vec3 NavMesh::CellCenter(CellRef cell) const { const f32 cs = config_.cell_size; f32 y = CellHeight(cell); - if (std::isnan(y)) y = 0; + if (::isnan(y)) y = 0; return {(static_cast(cell.x) + 0.5f) * cs, y, (static_cast(cell.z) + 0.5f) * cs}; } @@ -198,8 +201,8 @@ bool NavMesh::HeightAt(f32 x, f32 z, f32* out_height) const { // Sample space where integer coordinates sit on cell centers. const f32 sx = x / cs - 0.5f; const f32 sz = z / cs - 0.5f; - const i32 x0 = static_cast(std::floor(sx)); - const i32 z0 = static_cast(std::floor(sz)); + const i32 x0 = static_cast(::floor(sx)); + const i32 z0 = static_cast(::floor(sz)); const f32 fx = sx - static_cast(x0); const f32 fz = sz - static_cast(z0); f32 h[4]; @@ -208,7 +211,7 @@ bool NavMesh::HeightAt(f32 x, f32 z, f32* out_height) const { f32 weight_sum = 0, height_sum = 0; for (int i = 0; i < 4; ++i) { h[i] = CellHeight(cells[i]); - if (std::isnan(h[i])) continue; + if (::isnan(h[i])) continue; weight_sum += w[i]; height_sum += h[i] * w[i]; } @@ -222,13 +225,13 @@ bool NavMesh::Reachable(CellRef from, CellRef to) const { const Tile* ta = TileOf(from, &ia); const Tile* tb = TileOf(to, &ib); if (!ta || !tb || ta->area[ia] == kAreaNone || tb->area[ib] == kAreaNone) return false; - return std::fabs(ta->height[ia] - tb->height[ib]) <= config_.max_step; + return ::fabs(ta->height[ia] - tb->height[ib]) <= config_.max_step; } CellRef NavMesh::ClampToWalkable(const Vec3& pos, f32 max_radius) const { const CellRef at = CellAt(pos); if (Walkable(at)) return at; - const i32 rings = static_cast(std::ceil(max_radius / config_.cell_size)); + const i32 rings = static_cast(::ceil(max_radius / config_.cell_size)); // Ring scan outward; within a ring prefer the cell nearest to pos. for (i32 r = 1; r <= rings; ++r) { CellRef best; @@ -279,7 +282,7 @@ NavRaycast NavMesh::Raycast(const Vec3& from, const Vec3& to) const { // Amanatides & Woo traversal over the cell grid. const f32 dx = to.x - from.x; const f32 dz = to.z - from.z; - const f32 len = std::sqrt(dx * dx + dz * dz); + const f32 len = ::sqrt(dx * dx + dz * dz); const i32 step_x = dx > 0 ? 1 : -1; const i32 step_z = dz > 0 ? 1 : -1; const f32 inv_dx = dx != 0 ? 1.0f / dx : 0; @@ -293,16 +296,16 @@ NavRaycast NavMesh::Raycast(const Vec3& from, const Vec3& to) const { AreaId prev_area = Area(cell); // Generous bound: the traversal visits at most dx+dz+1 cells. const i32 max_steps = - std::abs(end.x - cell.x) + std::abs(end.z - cell.z) + 2; + ::abs(end.x - cell.x) + ::abs(end.z - cell.z) + 2; for (i32 i = 0; i < max_steps && !(cell == end); ++i) { CellRef next = cell; f32 t; if (t_max_x < t_max_z) { - t = std::min(t_max_x, 1.0f); + t = rx::Min(t_max_x, 1.0f); next.x += step_x; t_max_x += t_delta_x; } else { - t = std::min(t_max_z, 1.0f); + t = rx::Min(t_max_z, 1.0f); next.z += step_z; t_max_z += t_delta_z; } diff --git a/engine/nav/navmesh.h b/engine/nav/navmesh.h index b1a0799a..06ff5935 100644 --- a/engine/nav/navmesh.h +++ b/engine/nav/navmesh.h @@ -13,7 +13,7 @@ // integer cell grid; tiles group tile_cells^2 cells and are versioned, which is // how corridors notice the world changed under them (path.h, RepathReason). -#include +#include #include #include diff --git a/engine/nav/path.cc b/engine/nav/path.cc index 066c7eab..267ec1e8 100644 --- a/engine/nav/path.cc +++ b/engine/nav/path.cc @@ -1,8 +1,10 @@ +#include "base/algorithm.h" +#include "core/scalar.h" #include "nav/path.h" -#include -#include -#include +#include +#include +#include namespace rx::nav { namespace { @@ -21,14 +23,14 @@ f32 TriArea2(const Vec3& a, const Vec3& b, const Vec3& c) { f32 PlanarDist(const Vec3& a, const Vec3& b) { const f32 dx = a.x - b.x; const f32 dz = a.z - b.z; - return std::sqrt(dx * dx + dz * dz); + return ::sqrt(dx * dx + dz * dz); } f32 Octile(CellRef a, CellRef b, f32 cell_size) { - const f32 dx = std::fabs(static_cast(a.x - b.x)); - const f32 dz = std::fabs(static_cast(a.z - b.z)); - const f32 lo = std::min(dx, dz); - const f32 hi = std::max(dx, dz); + const f32 dx = ::fabs(static_cast(a.x - b.x)); + const f32 dz = ::fabs(static_cast(a.z - b.z)); + const f32 lo = rx::Min(dx, dz); + const f32 hi = rx::Max(dx, dz); return (hi + 0.41421356f * lo) * cell_size; } @@ -41,7 +43,7 @@ void HeapPush(PathScratch& s, u32 node) { const PathScratch::Node& a = s.nodes[s.heap[i]]; const PathScratch::Node& b = s.nodes[s.heap[parent]]; if (a.g + a.h >= b.g + b.h) break; - std::swap(s.heap[i], s.heap[parent]); + base::Swap(s.heap[i], s.heap[parent]); i = parent; } } @@ -63,7 +65,7 @@ u32 HeapPop(PathScratch& s) { if (l < count && f(l) < f(smallest)) smallest = l; if (r < count && f(r) < f(smallest)) smallest = r; if (smallest == i) break; - std::swap(s.heap[i], s.heap[smallest]); + base::Swap(s.heap[i], s.heap[smallest]); i = smallest; } return top; @@ -121,7 +123,7 @@ bool TraceCells(const NavMesh& mesh, const Vec3& from, const Vec3& to, f32 t_max_z = dz != 0 ? ((static_cast(cell.z + (step_z > 0)) * cs) - from.z) * inv_dz : 2.0f; const f32 t_delta_x = dx != 0 ? cs * static_cast(step_x) * inv_dx : 0; const f32 t_delta_z = dz != 0 ? cs * static_cast(step_z) * inv_dz : 0; - const i32 max_steps = std::abs(end.x - cell.x) + std::abs(end.z - cell.z) + 2; + const i32 max_steps = ::abs(end.x - cell.x) + ::abs(end.z - cell.z) + 2; for (i32 i = 0; i < max_steps && !(cell == end); ++i) { CellRef next = cell; if (t_max_x < t_max_z) { @@ -151,7 +153,7 @@ void ShortcutPass(const NavMesh& mesh, base::Vector* cells, u32 max_spa while (i + 1 < count) { u32 chosen = i + 1; if (i + 2 < count) { - const u32 far = std::min(i + max_span, count - 1); + const u32 far = rx::Min(i + max_span, count - 1); // Corridor cost of the stretch being challenged. f32 stretch_cost = 0; prefix.clear(); @@ -230,7 +232,7 @@ Portal MakePortal(const NavMesh& mesh, CellRef from, CellRef to, f32 radius) { // clearance belongs in cell walkability (paint margins around obstacles), // not in portals a third the agent's width. const f32 half_len = cs * 0.5f; - const f32 t = std::min(radius, half_len * 0.33f) / half_len; + const f32 t = rx::Min(radius, half_len * 0.33f) / half_len; portal.left = Lerp(portal.left, mid, t); portal.right = Lerp(portal.right, mid, t); return portal; @@ -261,7 +263,7 @@ bool AdvanceProgress(const NavMesh& mesh, Corridor* corridor, const Vec3& agent_ const u32 lo = corridor->progress > 1 ? corridor->progress - 1 : 0; for (u32 k = lo; k < count && k <= corridor->progress + window; ++k) { const CellRef c = corridor->cells[k]; - const i32 d = std::max(std::abs(c.x - at.x), std::abs(c.z - at.z)); + const i32 d = rx::Max(::abs(c.x - at.x), ::abs(c.z - at.z)); // Prefer the farthest matching cell so progress is monotonic even when // the agent straddles several corridor cells. if (d <= best_d && d <= 1) { @@ -270,7 +272,7 @@ bool AdvanceProgress(const NavMesh& mesh, Corridor* corridor, const Vec3& agent_ } } if (best == UINT32_MAX) return false; - corridor->progress = std::max(corridor->progress, best); + corridor->progress = rx::Max(corridor->progress, best); corridor->entered = true; return true; } @@ -361,7 +363,7 @@ PathStatus FindPath(const NavMesh& mesh, const PathRequest& request, PathScratch for (i32 walk = static_cast(last); walk >= 0; walk = scratch.nodes[walk].parent) { out->cells.push_back(scratch.nodes[walk].cell); } - std::reverse(out->cells.begin(), out->cells.end()); + base::Reverse(out->cells.begin(), out->cells.end()); out->cost = scratch.nodes[last].g; out->status = complete ? PathStatus::kComplete : PathStatus::kPartial; // A partial "path" that never left the start cell helps nobody: fail so the @@ -384,9 +386,9 @@ void ShortcutCorridor(const NavMesh& mesh, Corridor* corridor, u32 max_span) { // Second sweep from the far end: reverse, straighten, restore. Entry // costs are direction-sensitive, so the reversed comparison is // approximate; the epsilon in ShortcutPass keeps it conservative. - std::reverse(corridor->cells.begin(), corridor->cells.end()); + base::Reverse(corridor->cells.begin(), corridor->cells.end()); ShortcutPass(mesh, &corridor->cells, max_span, &line, &scratch); - std::reverse(corridor->cells.begin(), corridor->cells.end()); + base::Reverse(corridor->cells.begin(), corridor->cells.end()); corridor->cost = CorridorCost(mesh, corridor->cells); } StampTiles(mesh, corridor); diff --git a/engine/nav/query.cc b/engine/nav/query.cc index 3e6c230e..28071ed3 100644 --- a/engine/nav/query.cc +++ b/engine/nav/query.cc @@ -1,8 +1,10 @@ +#include "base/algorithm.h" +#include "base/containers/span.h" +#include "core/scalar.h" #include "nav/query.h" -#include -#include -#include +#include +#include namespace rx::nav { namespace { @@ -26,7 +28,7 @@ struct FloodHeap { while (i > 0) { const u32 p = (i - 1) / 2; if (items[i].weighted >= items[p].weighted) break; - std::swap(items[i], items[p]); + base::Swap(items[i], items[p]); i = p; } } @@ -43,7 +45,7 @@ struct FloodHeap { if (l < count && items[l].weighted < items[m].weighted) m = l; if (r < count && items[r].weighted < items[m].weighted) m = r; if (m == i) break; - std::swap(items[i], items[m]); + base::Swap(items[i], items[m]); i = m; } return top; @@ -57,7 +59,7 @@ bool DiagonalAllowed(const NavMesh& mesh, CellRef from, CellRef to) { } // namespace size_t EvaluatePositions(const NavMesh& mesh, const PositionQueryParams& params, - std::span candidates) { + base::Span candidates) { const CellRef source = mesh.ClampToWalkable(params.origin, params.clamp_radius * 2.0f); for (PositionCandidate& c : candidates) c.reachable = false; if (!source.valid() || candidates.empty()) return SIZE_MAX; @@ -120,7 +122,7 @@ size_t EvaluatePositions(const NavMesh& mesh, const PositionQueryParams& params, // over the same time window", so an agent already in the river is pushed // toward the bank (staying put pads at water cost) instead of freezing // because every move would add delta. - const f32 pad = std::max(params.virtual_length - settled->raw, 0.0f); + const f32 pad = rx::Max(params.virtual_length - settled->raw, 0.0f); c.score = c.delta_cost + pad * (mesh.TraverseMultiplier(mesh.Area(cell)) - 1.0f); if (winner == SIZE_MAX || c.score < candidates[winner].score || (c.score == candidates[winner].score && c.raw_length < candidates[winner].raw_length)) { diff --git a/engine/nav/query.h b/engine/nav/query.h index fd740d0a..33d1f0d0 100644 --- a/engine/nav/query.h +++ b/engine/nav/query.h @@ -12,10 +12,10 @@ // that, an agent already standing in a river would rank "stand still" (delta // 0) above every move, since any path it can take touches water. -#include #include +#include "base/containers/span.h" #include "core/export.h" #include "nav/navmesh.h" #include "nav/path.h" @@ -44,7 +44,7 @@ struct PositionCandidate { // ties), or SIZE_MAX when none are reachable. The flood is one search shared // by all candidates); adding candidates is nearly free. RX_NAV_EXPORT size_t EvaluatePositions(const NavMesh& mesh, const PositionQueryParams& params, - std::span candidates); + base::Span candidates); } // namespace rx::nav diff --git a/engine/net/bubble.cc b/engine/net/bubble.cc index 1e50dd13..a74632c6 100644 --- a/engine/net/bubble.cc +++ b/engine/net/bubble.cc @@ -1,6 +1,7 @@ +#include "base/memory/move.h" #include "net/bubble.h" -#include +#include namespace rx::net { namespace { @@ -84,11 +85,11 @@ void InterestMap::Update(ecs::World& world, u64 tick) { scratch_grid_.clear(); for (u32 bi = 0; bi < scratch_bubbles_.size(); ++bi) { const BubbleRef& b = scratch_bubbles_[bi]; - const f32 exit = std::sqrt(b.exit_sq); - const i32 min_x = static_cast(std::floor((b.center[0] - exit) * inv_cell)); - const i32 max_x = static_cast(std::floor((b.center[0] + exit) * inv_cell)); - const i32 min_z = static_cast(std::floor((b.center[2] - exit) * inv_cell)); - const i32 max_z = static_cast(std::floor((b.center[2] + exit) * inv_cell)); + const f32 exit = ::sqrt(b.exit_sq); + const i32 min_x = static_cast(::floor((b.center[0] - exit) * inv_cell)); + const i32 max_x = static_cast(::floor((b.center[0] + exit) * inv_cell)); + const i32 min_z = static_cast(::floor((b.center[2] - exit) * inv_cell)); + const i32 max_z = static_cast(::floor((b.center[2] + exit) * inv_cell)); for (i32 cx = min_x; cx <= max_x; ++cx) { for (i32 cz = min_z; cz <= max_z; ++cz) { const u64 key = CellKey(cx, cz); @@ -106,8 +107,8 @@ void InterestMap::Update(ecs::World& world, u64 tick) { // swapping into a scratch copy first. base::UnorderedMap previous; for (auto entry : peers_) { - InterestSet moved = std::move(entry.value.interest); - previous.insert(entry.key, std::move(moved)); + InterestSet moved = base::move(entry.value.interest); + previous.insert(entry.key, base::move(moved)); entry.value.interest.Clear(); } // Peers appearing for the first time this update. @@ -132,8 +133,8 @@ void InterestMap::Update(ecs::World& world, u64 tick) { seen_ids.insert(id.value, u8{1}); candidates.clear(); - const i32 cx = static_cast(std::floor(t.position[0] * inv_cell)); - const i32 cz = static_cast(std::floor(t.position[2] * inv_cell)); + const i32 cx = static_cast(::floor(t.position[0] * inv_cell)); + const i32 cz = static_cast(::floor(t.position[2] * inv_cell)); const base::Vector* bucket = scratch_grid_.find(CellKey(cx, cz)); u32 avatar_peer = kNoPeer; if (bucket) { @@ -239,8 +240,8 @@ void InterestMap::RemovePeer(u32 peer) { u32 PeerColor(u32 peer) { // Golden-angle hue walk: well-spread, stable, no table. - const f32 hue = std::fmod(static_cast(peer) * 137.50776f, 360.0f) / 60.0f; - const f32 x = 1.0f - std::fabs(std::fmod(hue, 2.0f) - 1.0f); + const f32 hue = ::fmod(static_cast(peer) * 137.50776f, 360.0f) / 60.0f; + const f32 x = 1.0f - ::fabs(::fmod(hue, 2.0f) - 1.0f); f32 r = 0, g = 0, b = 0; switch (static_cast(hue)) { case 0: r = 1; g = x; break; diff --git a/engine/net/bubble.h b/engine/net/bubble.h index 0ea7731d..63621437 100644 --- a/engine/net/bubble.h +++ b/engine/net/bubble.h @@ -1,11 +1,12 @@ #ifndef RX_NET_BUBBLE_H_ #define RX_NET_BUBBLE_H_ -#include #include #include +#include "base/functional/function.h" +#include "base/memory/move.h" #include "core/export.h" #include "core/types.h" #include "ecs/world.h" @@ -70,8 +71,8 @@ class RX_NET_EXPORT InterestMap { // Invoked from Update for every ownership handoff (old_peer/new_peer are // kNoPeer for the server side). - void SetOwnerChangedSink(std::function sink) { - owner_changed_ = std::move(sink); + void SetOwnerChangedSink(base::Function sink) { + owner_changed_ = base::move(sink); } private: @@ -87,7 +88,7 @@ class RX_NET_EXPORT InterestMap { base::UnorderedMap peers_; base::UnorderedMap owners_; base::Vector bubbles_; - std::function owner_changed_; + base::Function owner_changed_; // Scratch, kept across updates for capacity. struct BubbleRef { diff --git a/engine/net/bubble_debug.cc b/engine/net/bubble_debug.cc index a4eb688d..2ff790f9 100644 --- a/engine/net/bubble_debug.cc +++ b/engine/net/bubble_debug.cc @@ -8,6 +8,7 @@ #include "render/rhi/vulkan_interop.h" #include "render/util/shader_util.h" +#include "base/memory/move.h" #include "shaders/bubble_wire_ps_hlsl.h" #include "shaders/bubble_wire_vs_hlsl.h" @@ -162,7 +163,7 @@ void BubbleVisualizer::Record(const render::SceneHookContext& ctx) { VkCommandBuffer cb = render::GetVkCommandBuffer(*ctx.cmd); render::ColorAttachment color{.view = ctx.color_view, .load = render::LoadOp::kLoad}; render::DepthAttachment depth{.view = ctx.depth_view, .load = render::LoadOp::kLoad}; - ctx.cmd->BeginRendering({.extent = ctx.extent, .colors = {&color, 1}, .depth = &depth}); + ctx.cmd->BeginRendering({.extent = ctx.extent, .colors = base::Span(&color, 1), .depth = &depth}); vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_); BubblePush push{}; @@ -194,7 +195,7 @@ void BubbleVisualizer::Emit(render::FrameView& view, bubbles_.clear(); for (const BubbleState& b : bubbles) bubbles_.push_back(b); // Compose with whatever transparent-phase pass the app already installed. - auto previous = std::move(view.scene_transparent); + auto previous = base::move(view.scene_transparent); view.scene_transparent = [this, previous](const render::SceneHookContext& ctx) { if (previous) previous(ctx); Record(ctx); diff --git a/engine/net/protocol.cc b/engine/net/protocol.cc index 6b5cbfba..b67cea8f 100644 --- a/engine/net/protocol.cc +++ b/engine/net/protocol.cc @@ -1,5 +1,7 @@ #include "net/protocol.h" +#include "base/containers/vector.h" +#include "base/optional.h" #include "net/wire.h" namespace rx::net { @@ -32,23 +34,23 @@ EntityState TakeEntityState(ByteReader& r) { } // namespace -std::vector ClientJoin::Encode() const { +base::Vector ClientJoin::Encode() const { ByteWriter w; w.U32(protocol); w.Str(player_name); return w.Take(); } -std::optional ClientJoin::Decode(const u8* data, size_t size) { +base::Optional ClientJoin::Decode(const u8* data, size_t size) { ByteReader r(data, size); ClientJoin m; m.protocol = r.U32(); m.player_name = r.Str(); - if (!r.ok()) return std::nullopt; + if (!r.ok()) return base::nullopt; return m; } -std::vector JoinAccept::Encode() const { +base::Vector JoinAccept::Encode() const { ByteWriter w; w.U64(player_entity); w.U64(server_tick); @@ -59,7 +61,7 @@ std::vector JoinAccept::Encode() const { return w.Take(); } -std::optional JoinAccept::Decode(const u8* data, size_t size) { +base::Optional JoinAccept::Decode(const u8* data, size_t size) { ByteReader r(data, size); JoinAccept m; m.player_entity = r.U64(); @@ -68,27 +70,27 @@ std::optional JoinAccept::Decode(const u8* data, size_t size) { m.client_id = r.U32(); m.tick_rate = r.U16(); m.snapshot_rate = r.U16(); - if (!r.ok()) return std::nullopt; + if (!r.ok()) return base::nullopt; return m; } -std::vector JoinRefuse::Encode() const { +base::Vector JoinRefuse::Encode() const { ByteWriter w; w.U8(static_cast(reason)); w.Str(detail); return w.Take(); } -std::optional JoinRefuse::Decode(const u8* data, size_t size) { +base::Optional JoinRefuse::Decode(const u8* data, size_t size) { ByteReader r(data, size); JoinRefuse m; m.reason = static_cast(r.U8()); m.detail = r.Str(); - if (!r.ok()) return std::nullopt; + if (!r.ok()) return base::nullopt; return m; } -std::vector Snapshot::Encode() const { +base::Vector Snapshot::Encode() const { ByteWriter w; w.U64(server_tick); w.Bool(full); @@ -99,26 +101,26 @@ std::vector Snapshot::Encode() const { return w.Take(); } -std::optional Snapshot::Decode(const u8* data, size_t size) { +base::Optional Snapshot::Decode(const u8* data, size_t size) { ByteReader r(data, size); Snapshot m; m.server_tick = r.U64(); m.full = r.Bool(); const u32 entity_count = r.U32(); if (static_cast(entity_count) * kEntityRecordBytes > r.remaining()) { - return std::nullopt; + return base::nullopt; } m.entities.reserve(entity_count); for (u32 i = 0; i < entity_count; ++i) m.entities.push_back(TakeEntityState(r)); const u32 despawn_count = r.U32(); - if (static_cast(despawn_count) * 8 > r.remaining()) return std::nullopt; + if (static_cast(despawn_count) * 8 > r.remaining()) return base::nullopt; m.despawned.reserve(despawn_count); for (u32 i = 0; i < despawn_count; ++i) m.despawned.push_back(r.U64()); - if (!r.ok()) return std::nullopt; + if (!r.ok()) return base::nullopt; return m; } -std::vector PlayerInput::Encode() const { +base::Vector PlayerInput::Encode() const { ByteWriter w; w.U64(client_tick); w.F32(move_x); @@ -130,7 +132,7 @@ std::vector PlayerInput::Encode() const { return w.Take(); } -std::optional PlayerInput::Decode(const u8* data, size_t size) { +base::Optional PlayerInput::Decode(const u8* data, size_t size) { ByteReader r(data, size); PlayerInput m; m.client_tick = r.U64(); @@ -140,11 +142,11 @@ std::optional PlayerInput::Decode(const u8* data, size_t size) { m.yaw = r.F32(); m.pitch = r.F32(); m.buttons = r.U32(); - if (!r.ok()) return std::nullopt; + if (!r.ok()) return base::nullopt; return m; } -std::vector EncodeBubbleSync(const base::Vector& bubbles) { +base::Vector EncodeBubbleSync(const base::Vector& bubbles) { ByteWriter w; w.U32(static_cast(bubbles.size())); for (const BubbleState& b : bubbles) { @@ -157,11 +159,11 @@ std::vector EncodeBubbleSync(const base::Vector& bubbles) { return w.Take(); } -std::optional> DecodeBubbleSync(const u8* data, size_t size) { +base::Optional> DecodeBubbleSync(const u8* data, size_t size) { ByteReader r(data, size); const u32 count = r.U32(); if (static_cast(count) * kBubbleRecordBytes > r.remaining()) { - return std::nullopt; + return base::nullopt; } base::Vector out; out.reserve(count); @@ -174,7 +176,7 @@ std::optional> DecodeBubbleSync(const u8* data, size_t b.owned_count = r.U32(); out.push_back(b); } - if (!r.ok()) return std::nullopt; + if (!r.ok()) return base::nullopt; return out; } diff --git a/engine/net/protocol.h b/engine/net/protocol.h index 964a6e47..0949f937 100644 --- a/engine/net/protocol.h +++ b/engine/net/protocol.h @@ -1,12 +1,12 @@ #ifndef RX_NET_PROTOCOL_H_ #define RX_NET_PROTOCOL_H_ -#include -#include -#include #include +#include "base/containers/vector.h" +#include "base/optional.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/types.h" @@ -50,10 +50,10 @@ enum class DisconnectReason : u8 { // both sides must agree on it, engine and game payloads alike. struct RX_NET_EXPORT ClientJoin { u32 protocol = 0; - std::string player_name; + base::String player_name; - std::vector Encode() const; - static std::optional Decode(const u8* data, size_t size); + base::Vector Encode() const; + static base::Optional Decode(const u8* data, size_t size); }; // Server reply admitting a client to the session. @@ -65,17 +65,17 @@ struct RX_NET_EXPORT JoinAccept { u16 tick_rate = 60; u16 snapshot_rate = 20; - std::vector Encode() const; - static std::optional Decode(const u8* data, size_t size); + base::Vector Encode() const; + static base::Optional Decode(const u8* data, size_t size); }; // Server reply turning a client away. struct RX_NET_EXPORT JoinRefuse { DisconnectReason reason = DisconnectReason::kUnknown; - std::string detail; + base::String detail; - std::vector Encode() const; - static std::optional Decode(const u8* data, size_t size); + base::Vector Encode() const; + static base::Optional Decode(const u8* data, size_t size); }; // State of one replicated entity: the transform plus two opaque payload slots. @@ -102,8 +102,8 @@ struct RX_NET_EXPORT Snapshot { base::Vector entities; base::Vector despawned; - std::vector Encode() const; - static std::optional Decode(const u8* data, size_t size); + base::Vector Encode() const; + static base::Optional Decode(const u8* data, size_t size); }; // Client-to-server input for its player entity. @@ -116,8 +116,8 @@ struct RX_NET_EXPORT PlayerInput { f32 pitch = 0; u32 buttons = 0; - std::vector Encode() const; - static std::optional Decode(const u8* data, size_t size); + base::Vector Encode() const; + static base::Optional Decode(const u8* data, size_t size); }; // One player's interest bubble as the server sees it, replicated to every @@ -133,8 +133,8 @@ struct RX_NET_EXPORT BubbleState { bool operator==(const BubbleState&) const = default; }; -RX_NET_EXPORT std::vector EncodeBubbleSync(const base::Vector& bubbles); -RX_NET_EXPORT std::optional> DecodeBubbleSync(const u8* data, +RX_NET_EXPORT base::Vector EncodeBubbleSync(const base::Vector& bubbles); +RX_NET_EXPORT base::Optional> DecodeBubbleSync(const u8* data, size_t size); } // namespace rx::net diff --git a/engine/net/replication.cc b/engine/net/replication.cc index a0f0e453..7144adc8 100644 --- a/engine/net/replication.cc +++ b/engine/net/replication.cc @@ -1,10 +1,11 @@ #include "net/replication.h" -#include +#include #include #include "asset/asset_id.h" +#include "base/atomic.h" #include "core/log.h" namespace rx::net { @@ -28,7 +29,7 @@ f32 SegmentPlanarSpeed(const InterpolatedTransform& interp) { if (interp.duration <= 0) return 0; const f32 dx = interp.to.position[0] - interp.from.position[0]; const f32 dz = interp.to.position[2] - interp.from.position[2]; - return std::sqrt(dx * dx + dz * dz) / interp.duration; + return ::sqrt(dx * dx + dz * dz) / interp.duration; } // Lerps position and scale, nlerps the quaternion along the shorter arc. @@ -48,7 +49,7 @@ void Blend(const scene::Transform& a, const scene::Transform& b, f32 alpha, q[i] = a.rotation[i] + (sign * b.rotation[i] - a.rotation[i]) * alpha; length_sq += q[i] * q[i]; } - const f32 inv_length = length_sq > 0 ? 1.0f / std::sqrt(length_sq) : 0; + const f32 inv_length = length_sq > 0 ? 1.0f / ::sqrt(length_sq) : 0; for (int i = 0; i < 4; ++i) out->rotation[i] = q[i] * inv_length; } @@ -56,7 +57,7 @@ void Blend(const scene::Transform& a, const scene::Transform& b, f32 alpha, NetworkId AllocateNetworkId() { static base::Atomic next{1}; - return NetworkId{next.fetch_add(1, std::memory_order_relaxed)}; + return NetworkId{next.fetch_add(1, base::memory_order_relaxed)}; } void TickInterpolation(ecs::World& world, f32 dt) { diff --git a/engine/net/replication.h b/engine/net/replication.h index b2e17812..8727096d 100644 --- a/engine/net/replication.h +++ b/engine/net/replication.h @@ -1,11 +1,11 @@ #ifndef RX_NET_REPLICATION_H_ #define RX_NET_REPLICATION_H_ -#include #include #include +#include "base/functional/function.h" #include "core/export.h" #include "core/types.h" #include "ecs/world.h" @@ -58,10 +58,10 @@ RX_NET_EXPORT void TickInterpolation(ecs::World& world, f32 dt); struct ReplicationHooks { // Server capture: the tag stored for an entity (0 = none), e.g. a game // packs its form/record id here. - std::function capture_user_tag; + base::Function capture_user_tag; // Client apply: called once when a replica spawns with a non-zero tag, so // the game attaches whatever component the tag denotes. - std::function on_replica_spawned; + base::Function on_replica_spawned; }; // Set of net ids relevant to one peer. Bubble membership feeds this; a diff --git a/engine/net/rpc_channel.cc b/engine/net/rpc_channel.cc index fa56de77..707bda95 100644 --- a/engine/net/rpc_channel.cc +++ b/engine/net/rpc_channel.cc @@ -1,7 +1,9 @@ #include "net/rpc_channel.h" -#include - +#include "base/containers/vector.h" +#include "base/memory/move.h" +#include "base/optional.h" +#include "base/strings/xstring.h" #include "core/log.h" #include "net/znet_util.h" @@ -15,7 +17,7 @@ constexpr size_t kMaxRpcPacket = 60000; } // namespace void RpcServerChannel::OnPacket(u32 peer, const u8* data, size_t size) { - std::optional call = rpc::DecodeCall(data, size); + base::Optional call = rpc::DecodeCall(data, size); if (!call) { RX_WARN("net: dropped corrupt rpc from peer {}", peer); return; @@ -27,7 +29,7 @@ void RpcServerChannel::OnPacket(u32 peer, const u8* data, size_t size) { } bool RpcServerChannel::Send(u32 destination, const rpc::RpcCall& call) { - std::vector payload = rpc::EncodeCall(call); + base::Vector payload = rpc::EncodeCall(call); if (payload.size() > kMaxRpcPacket) { RX_WARN("net: rpc '{}' is too large to send ({} bytes)", call.name, payload.size()); return false; @@ -41,21 +43,21 @@ bool RpcServerChannel::EmitToClient(u32 peer, const rpc::RpcCall& call) { return Send(peer, call); } -bool RpcServerChannel::EmitToClient(u32 peer, std::string name, rpc::RpcArgs args) { - return Send(peer, rpc::RpcCall{std::move(name), std::move(args)}); +bool RpcServerChannel::EmitToClient(u32 peer, base::String name, rpc::RpcArgs args) { + return Send(peer, rpc::RpcCall{base::move(name), base::move(args)}); } bool RpcServerChannel::Broadcast(const rpc::RpcCall& call) { return Send(tx::network::ZPeerId::to_all, call); } -bool RpcServerChannel::Broadcast(std::string name, rpc::RpcArgs args) { +bool RpcServerChannel::Broadcast(base::String name, rpc::RpcArgs args) { return Send(tx::network::ZPeerId::to_all, - rpc::RpcCall{std::move(name), std::move(args)}); + rpc::RpcCall{base::move(name), base::move(args)}); } void RpcClientChannel::OnPacket(const u8* data, size_t size) { - std::optional call = rpc::DecodeCall(data, size); + base::Optional call = rpc::DecodeCall(data, size); if (!call) { RX_WARN("net: dropped corrupt rpc from server"); return; @@ -67,7 +69,7 @@ void RpcClientChannel::OnPacket(const u8* data, size_t size) { } bool RpcClientChannel::EmitToServer(const rpc::RpcCall& call) { - std::vector payload = rpc::EncodeCall(call); + base::Vector payload = rpc::EncodeCall(call); if (payload.size() > kMaxRpcPacket) { RX_WARN("net: rpc '{}' is too large to send ({} bytes)", call.name, payload.size()); return false; @@ -77,8 +79,8 @@ bool RpcClientChannel::EmitToServer(const rpc::RpcCall& call) { return true; } -bool RpcClientChannel::EmitToServer(std::string name, rpc::RpcArgs args) { - return EmitToServer(rpc::RpcCall{std::move(name), std::move(args)}); +bool RpcClientChannel::EmitToServer(base::String name, rpc::RpcArgs args) { + return EmitToServer(rpc::RpcCall{base::move(name), base::move(args)}); } } // namespace rx::net diff --git a/engine/net/rpc_channel.h b/engine/net/rpc_channel.h index d31671b0..93951ea0 100644 --- a/engine/net/rpc_channel.h +++ b/engine/net/rpc_channel.h @@ -3,11 +3,12 @@ // Compiled only when the transport is available (RX_NET_HAS_ZETANET). -#include +#include #include #include +#include "base/strings/xstring.h" #include "core/export.h" #include "core/types.h" #include "rpc/rpc_message.h" @@ -35,11 +36,11 @@ class RX_NET_EXPORT RpcServerChannel { // Sends a call to one client. Returns false if the encoded call exceeds the // single-datagram limit (the caller should split its payload). bool EmitToClient(u32 peer, const rpc::RpcCall& call); - bool EmitToClient(u32 peer, std::string name, rpc::RpcArgs args); + bool EmitToClient(u32 peer, base::String name, rpc::RpcArgs args); // Sends a call to every connected client. bool Broadcast(const rpc::RpcCall& call); - bool Broadcast(std::string name, rpc::RpcArgs args); + bool Broadcast(base::String name, rpc::RpcArgs args); private: bool Send(u32 destination, const rpc::RpcCall& call); @@ -61,7 +62,7 @@ class RX_NET_EXPORT RpcClientChannel { // Sends a call to the host. Returns false if the encoded call exceeds the // single-datagram limit. bool EmitToServer(const rpc::RpcCall& call); - bool EmitToServer(std::string name, rpc::RpcArgs args); + bool EmitToServer(base::String name, rpc::RpcArgs args); private: tx::network::ZClient& client_; diff --git a/engine/net/session.cc b/engine/net/session.cc index 728c2d31..37df0112 100644 --- a/engine/net/session.cc +++ b/engine/net/session.cc @@ -1,8 +1,11 @@ #include "net/session.h" -#include +#include #include "asset/asset_id.h" +#include "base/containers/vector.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" #include "core/log.h" #include "net/rpc_channel.h" #include "net/znet_util.h" @@ -26,14 +29,14 @@ void DefaultSimulatePlayer(ecs::World& world, ecs::Entity player, t->position[2] += ClampAxis(input.move_z) * kDefaultPlayerSpeed * dt; const f32 half_yaw = input.yaw * 0.5f; t->rotation[0] = 0; - t->rotation[1] = std::sin(half_yaw); + t->rotation[1] = ::sin(half_yaw); t->rotation[2] = 0; - t->rotation[3] = std::cos(half_yaw); + t->rotation[3] = ::cos(half_yaw); } } // namespace -ServerSession::ServerSession(SessionConfig config) : config_(std::move(config)) { +ServerSession::ServerSession(SessionConfig config) : config_(base::move(config)) { interest_.Configure(config_.interest); } @@ -56,7 +59,7 @@ bool ServerSession::Start() { return false; } started_ = true; - rpc_ = std::make_unique(server_); + rpc_ = base::MakeUnique(server_); RX_INFO("net: server listening on {} (bubbles {})", config_.port, config_.bubble_radius > 0 ? "on" : "off"); return true; @@ -173,7 +176,7 @@ void ServerSession::HandleJoin(ecs::World& world, u32 peer, const ClientJoin& jo world.Add(fresh.player, InterestBubble{peer, config_.bubble_radius}); } fresh.player_net_id = net_id.value; - clients_.insert(peer, std::move(fresh)); + clients_.insert(peer, base::move(fresh)); client = clients_.find(peer); if (player_spawn_sink_) player_spawn_sink_(world, client->player, peer); RX_INFO("net: peer {} joined as '{}' ({} clients)", peer, client->name.c_str(), @@ -255,7 +258,7 @@ void ServerSession::SendSnapshots(ecs::World& world) { // Snapshots stay unreliable by design: the next one supersedes a lost one // and keyframes repair anything structural. - std::vector blob = snapshot_.Encode(); + base::Vector blob = snapshot_.Encode(); stats_.snapshots_sent += 1; stats_.snapshot_records_sent += written; stats_.snapshot_bytes_sent += blob.size(); @@ -274,12 +277,12 @@ void ServerSession::SendSnapshots(ecs::World& world) { } } -void ServerSession::SendTo(u32 peer, u16 type, const std::vector& payload, +void ServerSession::SendTo(u32 peer, u16 type, const base::Vector& payload, bool reliable, tx::network::PacketPriority priority) { server_.Push(MakePacket(peer, type, payload, reliable, priority)); } -void ServerSession::Broadcast(u16 type, const std::vector& payload, bool reliable, +void ServerSession::Broadcast(u16 type, const base::Vector& payload, bool reliable, tx::network::PacketPriority priority) { if (clients_.size() == 0) return; server_.Push(MakePacket(tx::network::ZPeerId::to_all, type, payload, reliable, priority)); @@ -295,7 +298,7 @@ u64 ServerSession::PlayerNetId(u32 peer) const { return client ? client->player_net_id : 0; } -ClientSession::ClientSession(SessionConfig config) : config_(std::move(config)) { +ClientSession::ClientSession(SessionConfig config) : config_(base::move(config)) { if (config_.snapshot_interval_ticks > 0 && config_.tick_rate > 0) { snapshot_dt_ = static_cast(config_.snapshot_interval_ticks) / static_cast(config_.tick_rate); @@ -320,7 +323,7 @@ bool ClientSession::Start() { config_.port); return false; } - rpc_ = std::make_unique(client_); + rpc_ = base::MakeUnique(client_); RX_INFO("net: connecting to {}:{}", config_.address.c_str(), config_.port); return true; } @@ -386,7 +389,7 @@ void ClientSession::Tick(ecs::World& world, f32 dt) { ++tick_; } -void ClientSession::SendToServer(u16 type, const std::vector& payload, bool reliable, +void ClientSession::SendToServer(u16 type, const base::Vector& payload, bool reliable, tx::network::PacketPriority priority) { client_.Push(MakePacket(tx::network::ZPeerId::to_server, type, payload, reliable, priority)); } @@ -433,7 +436,7 @@ void ClientSession::PollMessages(ecs::World& world) { } case MessageType::kBubbleSync: { if (auto bubbles = DecodeBubbleSync(PacketData(packet), packet.data.size())) { - bubbles_ = std::move(*bubbles); + bubbles_ = base::move(*bubbles); } break; } diff --git a/engine/net/session.h b/engine/net/session.h index 82bd7340..f0e6c92c 100644 --- a/engine/net/session.h +++ b/engine/net/session.h @@ -12,9 +12,11 @@ #include #include -#include -#include +#include "base/containers/vector.h" +#include "base/functional/function.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" #include "core/export.h" #include "ecs/world.h" #include "net/bubble.h" @@ -83,47 +85,47 @@ class RX_NET_EXPORT ServerSession : public Session { // Every data-channel message with id >= kFirstGameMessage lands here, // undecoded, attributed to its peer. Unset drops them. void SetGameMessageSink( - std::function sink) { - game_message_sink_ = std::move(sink); + base::Function sink) { + game_message_sink_ = base::move(sink); } // Called after the server spawns a joiner's player entity (Transform + // NetworkId + optional Renderable + InterestBubble already attached), so // the game adds its own components or moves the spawn. - void SetPlayerSpawnSink(std::function sink) { - player_spawn_sink_ = std::move(sink); + void SetPlayerSpawnSink(base::Function sink) { + player_spawn_sink_ = base::move(sink); } // Integrates one player's newest input each fixed step. Unset runs a plain // fly-move (demo default); a game replaces it with its actual locomotion. void SetPlayerSimulator( - std::function sim) { - player_simulator_ = std::move(sim); + base::Function sim) { + player_simulator_ = base::move(sim); } // Per-entity replication payload (user tags) capture, and (through the // same hooks type) what a client does when a tagged replica spawns. - void SetReplicationHooks(ReplicationHooks hooks) { hooks_ = std::move(hooks); } + void SetReplicationHooks(ReplicationHooks hooks) { hooks_ = base::move(hooks); } // Join/leave notifications, the fundamental multiplayer hooks for // server-side game logic. Fired for every peer; unset sinks drop the notice. - void SetClientJoinedSink(std::function sink) { - client_joined_sink_ = std::move(sink); + void SetClientJoinedSink(base::Function sink) { + client_joined_sink_ = base::move(sink); } - void SetClientLeftSink(std::function sink) { - client_left_sink_ = std::move(sink); + void SetClientLeftSink(base::Function sink) { + client_left_sink_ = base::move(sink); } // sending // Ships a game payload to one client / every client on the data channel. - void SendTo(u32 peer, u16 type, const std::vector& payload, bool reliable, + void SendTo(u32 peer, u16 type, const base::Vector& payload, bool reliable, tx::network::PacketPriority priority = tx::network::PacketPriority::Medium); - void Broadcast(u16 type, const std::vector& payload, bool reliable, + void Broadcast(u16 type, const base::Vector& payload, bool reliable, tx::network::PacketPriority priority = tx::network::PacketPriority::Medium); // The server's scripting RPC channel. Always present once Start succeeds. - RpcServerChannel* rpc() { return rpc_.get(); } + RpcServerChannel* rpc() { return rpc_.Get_UseOnlyIfYouKnowWhatYouareDoing(); } // Escape hatch for game layers that ride zetanet features directly (file // transfer, manifests): the raw transport. Everything sent through it must @@ -144,7 +146,7 @@ class RX_NET_EXPORT ServerSession : public Session { // Visits every admitted peer id (game layers pushing per-peer payloads, // e.g. re-offering an asset manifest after a live reload). - void ForEachPeer(const std::function& fn) const { + void ForEachPeer(const base::Function& fn) const { for (auto entry : clients_) fn(entry.key); } @@ -173,12 +175,12 @@ class RX_NET_EXPORT ServerSession : public Session { base::UnorderedMap clients_; base::Vector scratch_dropped_; ReplicationHooks hooks_; - std::function game_message_sink_; - std::function player_spawn_sink_; - std::function player_simulator_; - std::function client_joined_sink_; - std::function client_left_sink_; - std::unique_ptr rpc_; + base::Function game_message_sink_; + base::Function player_spawn_sink_; + base::Function player_simulator_; + base::Function client_joined_sink_; + base::Function client_left_sink_; + base::UniquePointer rpc_; NetStats stats_; u64 tick_ = 0; bool force_keyframe_ = false; @@ -198,31 +200,31 @@ class RX_NET_EXPORT ClientSession : public Session { // Every data-channel message with id >= kFirstGameMessage lands here, // undecoded. Unset drops them. - void SetGameMessageSink(std::function sink) { - game_message_sink_ = std::move(sink); + void SetGameMessageSink(base::Function sink) { + game_message_sink_ = base::move(sink); } // What a replica spawn with a user tag means to this game (hooks are the // client half of ReplicationHooks; capture is unused here). - void SetReplicationHooks(ReplicationHooks hooks) { hooks_ = std::move(hooks); } + void SetReplicationHooks(ReplicationHooks hooks) { hooks_ = base::move(hooks); } // Control-channel packets zetanet classifies as file transfers, polled // before Update() would discard them. A game's asset streaming taps here. - void SetFilePacketSink(std::function sink) { - file_packet_sink_ = std::move(sink); + void SetFilePacketSink(base::Function sink) { + file_packet_sink_ = base::move(sink); } // Fired once when the server accepts the join. - void SetJoinedSink(std::function sink) { - joined_sink_ = std::move(sink); + void SetJoinedSink(base::Function sink) { + joined_sink_ = base::move(sink); } // Ships a game payload to the server on the data channel. - void SendToServer(u16 type, const std::vector& payload, bool reliable, + void SendToServer(u16 type, const base::Vector& payload, bool reliable, tx::network::PacketPriority priority = tx::network::PacketPriority::Medium); // The client's scripting RPC channel. Always present once Start succeeds. - RpcClientChannel* rpc() { return rpc_.get(); } + RpcClientChannel* rpc() { return rpc_.Get_UseOnlyIfYouKnowWhatYouareDoing(); } // Escape hatch: the raw transport (see ServerSession::raw). tx::network::ZClient& raw() { return client_; } @@ -247,10 +249,10 @@ class RX_NET_EXPORT ClientSession : public Session { tx::network::ZClient client_; SnapshotApplier applier_; ReplicationHooks hooks_; - std::unique_ptr rpc_; - std::function game_message_sink_; - std::function file_packet_sink_; - std::function joined_sink_; + base::UniquePointer rpc_; + base::Function game_message_sink_; + base::Function file_packet_sink_; + base::Function joined_sink_; base::Vector bubbles_; PlayerInput input_; u64 player_net_id_ = 0; diff --git a/engine/net/wire.h b/engine/net/wire.h index 17127481..ab822ed1 100644 --- a/engine/net/wire.h +++ b/engine/net/wire.h @@ -1,11 +1,13 @@ #ifndef RX_NET_WIRE_H_ #define RX_NET_WIRE_H_ -#include -#include -#include -#include +#include +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/types.h" namespace rx::net { @@ -30,22 +32,22 @@ class ByteWriter { } void F32(f32 v) { u32 bits; - std::memcpy(&bits, &v, 4); + base::MemCopy(&bits, &v, 4); U32(bits); } void Bool(bool v) { U8(v ? 1 : 0); } // Length-prefixed (u16) string; longer input is truncated. - void Str(std::string_view s) { + void Str(base::StringRef s) { const u16 len = static_cast(s.size() > 0xffff ? 0xffff : s.size()); U16(len); buf_.insert(buf_.end(), s.data(), s.data() + len); } - std::vector Take() { return std::move(buf_); } + base::Vector Take() { return base::move(buf_); } size_t size() const { return buf_.size(); } private: - std::vector buf_; + base::Vector buf_; }; class ByteReader { @@ -79,14 +81,14 @@ class ByteReader { f32 F32() { const u32 bits = U32(); f32 v; - std::memcpy(&v, &bits, 4); + base::MemCopy(&v, &bits, 4); return v; } bool Bool() { return U8() != 0; } - std::string Str() { + base::String Str() { const u16 len = U16(); if (!Want(len)) return {}; - std::string s(reinterpret_cast(data_ + pos_), len); + base::String s(reinterpret_cast(data_ + pos_), len); pos_ += len; return s; } diff --git a/engine/net/znet_util.h b/engine/net/znet_util.h index c13d54e3..25eca80a 100644 --- a/engine/net/znet_util.h +++ b/engine/net/znet_util.h @@ -5,11 +5,11 @@ // the transport is available (RX_NET_HAS_ZETANET); the codec/interest halves // of the module never include this. -#include #include #include +#include "base/containers/vector.h" #include "core/types.h" #include "net/protocol.h" @@ -22,7 +22,7 @@ inline tx::network::PacketType ToPacketType(u16 type) { // Wraps an encoded payload in an outgoing zetanet packet. Snapshots stay // unreliable by design, joins and keyframes ride the reliable path. inline tx::network::OutgoingPacket MakePacket( - u32 destination, u16 type, const std::vector& payload, bool reliable, + u32 destination, u16 type, const base::Vector& payload, bool reliable, tx::network::PacketPriority priority = tx::network::PacketPriority::Medium) { const tx::network::PackageFlags flags{ .reliable = reliable ? u8{1} : u8{0}, @@ -41,7 +41,7 @@ inline tx::network::OutgoingPacket MakePacket( } inline tx::network::OutgoingPacket MakePacket( - u32 destination, MessageType type, const std::vector& payload, bool reliable, + u32 destination, MessageType type, const base::Vector& payload, bool reliable, tx::network::PacketPriority priority = tx::network::PacketPriority::Medium) { return MakePacket(destination, static_cast(type), payload, reliable, priority); } diff --git a/engine/physics/aircraft.cc b/engine/physics/aircraft.cc index 91293cfb..35261c28 100644 --- a/engine/physics/aircraft.cc +++ b/engine/physics/aircraft.cc @@ -1,8 +1,8 @@ #include "physics/aircraft.h" -#include -#include +#include +#include "core/scalar.h" #include "physics/shape_desc.h" namespace rx::physics { @@ -29,8 +29,8 @@ f32 SmoothStep(f32 edge0, f32 edge1, f32 x) { // `alpha`. A logistic centred on the stall angle blends the attached linear // lift curve into a flat-plate curve; `half_width` (rad) sets how abruptly. f32 StallBlend(f32 alpha, f32 stall_alpha, f32 half_width) { - f32 x = (std::fabs(alpha) - stall_alpha) / std::max(half_width, 1e-3f); - return 1.0f / (1.0f + std::exp(-4.0f * x)); + f32 x = (::fabs(alpha) - stall_alpha) / rx::Max(half_width, 1e-3f); + return 1.0f / (1.0f + ::exp(-4.0f * x)); } } // namespace @@ -62,7 +62,7 @@ Aircraft::Aircraft(PhysicsWorld& world, const AircraftDesc& desc, const Vec3& po : world_(world), desc_(desc) { // Clamp payload to the hard structural limit; the plane still spawns when // loaded between MTOM and the limit, it just flies badly (see over_mtom()). - const f32 max_payload = std::max(0.0f, desc_.structural_mass_limit_kg - desc_.empty_mass_kg); + const f32 max_payload = rx::Max(0.0f, desc_.structural_mass_limit_kg - desc_.empty_mass_kg); const f32 payload = Clampf(desc_.payload_kg, 0.0f, max_payload); desc_.payload_kg = payload; total_mass_ = desc_.empty_mass_kg + payload; @@ -142,8 +142,8 @@ void Aircraft::Update(const AircraftInput& input, f32 dt) { // Filter flap deflection toward the quantized command (mechanical lag). { - const u32 steps = std::max(1u, desc_.flap_steps); - const f32 quantized = std::round(Clampf(input.flaps, 0.0f, 1.0f) * steps) / steps; + const u32 steps = rx::Max(1u, desc_.flap_steps); + const f32 quantized = ::round(Clampf(input.flaps, 0.0f, 1.0f) * steps) / steps; const f32 rate = 0.5f; // full travel in ~2 s flaps_ += Clampf(quantized - flaps_, -rate * dt, rate * dt); } @@ -155,8 +155,8 @@ void Aircraft::Update(const AircraftInput& input, f32 dt) { const f32 f_com = Dot(vair_com, fwd); const f32 u_com = Dot(vair_com, up); const f32 s_com = Dot(vair_com, right); - const f32 alpha_com = std::atan2(-u_com, f_com); - const f32 beta_com = std::atan2(s_com, std::max(std::fabs(f_com), 1e-3f)); + const f32 alpha_com = ::atan2(-u_com, f_com); + const f32 beta_com = ::atan2(s_com, rx::Max(::fabs(f_com), 1e-3f)); // wing halves: strip theory at each half's aerodynamic centre // Each half is evaluated at its OWN point velocity (GetPointVelocity), so @@ -181,13 +181,13 @@ void Aircraft::Update(const AircraftInput& input, f32 dt) { } const f32 fc = Dot(v, fwd); const f32 uc = Dot(v, up); - const f32 alpha = std::atan2(-uc, fc); + const f32 alpha = ::atan2(-uc, fc); // Attached linear curve (with flap + aileron camber) blended into a // flat-plate curve past the stall; drag rises with the flat-plate term. const f32 cl_attached = desc_.wing_cl_alpha * (alpha - desc_.wing_zero_lift_alpha_rad) + flap_cl + cl_bias; - const f32 cl_flat = std::sin(2.0f * alpha); // 2 sin a cos a, flat plate + const f32 cl_flat = ::sin(2.0f * alpha); // 2 sin a cos a, flat plate const f32 blend = StallBlend(alpha, desc_.wing_stall_alpha_rad, desc_.post_stall_decay); const f32 cl = (1.0f - blend) * cl_attached + blend * cl_flat; *stalled = blend > 0.5f; @@ -195,7 +195,7 @@ void Aircraft::Update(const AircraftInput& input, f32 dt) { // Induced drag from this half's lift; parasitic drag is applied once at the // CoM below. Post-stall separation adds a flat-plate drag bump. const f32 cd_induced = cl_attached * cl_attached / (kPi * aspect_ratio * desc_.oswald_efficiency); - const f32 cd = (1.0f - blend) * cd_induced + blend * (0.15f + 2.0f * std::sin(alpha) * std::sin(alpha)); + const f32 cd = (1.0f - blend) * cd_induced + blend * (0.15f + 2.0f * ::sin(alpha) * ::sin(alpha)); const f32 qh = 0.5f * kAirDensity * vlen * vlen; const f32 lift = qh * half_area * cl; @@ -226,7 +226,7 @@ void Aircraft::Update(const AircraftInput& input, f32 dt) { // fuselage side drag (sideslip): damps lateral sliding through the air { const f32 vside = Dot(vair_com, right); - const f32 fside = -0.5f * kAirDensity * std::fabs(vside) * vside * desc_.fuselage_side_cd * + const f32 fside = -0.5f * kAirDensity * ::fabs(vside) * vside * desc_.fuselage_side_cd * desc_.fuselage_side_area_m2; world_.AddForce(body_, right * (fside * aero_fade)); } @@ -244,7 +244,7 @@ void Aircraft::Update(const AircraftInput& input, f32 dt) { if (vlen > 1e-3f) { const f32 fc = Dot(v, fwd); const f32 uc = Dot(v, up); - const f32 alpha_t = std::atan2(-uc, fc); + const f32 alpha_t = ::atan2(-uc, fc); const f32 cl_t = desc_.tail_cl_alpha * alpha_t - desc_.elevator_authority * input.pitch; const f32 qt = 0.5f * kAirDensity * vlen * vlen; const f32 lift = qt * desc_.tail_area_m2 * cl_t; @@ -264,7 +264,7 @@ void Aircraft::Update(const AircraftInput& input, f32 dt) { if (vlen > 1e-3f) { const f32 fc = Dot(v, fwd); const f32 sc = Dot(v, right); - const f32 beta_f = std::atan2(sc, std::max(std::fabs(fc), 1e-3f)); + const f32 beta_f = ::atan2(sc, rx::Max(::fabs(fc), 1e-3f)); // Side force opposes sideslip (weathervane) and responds to rudder. +yaw // input yaws the nose right (toward body -X): with +Z fwd / +Y up / +X // left, a nose-right yaw is a NEGATIVE rotation about +Y, which the fin @@ -292,22 +292,22 @@ void Aircraft::Update(const AircraftInput& input, f32 dt) { f32 engine_load = 0.0f; f32 telemetry_rpm = 0.0f; if (desc_.propulsion == AircraftDesc::Propulsion::kProp) { - const f32 idle_frac = Clampf(desc_.prop_idle_rpm / std::max(desc_.prop_max_rpm, 1.0f), 0.0f, 1.0f); - const f32 target = std::max(idle_frac, Clampf(input.throttle, 0.0f, 1.0f)); - const f32 tau = std::max(desc_.engine_spool_time_s, 1e-3f); + const f32 idle_frac = Clampf(desc_.prop_idle_rpm / rx::Max(desc_.prop_max_rpm, 1.0f), 0.0f, 1.0f); + const f32 target = rx::Max(idle_frac, Clampf(input.throttle, 0.0f, 1.0f)); + const f32 tau = rx::Max(desc_.engine_spool_time_s, 1e-3f); engine_spin_ += (target - engine_spin_) * Clampf(dt / tau, 0.0f, 1.0f); // Momentum-theory-flavoured thrust: power/velocity, capped near static. // Shaft power of a fixed-pitch prop grows ~ rpm^3, so idle thrust is tiny // (a parked plane at throttle 0 barely creeps) while full power is unchanged. const f32 power = desc_.prop_max_power_w * engine_spin_ * engine_spin_ * engine_spin_; - const f32 v_eff = std::max(speed, desc_.prop_min_airspeed_mps); + const f32 v_eff = rx::Max(speed, desc_.prop_min_airspeed_mps); const f32 cap = desc_.prop_static_thrust_cap_n * engine_spin_; - thrust = std::min(power * desc_.prop_efficiency / v_eff, cap); - engine_load = Clampf(thrust / std::max(desc_.prop_static_thrust_cap_n, 1.0f), 0.0f, 1.0f); + thrust = rx::Min(power * desc_.prop_efficiency / v_eff, cap); + engine_load = Clampf(thrust / rx::Max(desc_.prop_static_thrust_cap_n, 1.0f), 0.0f, 1.0f); telemetry_rpm = engine_spin_ * desc_.prop_max_rpm; } else { const f32 target = Clampf(input.throttle, 0.0f, 1.0f); - const f32 tau = std::max(desc_.jet_spool_time_s, 1e-3f); + const f32 tau = rx::Max(desc_.jet_spool_time_s, 1e-3f); engine_spin_ += (target - engine_spin_) * Clampf(dt / tau, 0.0f, 1.0f); thrust = desc_.jet_max_thrust_n * engine_spin_; engine_load = engine_spin_; @@ -335,7 +335,7 @@ void Aircraft::Update(const AircraftInput& input, f32 dt) { if (compression <= 0.0f) continue; const f32 comp = Clampf(compression, 0.0f, w.travel); on_ground = true; - state_.gear_compression[i] = comp / std::max(w.travel, 1e-3f); + state_.gear_compression[i] = comp / rx::Max(w.travel, 1e-3f); const Vec3 contact = hit.position; Vec3 n = hit.normal; @@ -348,7 +348,7 @@ void Aircraft::Update(const AircraftInput& input, f32 dt) { // Suspension spring-damper; never pulls the wheel toward the ground. const f32 f_spring = w.spring * comp; const f32 f_damp = -w.damper * vn; - const f32 fn = std::max(f_spring + f_damp, 0.0f); + const f32 fn = rx::Max(f_spring + f_damp, 0.0f); Vec3 gear_force = n * fn; // Ground-plane tire frame: rolling direction = body forward projected onto diff --git a/engine/physics/boat.cc b/engine/physics/boat.cc index f0cf1fef..36a4f804 100644 --- a/engine/physics/boat.cc +++ b/engine/physics/boat.cc @@ -1,7 +1,7 @@ +#include "core/scalar.h" #include "physics/boat.h" -#include -#include +#include namespace rx::physics { @@ -23,15 +23,15 @@ f32 SmoothStep(f32 edge0, f32 edge1, f32 x) { // Yaw quaternion about +Y (x,y,z,w), for spawn orientation. Quat YawQuat(f32 yaw_radians) { - return {0.0f, std::sin(yaw_radians * 0.5f), 0.0f, std::cos(yaw_radians * 0.5f)}; + return {0.0f, ::sin(yaw_radians * 0.5f), 0.0f, ::cos(yaw_radians * 0.5f)}; } // v * |v|: signed quadratic term (drag ~ v^2 but keeps the sign of v). -f32 SignedSquare(f32 v) { return v * std::fabs(v); } +f32 SignedSquare(f32 v) { return v * ::fabs(v); } // Guards a force/torque against NaN/Inf before it reaches the solver. bool Finite(const Vec3& v) { - return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z); + return ::isfinite(v.x) && ::isfinite(v.y) && ::isfinite(v.z); } } // namespace @@ -42,7 +42,7 @@ Boat::Boat(PhysicsWorld& world, const BoatDesc& desc, const Vec3& position, f32 // derives exactly desc.mass. Buoyancy and drag are our model's job, not the // shape's, so the shape density only sets the mass here. const Vec3& he = desc_.hull_half_extent; - const f32 volume = std::max(8.0f * he.x * he.y * he.z, 1e-3f); + const f32 volume = rx::Max(8.0f * he.x * he.y * he.z, 1e-3f); const f32 density = desc_.mass / volume; const Quat q = YawQuat(yaw_radians); const f32 rot[4] = {q.x, q.y, q.z, q.w}; @@ -108,10 +108,10 @@ void Boat::Update(const BoatInput& input, f32 dt) { // engine spool: rpm chases the throttle target with a first-order lag const f32 target_rpm = - desc_.idle_rpm + std::fabs(throttle) * (desc_.max_rpm - desc_.idle_rpm); - const f32 spool = desc_.spool_time > 0 ? (1.0f - std::exp(-dt / desc_.spool_time)) : 1.0f; + desc_.idle_rpm + ::fabs(throttle) * (desc_.max_rpm - desc_.idle_rpm); + const f32 spool = desc_.spool_time > 0 ? (1.0f - ::exp(-dt / desc_.spool_time)) : 1.0f; rpm_ += (target_rpm - rpm_) * spool; - const f32 rpm_span = std::max(desc_.max_rpm - desc_.idle_rpm, 1.0f); + const f32 rpm_span = rx::Max(desc_.max_rpm - desc_.idle_rpm, 1.0f); const f32 thrust_frac = Clamp((rpm_ - desc_.idle_rpm) / rpm_span, 0.0f, 1.0f); // Signed thrust magnitude: astern is throttled down by reverse_fraction. f32 thrust_mag = desc_.max_thrust * thrust_frac; @@ -126,13 +126,13 @@ void Boat::Update(const BoatInput& input, f32 dt) { // A per-sample vertical damper bleeds heave/roll/pitch so the springs settle // without ringing. Because buoyancy is distributed through the volume, the // centre of buoyancy migrates to the submerged side and rights the hull. - const u32 nx = std::max(desc_.grid_beam, 1); - const u32 ny = std::max(desc_.grid_height, 1); - const u32 nz = std::max(desc_.grid_len, 1); + const u32 nx = rx::Max(desc_.grid_beam, 1); + const u32 ny = rx::Max(desc_.grid_height, 1); + const u32 nz = rx::Max(desc_.grid_len, 1); const Vec3& he = desc_.hull_half_extent; const f32 hull_volume = 8.0f * he.x * he.y * he.z; const f32 subvol = hull_volume / static_cast(nx * ny * nz); - const f32 layer_thickness = std::max(2.0f * he.y / static_cast(ny), 1e-3f); + const f32 layer_thickness = rx::Max(2.0f * he.y / static_cast(ny), 1e-3f); const f32 buoy_per_sample = kWaterDensity * kGravity * subvol; u32 bottom_wet = 0; // submerged samples in the bottom layer -> wetted/planing const u32 bottom_total = nx * nz; @@ -184,10 +184,10 @@ void Boat::Update(const BoatInput& input, f32 dt) { const f32 v_fwd = Dot(vrel, forward); const f32 v_lat = Dot(vrel, right); const f32 v_vert = Dot(vel, up); - const f32 speed = std::sqrt(vel.x * vel.x + vel.z * vel.z); + const f32 speed = ::sqrt(vel.x * vel.x + vel.z * vel.z); // Planing fraction from forward speed; drives bow lift + drag drop. - const f32 planing = SmoothStep(desc_.hull_speed, desc_.plane_full_speed, std::fabs(v_fwd)); + const f32 planing = SmoothStep(desc_.hull_speed, desc_.plane_full_speed, ::fabs(v_fwd)); // Longitudinal: fore drag < aft drag; wetted-scaled; dropped by planing when // moving ahead. Lateral: strong keel drag so the hull carves rather than @@ -208,7 +208,7 @@ void Boat::Update(const BoatInput& input, f32 dt) { // Applied at a modest forward lever so the bow rises a few degrees. if (planing > 0.0f && desc_.plane_lift > 0.0f && v_fwd > 0.0f && wetted > 0.0f) { f32 lift = desc_.plane_lift * v_fwd * v_fwd * planing; - lift = std::min(lift, desc_.plane_lift_cap * weight) * wetted; + lift = rx::Min(lift, desc_.plane_lift_cap * weight) * wetted; const Vec3 bow = pos + Rotate(q, Vec3{0.0f, -he.y, he.z * 0.35f}); const Vec3 lift_force{0.0f, lift, 0.0f}; // world up: lifts and pitches bow up if (Finite(lift_force)) world_.AddForceAtPoint(body_, lift_force, bow); @@ -231,7 +231,7 @@ void Boat::Update(const BoatInput& input, f32 dt) { // Authority scales with the water speed over the rudder (|v_fwd|^2) plus the // propeller wash (proportional to thrust), so the boat still answers the helm // on the wash at a standstill. Only the wash term needs the prop submerged. - const f32 wash = (prop_submerged ? desc_.rudder_wash_gain * std::fabs(thrust_mag) : 0.0f); + const f32 wash = (prop_submerged ? desc_.rudder_wash_gain * ::fabs(thrust_mag) : 0.0f); const f32 rudder_mag = steer * (desc_.rudder_speed_gain * v_fwd * v_fwd + wash); if (rudder_mag != 0.0f) { const Vec3 rudder_pt = pos + Rotate(q, desc_.rudder_offset); @@ -247,13 +247,13 @@ void Boat::Update(const BoatInput& input, f32 dt) { // little as it shoves it downwind. Conservative coefficient (see wind_drag). if (desc_.wind_drag > 0.0f && exposed > 0.0f) { const Vec3 vrel_wind = world_.wind() - vel; // air velocity relative to hull - const f32 wind_speed = std::sqrt(Dot(vrel_wind, vrel_wind)); + const f32 wind_speed = ::sqrt(Dot(vrel_wind, vrel_wind)); if (wind_speed > 1e-3f) { const Vec3 wdir = vrel_wind * (1.0f / wind_speed); const f32 side_area = (2.0f * he.z) * (2.0f * he.y); // beam-on profile const f32 front_area = (2.0f * he.x) * (2.0f * he.y); // head-on profile const f32 area = - std::fabs(Dot(wdir, right)) * side_area + std::fabs(Dot(wdir, forward)) * front_area; + ::fabs(Dot(wdir, right)) * side_area + ::fabs(Dot(wdir, forward)) * front_area; const f32 mag = 0.5f * kAirDensity * desc_.wind_drag * area * exposed * wind_speed * wind_speed; const Vec3 wind_force = wdir * mag; diff --git a/engine/physics/cloth_collision.cc b/engine/physics/cloth_collision.cc index c5ffd9ee..a87b62c8 100644 --- a/engine/physics/cloth_collision.cc +++ b/engine/physics/cloth_collision.cc @@ -1,10 +1,13 @@ +#include "base/algorithm.h" +#include "base/memory/move.h" +#include "core/scalar.h" #include "physics/cloth_collision.h" +#include "core/sort.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include namespace rx::physics::detail { namespace { @@ -41,7 +44,7 @@ bool SameFace(const FaceKey& a, const FaceKey& b) { } bool IsFinite(const Vec3& p) { - return std::isfinite(p.x) && std::isfinite(p.y) && std::isfinite(p.z); + return ::isfinite(p.x) && ::isfinite(p.y) && ::isfinite(p.z); } f64 TriangleAreaTwice(const Vec3& a, const Vec3& b, const Vec3& c) { @@ -54,7 +57,7 @@ f64 TriangleAreaTwice(const Vec3& a, const Vec3& b, const Vec3& c) { const f64 x = aby * acz - abz * acy; const f64 y = abz * acx - abx * acz; const f64 z = abx * acy - aby * acx; - return std::sqrt(x * x + y * y + z * z); + return ::sqrt(x * x + y * y + z * z); } f64 SignedVolumeContribution(const Vec3& a, const Vec3& b, const Vec3& c) { @@ -71,17 +74,19 @@ Edge MakeEdge(u32 a, u32 b) { } FaceKey MakeFaceKey(u32 a, u32 b, u32 c) { - if (a > b) std::swap(a, b); - if (b > c) std::swap(b, c); - if (a > b) std::swap(a, b); + if (a > b) base::Swap(a, b); + if (b > c) base::Swap(b, c); + if (a > b) base::Swap(a, b); return {a, b, c}; } bool IsTopologyNeighbor(const ClothTopology& topology, u32 vertex, u32 other) { const u32 begin = topology.neighbor_offsets[vertex]; const u32 end = topology.neighbor_offsets[vertex + 1]; - return std::binary_search(topology.neighbors.begin() + begin, - topology.neighbors.begin() + end, other); + const u32* first = topology.neighbors.data() + begin; + const u32* last = topology.neighbors.data() + end; + const u32* it = base::LowerBound(first, last, other); + return it != last && !(other < *it); } Vec3 ClosestPointOnSegment(const Vec3& p, const Vec3& a, const Vec3& b, @@ -89,7 +94,7 @@ Vec3 ClosestPointOnSegment(const Vec3& p, const Vec3& a, const Vec3& b, const Vec3 ab = b - a; const f32 denominator = Dot(ab, ab); *t = denominator > 1.0e-12f - ? std::clamp(Dot(p - a, ab) / denominator, 0.0f, 1.0f) + ? rx::Clamp(Dot(p - a, ab) / denominator, 0.0f, 1.0f) : 0; return a + ab * *t; } @@ -200,42 +205,42 @@ f64 Dot64(const Vec3& a, const Vec3& b) { u32 FindCubicRoots(f64 c0, f64 c1, f64 c2, f64 c3, f32 roots[4]) { const f64 scale = - std::abs(c0) + std::abs(c1) + std::abs(c2) + std::abs(c3) + 1.0; + ::abs(c0) + ::abs(c1) + ::abs(c2) + ::abs(c3) + 1.0; const f64 epsilon = scale * 1.0e-10; f64 points[4] = {0, 1, 0, 0}; u32 point_count = 2; const f64 da = 3 * c3; const f64 db = 2 * c2; const f64 dc = c1; - if (std::abs(da) > epsilon) { + if (::abs(da) > epsilon) { const f64 discriminant = db * db - 4 * da * dc; if (discriminant >= 0) { - const f64 root = std::sqrt(discriminant); + const f64 root = ::sqrt(discriminant); const f64 t0 = (-db - root) / (2 * da); const f64 t1 = (-db + root) / (2 * da); if (t0 > 0 && t0 < 1 && point_count < 4) points[point_count++] = t0; if (t1 > 0 && t1 < 1 && point_count < 4) points[point_count++] = t1; } - } else if (std::abs(db) > epsilon) { + } else if (::abs(db) > epsilon) { const f64 t = -dc / db; if (t > 0 && t < 1 && point_count < 4) points[point_count++] = t; } for (u32 i = 1; i < point_count; ++i) { for (u32 j = i; j > 0 && points[j] < points[j - 1]; --j) - std::swap(points[j], points[j - 1]); + base::Swap(points[j], points[j - 1]); } auto evaluate = [&](f64 t) { return ((c3 * t + c2) * t + c1) * t + c0; }; u32 root_count = 0; auto add_root = [&](f64 root) { if (root < -epsilon || root > 1 + epsilon) return; - const f32 value = static_cast(std::clamp(root, 0.0, 1.0)); + const f32 value = static_cast(rx::Clamp(root, 0.0, 1.0)); if (root_count < 4 && (root_count == 0 || - std::abs(value - roots[root_count - 1]) > 1.0e-5f)) { + ::abs(value - roots[root_count - 1]) > 1.0e-5f)) { roots[root_count++] = value; } }; for (u32 i = 0; i < point_count; ++i) { - if (std::abs(evaluate(points[i])) <= epsilon) add_root(points[i]); + if (::abs(evaluate(points[i])) <= epsilon) add_root(points[i]); if (i + 1 == point_count) continue; f64 low = points[i], high = points[i + 1]; f64 low_value = evaluate(low); @@ -253,7 +258,9 @@ u32 FindCubicRoots(f64 c0, f64 c1, f64 c2, f64 c3, f32 roots[4]) { } add_root(0.5 * (low + high)); } - std::sort(roots, roots + root_count); + // At most four roots: libstdc++ and base both insertion-sort a range this + // short, and insertion sort is stable, so the result is identical. + base::Sort(roots, roots + root_count); return root_count; } @@ -283,7 +290,7 @@ bool FindVertexFaceContact(const Vec3& p0, const Vec3& p1, const Vec3& a0, const Vec3 vb = b1 - b0; const Vec3 vc = c1 - c0; const f32 speed = - std::max({Length(vp - va), Length(vp - vb), Length(vp - vc)}); + rx::Max({Length(vp - va), Length(vp - vb), Length(vp - vc)}); f32 time = 0; for (u32 step = 0; step < 64 && time <= 1; ++step) { const Vec3 p = Lerp(p0, p1, time); @@ -325,10 +332,10 @@ bool FindVertexFaceContact(const Vec3& p0, const Vec3& p1, const Vec3& a0, const Vec3 c = Lerp(c0, c1, root); f32 wa = 0, wb = 0, wc = 0; const Vec3 closest = ClosestPointOnTriangle(p, a, b, c, &wa, &wb, &wc); - if (Length(p - closest) > std::max(distance * 0.1f, 1.0e-5f)) continue; + if (Length(p - closest) > rx::Max(distance * 0.1f, 1.0e-5f)) continue; Vec3 normal = Normalize(Cross(b - a, c - a)); if (Length(normal) < 0.5f) continue; - const f32 before = std::max(root - 1.0e-4f, 0.0f); + const f32 before = rx::Max(root - 1.0e-4f, 0.0f); const Vec3 before_p = Lerp(p0, p1, before); const Vec3 before_a = Lerp(a0, a1, before); const Vec3 before_b = Lerp(b0, b1, before); @@ -357,25 +364,25 @@ void ClosestPointsOnSegments(const Vec3& p1, const Vec3& q1, const Vec3& p2, *s = *t = 0; } else if (a <= 1.0e-12f) { *s = 0; - *t = std::clamp(f / e, 0.0f, 1.0f); + *t = rx::Clamp(f / e, 0.0f, 1.0f); } else { const f32 c = Dot(d1, r); if (e <= 1.0e-12f) { *t = 0; - *s = std::clamp(-c / a, 0.0f, 1.0f); + *s = rx::Clamp(-c / a, 0.0f, 1.0f); } else { const f32 b = Dot(d1, d2); const f32 denominator = a * e - b * b; *s = denominator != 0 - ? std::clamp((b * f - c * e) / denominator, 0.0f, 1.0f) + ? rx::Clamp((b * f - c * e) / denominator, 0.0f, 1.0f) : 0; *t = (b * *s + f) / e; if (*t < 0) { *t = 0; - *s = std::clamp(-c / a, 0.0f, 1.0f); + *s = rx::Clamp(-c / a, 0.0f, 1.0f); } else if (*t > 1) { *t = 1; - *s = std::clamp((b - c) / a, 0.0f, 1.0f); + *s = rx::Clamp((b - c) / a, 0.0f, 1.0f); } } } @@ -397,7 +404,7 @@ bool FindEdgeContact(const Vec3& a0, const Vec3& a1, const Vec3& b0, const Vec3 vb = b1 - b0; const Vec3 vc = c1 - c0; const Vec3 vd = d1 - d0; - const f32 speed = std::max( + const f32 speed = rx::Max( {Length(va - vc), Length(va - vd), Length(vb - vc), Length(vb - vd)}); f32 time = 0; for (u32 step = 0; step < 64 && time <= 1; ++step) { @@ -434,11 +441,11 @@ bool FindEdgeContact(const Vec3& a0, const Vec3& a1, const Vec3& b0, f32 edge_a = 0, edge_b = 0; Vec3 point_a, point_b; ClosestPointsOnSegments(a, b, c, d, &edge_a, &edge_b, &point_a, &point_b); - if (Length(point_a - point_b) > std::max(distance * 0.1f, 1.0e-5f)) + if (Length(point_a - point_b) > rx::Max(distance * 0.1f, 1.0e-5f)) continue; Vec3 normal = Normalize(Cross(b - a, d - c)); if (Length(normal) < 0.5f) continue; - const f32 before = std::max(root - 1.0e-4f, 0.0f); + const f32 before = rx::Max(root - 1.0e-4f, 0.0f); f32 before_a_weight = 0, before_b_weight = 0; Vec3 before_a, before_b; ClosestPointsOnSegments(Lerp(a0, a1, before), Lerp(b0, b1, before), @@ -455,17 +462,17 @@ bool FindEdgeContact(const Vec3& a0, const Vec3& a1, const Vec3& b0, using Bounds = ClothSelfCollisionScratch::Bounds; Bounds EmptyBounds() { - const f32 inf = std::numeric_limits::infinity(); + const f32 inf = INFINITY; return {{inf, inf, inf}, {-inf, -inf, -inf}}; } void Expand(Bounds* bounds, const Vec3& point) { - bounds->low = {std::min(bounds->low.x, point.x), - std::min(bounds->low.y, point.y), - std::min(bounds->low.z, point.z)}; - bounds->high = {std::max(bounds->high.x, point.x), - std::max(bounds->high.y, point.y), - std::max(bounds->high.z, point.z)}; + bounds->low = {rx::Min(bounds->low.x, point.x), + rx::Min(bounds->low.y, point.y), + rx::Min(bounds->low.z, point.z)}; + bounds->high = {rx::Max(bounds->high.x, point.x), + rx::Max(bounds->high.y, point.y), + rx::Max(bounds->high.z, point.z)}; } void Expand(Bounds* bounds, const Bounds& other) { @@ -511,10 +518,13 @@ u32 BuildBvhNode(u32 begin, u32 end, ClothSelfCollisionScratch* scratch) { const Vec3 center = (primitive_bounds.low + primitive_bounds.high) * 0.5f; return axis == 0 ? center.x : (axis == 1 ? center.y : center.z); }; - std::nth_element(scratch->primitives.begin() + begin, - scratch->primitives.begin() + middle, - scratch->primitives.begin() + end, - [&](u32 a, u32 b) { return coordinate(a) < coordinate(b); }); + // Ties on the axis break by primitive id: with a total order, which + // primitives land on each side is fixed by the mesh alone. + u32* primitives = scratch->primitives.data(); + rx::NthElement(primitives + begin, primitives + middle, primitives + end, [&](u32 a, u32 b) { + const f32 ca = coordinate(a), cb = coordinate(b); + return ca != cb ? ca < cb : a < b; + }); const u32 left = BuildBvhNode(begin, middle, scratch); const u32 right = BuildBvhNode(middle, end, scratch); scratch->bvh[node_index].left = left; @@ -598,8 +608,8 @@ bool BuildClothTopology(const Vec3* positions, u32 vertex_count, } const f64 area_twice = TriangleAreaTwice(positions[a], positions[b], positions[c]); - if (!std::isfinite(area_twice) || area_twice < kMinAreaTwice || - area_twice > std::numeric_limits::max()) { + if (!::isfinite(area_twice) || area_twice < kMinAreaTwice || + area_twice > FLT_MAX) { return false; } triangle_areas.push_back(static_cast(area_twice * 0.5)); @@ -609,7 +619,7 @@ bool BuildClothTopology(const Vec3* positions, u32 vertex_count, faces.push_back(MakeFaceKey(a, b, c)); const f64 volume = SignedVolumeContribution(positions[a], positions[b], positions[c]); - if (!std::isfinite(volume)) return false; + if (!::isfinite(volume)) return false; signed_volume += volume; neighbors.push_back({a, b}); neighbors.push_back({a, c}); @@ -622,7 +632,8 @@ bool BuildClothTopology(const Vec3* positions, u32 vertex_count, links.push_back({c, a, b}); } - std::sort(faces.begin(), faces.end(), [](const FaceKey& a, const FaceKey& b) { + // The key is the whole struct, so equal elements are indistinguishable. + base::Sort(faces.data(), faces.data() + faces.size(), [](const FaceKey& a, const FaceKey& b) { return a.a < b.a || (a.a == b.a && (a.b < b.b || (a.b == b.b && a.c < b.c))); }); @@ -630,7 +641,9 @@ bool BuildClothTopology(const Vec3* positions, u32 vertex_count, if (SameFace(faces[i - 1], faces[i])) return false; } - std::sort(edges.begin(), edges.end(), [](const Edge& a, const Edge& b) { + // Ties differ only in `forward`, which is compared symmetrically within a + // group and never read from unique_edges, so their order cannot matter. + base::Sort(edges.data(), edges.data() + edges.size(), [](const Edge& a, const Edge& b) { return a.a < b.a || (a.a == b.a && a.b < b.b); }); bool closed = true; @@ -648,32 +661,40 @@ bool BuildClothTopology(const Vec3* positions, u32 vertex_count, const f64 dx = static_cast(b.x) - a.x; const f64 dy = static_cast(b.y) - a.y; const f64 dz = static_cast(b.z) - a.z; - const f64 length = std::sqrt(dx * dx + dy * dy + dz * dz); - if (!std::isfinite(length)) return false; + const f64 length = ::sqrt(dx * dx + dy * dy + dz * dz); + if (!::isfinite(length)) return false; edge_length += length; ++edge_count; i = end; } - std::sort(neighbors.begin(), neighbors.end(), - [](const Neighbor& a, const Neighbor& b) { - return a.vertex < b.vertex || - (a.vertex == b.vertex && a.neighbor < b.neighbor); - }); - neighbors.erase(std::unique(neighbors.begin(), neighbors.end(), - [](const Neighbor& a, const Neighbor& b) { - return a.vertex == b.vertex && - a.neighbor == b.neighbor; - }), - neighbors.end()); + // The key is the whole struct, so equal elements are indistinguishable. + base::Sort(neighbors.data(), neighbors.data() + neighbors.size(), + [](const Neighbor& a, const Neighbor& b) { + return a.vertex < b.vertex || + (a.vertex == b.vertex && a.neighbor < b.neighbor); + }); + { + size_t kept = 0; + for (size_t i = 0; i < neighbors.size(); ++i) { + if (kept > 0 && neighbors[kept - 1].vertex == neighbors[i].vertex && + neighbors[kept - 1].neighbor == neighbors[i].neighbor) { + continue; + } + neighbors[kept++] = neighbors[i]; + } + neighbors.resize(kept); + } // A manifold vertex has one connected link: a cycle in the interior or a // path on a boundary. This rejects bow-tie vertices where otherwise valid // sheets touch only at one particle. - std::sort(links.begin(), links.end(), - [](const VertexLink& a, const VertexLink& b) { - return a.vertex < b.vertex; - }); + // Ties share a vertex; the per-vertex check below only sorts, counts degree + // and unions over the group, none of which depends on its order. + base::Sort(links.data(), links.data() + links.size(), + [](const VertexLink& a, const VertexLink& b) { + return a.vertex < b.vertex; + }); size_t link_at = 0; for (u32 vertex = 0; vertex < vertex_count; ++vertex) { const size_t begin = link_at; @@ -685,16 +706,16 @@ bool BuildClothTopology(const Vec3* positions, u32 vertex_count, local_vertices.push_back(links[i].a); local_vertices.push_back(links[i].b); } - std::sort(local_vertices.begin(), local_vertices.end()); + base::Sort(local_vertices.data(), local_vertices.data() + local_vertices.size()); local_vertices.erase( - std::unique(local_vertices.begin(), local_vertices.end()), + base::Unique(local_vertices.data(), local_vertices.data() + local_vertices.size()), local_vertices.end()); base::Vector parent; base::Vector degree; parent.resize(local_vertices.size()); degree.resize(local_vertices.size()); - std::iota(parent.begin(), parent.end(), 0u); - std::fill(degree.begin(), degree.end(), 0); + base::Iota(parent.begin(), parent.end(), 0u); + base::Fill(degree.begin(), degree.end(), 0); auto root = [&](u32 node) { while (parent[node] != node) { parent[node] = parent[parent[node]]; @@ -704,13 +725,15 @@ bool BuildClothTopology(const Vec3* positions, u32 vertex_count, }; for (size_t i = begin; i < link_at; ++i) { const u32 a = - static_cast(std::lower_bound(local_vertices.begin(), - local_vertices.end(), links[i].a) - - local_vertices.begin()); + static_cast(base::LowerBound(local_vertices.data(), + local_vertices.data() + local_vertices.size(), + links[i].a) - + local_vertices.data()); const u32 b = - static_cast(std::lower_bound(local_vertices.begin(), - local_vertices.end(), links[i].b) - - local_vertices.begin()); + static_cast(base::LowerBound(local_vertices.data(), + local_vertices.data() + local_vertices.size(), + links[i].b) - + local_vertices.data()); if (++degree[a] > 2 || ++degree[b] > 2) return false; const u32 ra = root(a), rb = root(b); if (ra != rb) parent[rb] = ra; @@ -727,7 +750,7 @@ bool BuildClothTopology(const Vec3* positions, u32 vertex_count, base::Vector component_parent; component_parent.resize(vertex_count); - std::iota(component_parent.begin(), component_parent.end(), 0u); + base::Iota(component_parent.begin(), component_parent.end(), 0u); auto component_root = [&](u32 node) { while (component_parent[node] != node) { component_parent[node] = component_parent[component_parent[node]]; @@ -746,7 +769,7 @@ bool BuildClothTopology(const Vec3* positions, u32 vertex_count, ClothTopology result; result.indices.assign(indices, indices + index_count); - result.triangle_areas = std::move(triangle_areas); + result.triangle_areas = base::move(triangle_areas); result.edges.reserve(unique_edges.size() * 2); for (const Edge& edge : unique_edges) { result.edges.push_back(edge.a); @@ -764,17 +787,17 @@ bool BuildClothTopology(const Vec3* positions, u32 vertex_count, result.neighbor_offsets[vertex_count] = static_cast(result.neighbors.size()); const f64 average_edge_length = edge_count > 0 ? edge_length / edge_count : 0; - if (!std::isfinite(average_edge_length) || - average_edge_length > std::numeric_limits::max() || - !std::isfinite(signed_volume) || - std::abs(signed_volume) > std::numeric_limits::max()) { + if (!::isfinite(average_edge_length) || + average_edge_length > FLT_MAX || + !::isfinite(signed_volume) || + ::abs(signed_volume) > FLT_MAX) { return false; } result.average_edge_length = static_cast(average_edge_length); result.signed_volume = static_cast(signed_volume); result.component_count = component_count; result.closed = closed; - *out = std::move(result); + *out = base::move(result); return true; } @@ -791,13 +814,13 @@ u32 SolveClothSelfCollision(const ClothTopology& topology, return 0; } - const f32 relaxation = std::max(0.0f, std::min(config.relaxation, 1.0f)); - const f32 max_correction = std::max(config.max_velocity, 0.01f) * dt; + const f32 relaxation = rx::Max(0.0f, rx::Min(config.relaxation, 1.0f)); + const f32 max_correction = rx::Max(config.max_velocity, 0.01f) * dt; const u32 triangle_count = static_cast(topology.indices.size() / 3); scratch->predicted.resize(positions.size()); for (size_t i = 0; i < positions.size(); ++i) { if (!IsFinite(positions[i]) || !IsFinite((*velocities)[i]) || - !std::isfinite(inverse_masses[i]) || inverse_masses[i] < 0) { + !::isfinite(inverse_masses[i]) || inverse_masses[i] < 0) { return 0; } scratch->predicted[i] = positions[i] + (*velocities)[i] * dt; @@ -805,7 +828,7 @@ u32 SolveClothSelfCollision(const ClothTopology& topology, } u32 total_contacts = 0; - for (u32 iteration = 0; iteration < std::max(config.iterations, 1u); + for (u32 iteration = 0; iteration < rx::Max(config.iterations, 1u); ++iteration) { BuildBvh( triangle_count, @@ -858,7 +881,7 @@ u32 SolveClothSelfCollision(const ClothTopology& topology, Vec3 correction = contact.normal * ((config.distance - separation) * relaxation / denominator); const f32 correction_length = Length(correction); - if (!IsFinite(correction) || !std::isfinite(correction_length)) return; + if (!IsFinite(correction) || !::isfinite(correction_length)) return; if (correction_length > max_correction) { correction = correction * (max_correction / correction_length); } @@ -929,7 +952,7 @@ u32 SolveClothSelfCollision(const ClothTopology& topology, Vec3 correction = contact.normal * ((config.distance - separation) * relaxation / denominator); const f32 correction_length = Length(correction); - if (!IsFinite(correction) || !std::isfinite(correction_length)) return; + if (!IsFinite(correction) || !::isfinite(correction_length)) return; if (correction_length > max_correction) { correction = correction * (max_correction / correction_length); } diff --git a/engine/physics/kite.cc b/engine/physics/kite.cc index 91abe938..a75543ab 100644 --- a/engine/physics/kite.cc +++ b/engine/physics/kite.cc @@ -1,7 +1,7 @@ +#include "core/scalar.h" #include "physics/kite.h" -#include -#include +#include namespace rx::physics { @@ -14,12 +14,12 @@ f32 Clamp(f32 v, f32 lo, f32 hi) { return v < lo ? lo : (v > hi ? hi : v); } // Yaw quaternion about +Y (x,y,z,w), for spawn orientation. Quat YawQuat(f32 yaw_radians) { - return {0.0f, std::sin(yaw_radians * 0.5f), 0.0f, std::cos(yaw_radians * 0.5f)}; + return {0.0f, ::sin(yaw_radians * 0.5f), 0.0f, ::cos(yaw_radians * 0.5f)}; } // Guards a force/torque against NaN/Inf before it reaches the solver. bool Finite(const Vec3& v) { - return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z); + return ::isfinite(v.x) && ::isfinite(v.y) && ::isfinite(v.z); } } // namespace @@ -31,10 +31,10 @@ Kite::Kite(PhysicsWorld& world, const KiteDesc& desc, const Vec3& anchor, const // from the box density (density = mass / volume) so Jolt derives exactly // desc.mass_kg; the aero model supplies all flight forces, gravity stays // Jolt's (the kite is heavier-than-air, so no buoyancy exemption). - const Vec3 he{std::max(desc_.span_m, 0.05f) * 0.5f, std::max(desc_.height_m, 0.05f) * 0.5f, - std::max(desc_.thickness_m, 0.01f) * 0.5f}; - const f32 volume = std::max(8.0f * he.x * he.y * he.z, 1e-4f); - const f32 density = std::max(desc_.mass_kg, 1e-3f) / volume; + const Vec3 he{rx::Max(desc_.span_m, 0.05f) * 0.5f, rx::Max(desc_.height_m, 0.05f) * 0.5f, + rx::Max(desc_.thickness_m, 0.01f) * 0.5f}; + const f32 volume = rx::Max(8.0f * he.x * he.y * he.z, 1e-4f); + const f32 density = rx::Max(desc_.mass_kg, 1e-3f) / volume; // Spawn in a launch-ready attitude, not flat: a flat plate cannot catch the // wind (you prop a real kite up to launch it). Pitch the sail back about its // span so its belly presents to a tailwind at roughly the trim incidence, then @@ -54,9 +54,9 @@ Kite::Kite(PhysicsWorld& world, const KiteDesc& desc, const Vec3& anchor, const // "added air mass" (the air it swings) that a rigid plate ignores; this floors // the tensor to that effective value so the attitude PD is stable. Scales with // the sail so bigger kites turn more slowly. - const f32 s = std::max(desc_.span_m, 0.1f); - const f32 h = std::max(desc_.height_m, 0.1f); - const f32 inertia = std::max(desc_.mass_kg, 1e-3f) * 0.5f * (s * s + h * h) / 12.0f + 0.08f; + const f32 s = rx::Max(desc_.span_m, 0.1f); + const f32 h = rx::Max(desc_.height_m, 0.1f); + const f32 inertia = rx::Max(desc_.mass_kg, 1e-3f) * 0.5f * (s * s + h * h) / 12.0f + 0.08f; world_.SetBodyInertia(body_, Vec3{inertia, inertia, inertia}); line_length_ = Clamp(desc_.line_length_m, desc_.min_line_m, desc_.max_line_m); state_.position = position; @@ -90,7 +90,7 @@ void Kite::Update(const KiteInput& input, f32 dt) { Vec3 ambient = world_.wind(); if (desc_.gust_amplitude_mps > 0.0f) { // Two-octave sinusoid: a cheap, bounded, deterministic gust. Along gust_dir. - const f32 g = 0.6f * std::sin(gust_time_ * 0.7f) + 0.4f * std::sin(gust_time_ * 2.3f + 1.7f); + const f32 g = 0.6f * ::sin(gust_time_ * 0.7f) + 0.4f * ::sin(gust_time_ * 2.3f + 1.7f); ambient += Normalize(desc_.gust_dir) * (desc_.gust_amplitude_mps * g); } @@ -108,7 +108,7 @@ void Kite::Update(const KiteInput& input, f32 dt) { if (speed > desc_.min_airspeed_mps) { const f32 wn = Dot(w, sail_normal); // signed normal component const Vec3 w_tan = w - sail_normal * wn; // tangential component - const f32 q_area = 0.5f * kAirDensity * std::max(desc_.sail_area_m2, 1e-3f); + const f32 q_area = 0.5f * kAirDensity * rx::Max(desc_.sail_area_m2, 1e-3f); // Pressure normal force: F = q_area * cn * (w.n) * |w|, along the normal. // Effective CN = cn * sin(alpha), the flat-plate linear region. const Vec3 f_normal = sail_normal * (q_area * desc_.normal_coeff * wn * speed); @@ -117,7 +117,7 @@ void Kite::Update(const KiteInput& input, f32 dt) { const Vec3 f_aero = f_normal + f_tan; if (Finite(f_aero)) world_.AddForceAtPoint(body_, f_aero, ac_world); // Angle of attack (sail incidence to the wind): sin(alpha) = (w.n)/|w|. - const f32 alpha = std::asin(Clamp(wn / speed, -1.0f, 1.0f)); + const f32 alpha = ::asin(Clamp(wn / speed, -1.0f, 1.0f)); alpha_deg = alpha * kRadToDeg; // attitude trim: align the belly normal to a belly-UP target @@ -135,7 +135,7 @@ void Kite::Update(const KiteInput& input, f32 dt) { wind_ref_primed_ = true; } else { constexpr f32 kAttTau = 0.35f; // s, attitude-target smoothing time constant - wind_ref_ += (w - wind_ref_) * (1.0f - std::exp(-dt / kAttTau)); + wind_ref_ += (w - wind_ref_) * (1.0f - ::exp(-dt / kAttTau)); } const f32 ref_speed = Length(wind_ref_); if (desc_.attitude_stiffness > 0.0f && ref_speed > desc_.min_airspeed_mps) { @@ -147,8 +147,8 @@ void Kite::Update(const KiteInput& input, f32 dt) { // target stays well-defined (no divide-by-zero, no snap). up_perp = upl > 1e-3f ? up_perp * (1.0f / upl) : Normalize(sail_normal - w_hat * Dot(sail_normal, w_hat)); - const f32 st = std::sin(desc_.trim_alpha_rad); - const f32 ct = std::cos(desc_.trim_alpha_rad); + const f32 st = ::sin(desc_.trim_alpha_rad); + const f32 ct = ::cos(desc_.trim_alpha_rad); const Vec3 n_target = w_hat * st + up_perp * ct; const f32 q_dyn = q_area * speed * speed; // 0.5 rho A |w|^2 const Vec3 align_torque = Cross(sail_normal, n_target) * (desc_.attitude_stiffness * q_dyn); @@ -180,7 +180,7 @@ void Kite::Update(const KiteInput& input, f32 dt) { const f32 losd = Length(los); if (losd > 1e-3f) { const Vec3 los_dir = los * (1.0f / losd); - const f32 dyn = 0.5f * kAirDensity * std::max(desc_.sail_area_m2, 1e-3f) * speed * speed; + const f32 dyn = 0.5f * kAirDensity * rx::Max(desc_.sail_area_m2, 1e-3f) * speed * speed; const Vec3 steer_torque = los_dir * (steer * desc_.steer_authority * dyn); if (Finite(steer_torque)) world_.AddTorque(body_, steer_torque); } diff --git a/engine/physics/physics_world.cc b/engine/physics/physics_world.cc index 19ed3149..64e6dec2 100644 --- a/engine/physics/physics_world.cc +++ b/engine/physics/physics_world.cc @@ -1,12 +1,10 @@ #include "physics/physics_world.h" -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include @@ -54,8 +52,16 @@ #include +#include "base/algorithm.h" +#include "base/atomic.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/threading/lock_guard.h" +#include "base/threading/mutex.h" +#include "base/threading/thread.h" #include "core/feature_registry.h" #include "core/log.h" +#include "core/scalar.h" #include "physics/cloth_collision.h" static_assert(JPH_VERSION_MAJOR > 5 || (JPH_VERSION_MAJOR == 5 && JPH_VERSION_MINOR >= 6), @@ -126,7 +132,7 @@ void TraceCallback(const char* fmt, ...) { char buffer[1024]; va_list args; va_start(args, fmt); - std::vsnprintf(buffer, sizeof(buffer), fmt, args); + ::vsnprintf(buffer, sizeof(buffer), fmt, args); va_end(args); RX_INFO("jolt: {}", buffer); } @@ -141,12 +147,12 @@ JPH::Mat44 ToJolt(const Mat4& m) { } bool IsFinite(const Vec3& value) { - return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z); + return ::isfinite(value.x) && ::isfinite(value.y) && ::isfinite(value.z); } bool IsFinite(const Mat4& value) { for (f32 element : value.m) { - if (!std::isfinite(element)) return false; + if (!::isfinite(element)) return false; } return true; } @@ -237,7 +243,7 @@ const char* SurfaceName(SurfaceType s) { // Rain wetness (0..1) scaling of a surface's grip, lerped toward its wet value. f32 WetGripMultiplier(SurfaceType s, f32 wetness) { const f32 wet = kSurfaceGrip[static_cast(s)].wet; - return 1.0f + (wet - 1.0f) * std::clamp(wetness, 0.0f, 1.0f); + return 1.0f + (wet - 1.0f) * rx::Clamp(wetness, 0.0f, 1.0f); } // Aquaplaning: grip fades as the contact patch floods (depth relative to the @@ -247,10 +253,10 @@ f32 WetGripMultiplier(SurfaceType s, f32 wetness) { // onset speed or on a dry patch this is a no-op (returns 1). f32 AquaplaneGrip(f32 wading_depth, f32 wheel_radius, f32 speed) { if (wading_depth <= 0.0f || wheel_radius <= 0.0f) return 1.0f; - const f32 depth_frac = std::min(wading_depth / (0.5f * wheel_radius), 1.0f); + const f32 depth_frac = rx::Min(wading_depth / (0.5f * wheel_radius), 1.0f); constexpr f32 kOnset = 8.0f; constexpr f32 kFull = 25.0f; - const f32 speed_frac = std::clamp((speed - kOnset) / (kFull - kOnset), 0.0f, 1.0f); + const f32 speed_frac = rx::Clamp((speed - kOnset) / (kFull - kOnset), 0.0f, 1.0f); return 1.0f - 0.9f * depth_frac * speed_frac; } @@ -260,13 +266,13 @@ f32 AquaplaneGrip(f32 wading_depth, f32 wheel_radius, f32 speed) { // so a caller-supplied count above the array size can never read past its end. template void ApplyTorqueCurve(JPH::VehicleEngineSettings& engine, const Point (&curve)[N], u32 count) { - count = std::min(count, static_cast(N)); + count = rx::Min(count, static_cast(N)); if (count == 0) return; engine.mNormalizedTorque.Clear(); engine.mNormalizedTorque.Reserve(count); for (u32 i = 0; i < count; ++i) { - engine.mNormalizedTorque.AddPoint(std::clamp(curve[i].rpm_fraction, 0.0f, 1.0f), - std::max(curve[i].torque_fraction, 0.0f)); + engine.mNormalizedTorque.AddPoint(rx::Clamp(curve[i].rpm_fraction, 0.0f, 1.0f), + rx::Max(curve[i].torque_fraction, 0.0f)); } engine.mNormalizedTorque.Sort(); } @@ -275,7 +281,7 @@ void ApplyTorqueCurve(JPH::VehicleEngineSettings& engine, const Point (&curve)[N // `fade_speed` (m/s), then held. fraction >= 1 or fade_speed <= 0 = no fade. f32 SteerFadeScale(f32 fraction, f32 fade_speed, f32 speed) { if (fade_speed <= 0.0f || fraction >= 1.0f) return 1.0f; - const f32 t = std::clamp(std::fabs(speed) / fade_speed, 0.0f, 1.0f); + const f32 t = rx::Clamp(::fabs(speed) / fade_speed, 0.0f, 1.0f); return 1.0f - (1.0f - fraction) * t; } @@ -285,10 +291,10 @@ struct PhysicsWorld::Impl { BroadPhaseLayers broad_phase_layers; ObjectVsBroadPhase object_vs_broad_phase; ObjectLayerPair object_layer_pair; - std::unique_ptr temp_allocator; + base::UniquePointer temp_allocator; JPH::TempAllocatorImplWithMallocFallback skin_allocator{1024 * 1024}; - std::unique_ptr job_system; - std::unique_ptr system; + base::UniquePointer job_system; + base::UniquePointer system; base::Vector dynamic_bodies; base::UnorderedMap> mesh_shapes; // One PhysicsMaterial per SurfaceType, created lazily. Installed on tagged @@ -438,9 +444,9 @@ struct PhysicsWorld::Impl { // watch/unwatch, reading) still takes `mutex`, and `watched_count` is only // written while holding it. struct ContactRecorder final : public JPH::ContactListener { - std::mutex mutex; + base::Mutex mutex; base::Vector watched; - std::atomic watched_count{0}; + base::Atomic watched_count{0}; // Caller must hold `mutex`. WatchedContacts* Find(JPH::BodyID id) { @@ -458,13 +464,13 @@ struct PhysicsWorld::Impl { // since step 2) and push. Behavior is identical to a single-lock version. void Record(const JPH::Body& body1, const JPH::Body& body2, const JPH::ContactManifold& manifold, const JPH::ContactSettings& settings) { - if (watched_count.load(std::memory_order_acquire) == 0) return; + if (watched_count.load(base::memory_order_acquire) == 0) return; const JPH::BodyID id1 = body1.GetID(); const JPH::BodyID id2 = body2.GetID(); bool watch1; bool watch2; { - std::lock_guard guard(mutex); + base::LockGuard guard(mutex); watch1 = Find(id1) != nullptr; watch2 = Find(id2) != nullptr; } @@ -490,9 +496,9 @@ struct PhysicsWorld::Impl { settings.mCombinedRestitution); f32 impulse = 0; for (f32 point_impulse : estimate.mContactImpulse) impulse += point_impulse; - impulse = std::abs(impulse); + impulse = ::abs(impulse); - std::lock_guard guard(mutex); + base::LockGuard guard(mutex); if (watch1) { if (WatchedContacts* w1 = Find(id1)) { PhysicsWorld::BodyContact contact; @@ -533,7 +539,7 @@ PhysicsWorld::PhysicsWorld() = default; PhysicsWorld::~PhysicsWorld() { if (impl_) { - impl_.reset(); + impl_.Reset(); JPH::UnregisterTypes(); delete JPH::Factory::sInstance; JPH::Factory::sInstance = nullptr; @@ -546,17 +552,17 @@ bool PhysicsWorld::Initialize() { JPH::Factory::sInstance = new JPH::Factory(); JPH::RegisterTypes(); - impl_ = std::make_unique(); + impl_ = base::MakeUnique(); // Large cloth batches can exceed the fixed arena. The normal path remains a // lock-free stack allocation; exceptional peaks fall back instead of // aborting. impl_->temp_allocator = - std::make_unique(32 * 1024 * 1024); - impl_->job_system = std::make_unique( + base::MakeUnique(32 * 1024 * 1024); + impl_->job_system = base::MakeUnique( JPH::cMaxPhysicsJobs, JPH::cMaxPhysicsBarriers, - static_cast(std::thread::hardware_concurrency()) - 1); + static_cast(base::GetProcessorCount()) - 1); - impl_->system = std::make_unique(); + impl_->system = base::MakeUnique(); impl_->system->Init(65536, 0, 65536, 10240, impl_->broad_phase_layers, impl_->object_vs_broad_phase, impl_->object_layer_pair); impl_->system->SetGravity({0, -9.81f, 0}); @@ -572,7 +578,7 @@ void PhysicsWorld::Update(f32 dt) { // Drop last step's recorded contacts before the listener refills them during // this step's collision detection; the buffers keep their capacity. { - std::lock_guard guard(impl_->contacts.mutex); + base::LockGuard guard(impl_->contacts.mutex); for (Impl::WatchedContacts& entry : impl_->contacts.watched) entry.contacts.clear(); } @@ -654,7 +660,7 @@ void PhysicsWorld::Update(f32 dt) { bodies.AddForce(entry.body, press); } else { const JPH::RVec3 com = bodies.GetCenterOfMassPosition(entry.body); - const f32 fb = std::clamp(entry.downforce_balance, 0.0f, 1.0f); + const f32 fb = rx::Clamp(entry.downforce_balance, 0.0f, 1.0f); bodies.AddForce(entry.body, press * fb, com + rotation * JPH::Vec3(0, 0, entry.front_z)); bodies.AddForce(entry.body, press * (1.0f - fb), com + rotation * JPH::Vec3(0, 0, entry.rear_z)); @@ -684,7 +690,7 @@ void PhysicsWorld::Update(f32 dt) { f32 surface_h = 0; Vec3 flow{}; if (!SampleWater(p, &surface_h, &flow) || p.y >= surface_h) continue; - const f32 depth = std::min(surface_h - p.y, 2.0f * wheel->GetSettings()->mRadius); + const f32 depth = rx::Min(surface_h - p.y, 2.0f * wheel->GetSettings()->mRadius); // The VEHICLE's velocity at the contact point (the chassis ploughing // through the water), not Wheel::GetContactPointVelocity() - that is the // velocity of the contacted GROUND body, which is zero on a static road @@ -695,7 +701,7 @@ void PhysicsWorld::Update(f32 dt) { if (speed < 0.1f) continue; const f32 area = wheel->GetSettings()->mWidth * depth; f32 mag = 0.5f * kWaterDensity * kWheelDragCd * area * speed * speed; - mag = std::min(mag, 8000.0f); // per-wheel cap keeps a deep, fast entry stable + mag = rx::Min(mag, 8000.0f); // per-wheel cap keeps a deep, fast entry stable const JPH::Vec3 force = rel * (-mag / speed); bodies.AddForce(entry.body, force, cp); } @@ -740,13 +746,13 @@ void PhysicsWorld::Update(f32 dt) { const JPH::Mat44 to_local = body.GetCenterOfMassTransform().InversedRotationTranslation().ToMat44(); - const f32 pin_duration = std::max(entry.pin_time_remaining, dt); + const f32 pin_duration = rx::Max(entry.pin_time_remaining, dt); for (size_t i = 0; i < entry.pinned.size(); ++i) { JPH::SoftBodyVertex& vertex = vertices[entry.pinned[i]]; const JPH::Vec3 target = to_local * ToJolt(entry.targets[i]); vertex.mVelocity = (target - vertex.mPosition) / pin_duration; } - entry.pin_time_remaining = std::max(entry.pin_time_remaining - dt, 0.0f); + entry.pin_time_remaining = rx::Max(entry.pin_time_remaining - dt, 0.0f); if (entry.aerodynamic_drag > 0) { const JPH::Quat inverse_rotation = body.GetRotation().Conjugated(); @@ -766,9 +772,9 @@ void PhysicsWorld::Update(f32 dt) { const JPH::Vec3 cloth_velocity = (a.mVelocity + b.mVelocity + c.mVelocity) / 3.0f; const f32 normal_speed = (local_wind - cloth_velocity).Dot(normal); const JPH::Vec3 force = normal * (0.25f * kAirDensity * entry.aerodynamic_drag * - area_twice * normal_speed * std::abs(normal_speed)); - if (!std::isfinite(force.GetX()) || !std::isfinite(force.GetY()) || - !std::isfinite(force.GetZ())) { + area_twice * normal_speed * ::abs(normal_speed)); + if (!::isfinite(force.GetX()) || !::isfinite(force.GetY()) || + !::isfinite(force.GetZ())) { continue; } for (JPH::SoftBodyVertex* vertex : {&a, &b, &c}) { @@ -777,12 +783,12 @@ void PhysicsWorld::Update(f32 dt) { // Explicit quadratic drag must not overshoot the air velocity in // one face contribution or it can oscillate and hit the speed cap. const f32 max_delta = - std::min(entry.max_linear_velocity * 0.25f, std::abs(normal_speed)); - if (delta_sq > max_delta * max_delta) delta *= max_delta / std::sqrt(delta_sq); + rx::Min(entry.max_linear_velocity * 0.25f, ::abs(normal_speed)); + if (delta_sq > max_delta * max_delta) delta *= max_delta / ::sqrt(delta_sq); vertex->mVelocity += delta; const f32 speed_sq = vertex->mVelocity.LengthSq(); if (speed_sq > entry.max_linear_velocity * entry.max_linear_velocity) { - vertex->mVelocity *= entry.max_linear_velocity / std::sqrt(speed_sq); + vertex->mVelocity *= entry.max_linear_velocity / ::sqrt(speed_sq); } } } @@ -818,7 +824,7 @@ void PhysicsWorld::Update(f32 dt) { } const JPH::EPhysicsUpdateError error = - impl_->system->Update(dt, 1, impl_->temp_allocator.get(), impl_->job_system.get()); + impl_->system->Update(dt, 1, impl_->temp_allocator.Get_UseOnlyIfYouKnowWhatYouareDoing(), impl_->job_system.Get_UseOnlyIfYouKnowWhatYouareDoing()); if (error != JPH::EPhysicsUpdateError::None) { RX_WARN("jolt update capacity error mask: {}", static_cast(error)); } @@ -856,7 +862,7 @@ BodyId PhysicsWorld::AddStaticMesh(const asset::Mesh& mesh, const Vec3& position JPH::PhysicsMaterialList materials; if (const JPH::PhysicsMaterial* m = impl_->material_for(surface)) materials.push_back(m); JPH::Ref shape_settings = - new JPH::MeshShapeSettings(std::move(vertices), std::move(triangles), std::move(materials)); + new JPH::MeshShapeSettings(base::move(vertices), base::move(triangles), base::move(materials)); if (scale != 1.0f) { shape_settings = new JPH::ScaledShapeSettings(shape_settings, JPH::Vec3::sReplicate(scale)); } @@ -890,7 +896,7 @@ bool PhysicsWorld::RegisterMeshShape(u64 key, const asset::Mesh& mesh) { for (size_t i = 0; i + 2 < lod.indices.size(); i += 3) { triangles.push_back({lod.indices[i], lod.indices[i + 1], lod.indices[i + 2], 0}); } - JPH::MeshShapeSettings settings(std::move(vertices), std::move(triangles)); + JPH::MeshShapeSettings settings(base::move(vertices), base::move(triangles)); JPH::ShapeSettings::ShapeResult result = settings.Create(); if (result.HasError()) return false; impl_->mesh_shapes.insert(key, result.Get()); @@ -1198,7 +1204,7 @@ JointId PhysicsWorld::AddSwingTwistJoint(BodyId a, BodyId b, const f32 frame_a[1 Impl::JointEntry entry; entry.constraint = constraint; entry.hinge = false; - impl_->joints.push_back(std::move(entry)); + impl_->joints.push_back(base::move(entry)); return static_cast(impl_->joints.size()); } @@ -1228,7 +1234,7 @@ JointId PhysicsWorld::AddHingeJoint(BodyId a, BodyId b, const f32 frame_a[12], Impl::JointEntry entry; entry.constraint = constraint; entry.hinge = true; - impl_->joints.push_back(std::move(entry)); + impl_->joints.push_back(base::move(entry)); return static_cast(impl_->joints.size()); } @@ -1260,7 +1266,7 @@ void PhysicsWorld::SetJointMotorTarget(JointId joint, const f32 target_quat[4]) if (entry.hinge) { auto* hinge = static_cast(entry.constraint.GetPtr()); // Twist angle about the hinge axis (constraint-space X) of the target. - f32 angle = 2.0f * std::atan2(q.GetX(), q.GetW()); + f32 angle = 2.0f * ::atan2(q.GetX(), q.GetW()); hinge->SetTargetAngle(angle); } else { auto* st = static_cast(entry.constraint.GetPtr()); @@ -1526,7 +1532,7 @@ CharacterId PhysicsWorld::CreateCharacter(const Vec3& position, f32 radius, f32 // Accept ground contacts on the lower hemisphere so slopes register. settings->mSupportingVolume = JPH::Plane(JPH::Vec3::sAxisY(), -radius); JPH::Ref character = new JPH::CharacterVirtual( - settings, ToJolt(position), JPH::Quat::sIdentity(), 0, impl_->system.get()); + settings, ToJolt(position), JPH::Quat::sIdentity(), 0, impl_->system.Get_UseOnlyIfYouKnowWhatYouareDoing()); impl_->characters.push_back({character, 0.0f}); return impl_->characters.size(); // id = index + 1 } @@ -1610,13 +1616,13 @@ void PhysicsWorld::ConfigureCharacter(CharacterId id, f32 max_slope_angle, f32 s if (!impl_ || id == 0 || id > impl_->characters.size()) return; Impl::CharacterEntry& entry = impl_->characters[id - 1]; entry.character->SetMaxSlopeAngle(max_slope_angle); - entry.step_height = std::max(step_height, 0.0f); + entry.step_height = rx::Max(step_height, 0.0f); } bool PhysicsWorld::SetCharacterShape(CharacterId id, f32 radius, f32 half_height) { if (!impl_ || id == 0 || id > impl_->characters.size()) return false; JPH::CharacterVirtual* character = impl_->characters[id - 1].character; - JPH::Ref shape = new JPH::CapsuleShape(std::max(half_height, 0.0f), radius); + JPH::Ref shape = new JPH::CapsuleShape(rx::Max(half_height, 0.0f), radius); // A small penetration tolerance keeps a graze from vetoing an otherwise-clear // stand-up; a genuinely blocked capsule overlaps far deeper than this. return character->SetShape( @@ -1628,7 +1634,7 @@ bool PhysicsWorld::SphereCast(const Vec3& origin, const Vec3& direction, f32 max f32 radius, RayHit* out) const { if (!impl_) return false; Vec3 dir = Normalize(direction); - JPH::Ref sphere = new JPH::SphereShape(std::max(radius, 1e-3f)); + JPH::Ref sphere = new JPH::SphereShape(rx::Max(radius, 1e-3f)); JPH::RShapeCast shape_cast(sphere, JPH::Vec3::sReplicate(1.0f), JPH::RMat44::sTranslation(ToJolt(origin)), ToJolt(dir) * max_distance); @@ -1712,14 +1718,14 @@ void PhysicsWorld::RemoveBody(BodyId id) { // Drop any watched-contact entry for this body so the recorder's list does not // grow forever (and a later body reusing the BodyID sequence cannot alias it). { - std::lock_guard guard(impl_->contacts.mutex); + base::LockGuard guard(impl_->contacts.mutex); base::Vector& watched = impl_->contacts.watched; for (size_t i = 0; i < watched.size(); ++i) { if (watched[i].body == body) { - watched[i] = std::move(watched.back()); + watched[i] = base::move(watched.back()); watched.pop_back(); impl_->contacts.watched_count.store(static_cast(watched.size()), - std::memory_order_release); + base::memory_order_release); break; } } @@ -1779,7 +1785,7 @@ VehicleId PhysicsWorld::CreateVehicle(const VehicleDesc& desc, const Vec3& posit if (desc.max_brake_torque > 0 || desc.brake_bias_front != 0.5f) { const f32 base = desc.max_brake_torque > 0 ? desc.max_brake_torque : 1500.0f; const f32 share = front ? desc.brake_bias_front : (1.0f - desc.brake_bias_front); - w->mMaxBrakeTorque = base * 2.0f * std::clamp(share, 0.0f, 1.0f); + w->mMaxBrakeTorque = base * 2.0f * rx::Clamp(share, 0.0f, 1.0f); } // Suspension spring: per-axle frequency (falls back to the shared value, // then Jolt's default); shared damping. @@ -1951,7 +1957,7 @@ VehicleId PhysicsWorld::CreateMotorcycle(const MotorcycleDesc& desc, const Vec3& front->mPosition = JPH::Vec3(0, attach_y, desc.front_z); front->mMaxSteerAngle = desc.max_steer_angle; // Caster: the fork rakes back, and the steering axis follows the fork. - front->mSuspensionDirection = JPH::Vec3(0, -1, std::tan(desc.caster_angle)).Normalized(); + front->mSuspensionDirection = JPH::Vec3(0, -1, ::tan(desc.caster_angle)).Normalized(); front->mSteeringAxis = -front->mSuspensionDirection; front->mRadius = desc.wheel_radius; front->mWidth = desc.wheel_width; @@ -2081,8 +2087,8 @@ void PhysicsWorld::InstallVehicleFriction(u32 vehicle_index) { const Impl::VehicleEntry& entry = impl_->vehicles[vehicle_index]; if (IsLooseSurface(surf)) { const SurfaceGripEntry& hard = kSurfaceGrip[static_cast(SurfaceType::kAsphalt)]; - grip_long = std::min(grip_long * entry.offroad_grip, hard.longitudinal * wet); - grip_lat = std::min(grip_lat * entry.offroad_grip, hard.lateral * wet); + grip_long = rx::Min(grip_long * entry.offroad_grip, hard.longitudinal * wet); + grip_lat = rx::Min(grip_lat * entry.offroad_grip, hard.lateral * wet); } // Aquaplaning from the per-wheel water cache filled on the game thread at the @@ -2101,7 +2107,7 @@ void PhysicsWorld::InstallVehicleFriction(u32 vehicle_index) { } void PhysicsWorld::set_surface_wetness(f32 wetness) { - surface_wetness_ = std::clamp(wetness, 0.0f, 1.0f); + surface_wetness_ = rx::Clamp(wetness, 0.0f, 1.0f); } void PhysicsWorld::SetManualTransmission(VehicleId id, bool manual) { @@ -2146,7 +2152,7 @@ void PhysicsWorld::DriveVehicle(VehicleId id, const VehicleInput& input) { entry.manual_shifting = shifted; // Clutch: 1 = fully disengaged -> 0 friction, so the engine spins free of the // wheels. Applied every step so the pedal tracks continuously between shifts. - trans.Set(gear, std::clamp(1.0f - input.clutch, 0.0f, 1.0f)); + trans.Set(gear, rx::Clamp(1.0f - input.clutch, 0.0f, 1.0f)); // Route the throttle through the same traction-control shaping as the // automatic path so manual mode keeps the driver aid instead of bypassing it. const f32 throttle = TractionControlThrottle(id - 1, input.throttle); @@ -2172,19 +2178,19 @@ f32 PhysicsWorld::TractionControlThrottle(u32 vehicle_index, f32 forward) { JPH::BodyInterface& bodies = impl_->system->GetBodyInterface(); const JPH::Vec3 velocity = bodies.GetLinearVelocity(entry.body); const JPH::Vec3 fwd_axis = bodies.GetRotation(entry.body) * JPH::Vec3::sAxisZ(); - if (std::fabs(velocity.Dot(fwd_axis)) > 5.0f) { + if (::fabs(velocity.Dot(fwd_axis)) > 5.0f) { f32 max_slip = 0; for (u32 i = 0; i < entry.wheel_count; ++i) { const auto* wheel = static_cast(entry.constraint->GetWheel(i)); - if (wheel->HasContact()) max_slip = std::max(max_slip, wheel->mLongitudinalSlip); + if (wheel->HasContact()) max_slip = rx::Max(max_slip, wheel->mLongitudinalSlip); } // Proportional tracker: aim the throttle at the fraction that would put the // worst wheel on the slip target, smoothed so cut and recovery move at the // same rate (a ratcheting governor parks at its floor). constexpr f32 kSlipTarget = 0.09f; - const f32 target = max_slip > kSlipTarget ? std::max(0.12f, kSlipTarget / max_slip) : 1.0f; + const f32 target = max_slip > kSlipTarget ? rx::Max(0.12f, kSlipTarget / max_slip) : 1.0f; entry.tc_scale += (target - entry.tc_scale) * 0.25f; - forward *= std::min(1.0f, std::max(0.12f, entry.tc_scale)); + forward *= rx::Min(1.0f, rx::Max(0.12f, entry.tc_scale)); } else { entry.tc_scale = 1; } @@ -2274,11 +2280,11 @@ bool PhysicsWorld::GetVehicleState(VehicleId id, VehicleState* out) const { // ~0 to the wheels even though the engine still revs. GetTorque is linear in // its input, so load == delivered/max at this rpm. const f32 clutch = trans.GetClutchFriction(); - const f32 applied = std::fabs(controller->GetForwardInput()) * clutch; + const f32 applied = ::fabs(controller->GetForwardInput()) * clutch; const f32 max_torque_at_rpm = engine.GetTorque(1.0f); out->engine_torque = engine.GetTorque(applied); out->engine_load = - max_torque_at_rpm > 1.0e-3f ? std::clamp(out->engine_torque / max_torque_at_rpm, 0.0f, 1.0f) + max_torque_at_rpm > 1.0e-3f ? rx::Clamp(out->engine_torque / max_torque_at_rpm, 0.0f, 1.0f) : 0.0f; // is_shifting means a ratio change is in progress. The automatic box slips its // clutch through a change (clutch < 1 flags it); manual mode's clutch is the @@ -2298,7 +2304,7 @@ bool PhysicsWorld::GetVehicleState(VehicleId id, VehicleState* out) const { const f32 travel = settings->mSuspensionMaxLength - settings->mSuspensionMinLength; ws.suspension_compression = travel > 1.0e-4f - ? std::clamp((settings->mSuspensionMaxLength - wheel->GetSuspensionLength()) / travel, + ? rx::Clamp((settings->mSuspensionMaxLength - wheel->GetSuspensionLength()) / travel, 0.0f, 1.0f) : 0.0f; ws.longitudinal_slip = wheel->mLongitudinalSlip; @@ -2469,7 +2475,7 @@ StrandGroomId PhysicsWorld::CreateStrandGroom(const StrandGroomDesc& desc, } } - impl_->strand_grooms.push_back(std::move(entry)); + impl_->strand_grooms.push_back(base::move(entry)); return impl_->strand_grooms.size(); } @@ -2567,22 +2573,22 @@ ClothId PhysicsWorld::CreateCloth(const ClothDesc& desc, const Mat4& transform) !IsFinite(transform)) { return 0; } - if (!std::isfinite(desc.areal_density) || desc.areal_density <= 0 || - !std::isfinite(desc.warp_compliance) || desc.warp_compliance < 0 || - !std::isfinite(desc.weft_compliance) || desc.weft_compliance < 0 || - !std::isfinite(desc.shear_compliance) || desc.shear_compliance < 0 || - !std::isfinite(desc.bend_compliance) || desc.bend_compliance < 0 || - !std::isfinite(desc.max_stretch) || desc.max_stretch < 1 || - !std::isfinite(desc.collision_radius) || desc.collision_radius < 0 || - !std::isfinite(desc.self_collision_distance) || desc.self_collision_distance < 0 || - !std::isfinite(desc.self_collision_relaxation) || desc.self_collision_relaxation < 0 || + if (!::isfinite(desc.areal_density) || desc.areal_density <= 0 || + !::isfinite(desc.warp_compliance) || desc.warp_compliance < 0 || + !::isfinite(desc.weft_compliance) || desc.weft_compliance < 0 || + !::isfinite(desc.shear_compliance) || desc.shear_compliance < 0 || + !::isfinite(desc.bend_compliance) || desc.bend_compliance < 0 || + !::isfinite(desc.max_stretch) || desc.max_stretch < 1 || + !::isfinite(desc.collision_radius) || desc.collision_radius < 0 || + !::isfinite(desc.self_collision_distance) || desc.self_collision_distance < 0 || + !::isfinite(desc.self_collision_relaxation) || desc.self_collision_relaxation < 0 || desc.self_collision_relaxation > 1 || - !std::isfinite(desc.aerodynamic_drag) || desc.aerodynamic_drag < 0 || - !std::isfinite(desc.damping) || desc.damping < 0 || - !std::isfinite(desc.gravity_factor) || !std::isfinite(desc.friction) || desc.friction < 0 || - !std::isfinite(desc.restitution) || desc.restitution < 0 || - !std::isfinite(desc.pressure) || desc.pressure < 0 || - !std::isfinite(desc.max_linear_velocity) || desc.max_linear_velocity <= 0 || + !::isfinite(desc.aerodynamic_drag) || desc.aerodynamic_drag < 0 || + !::isfinite(desc.damping) || desc.damping < 0 || + !::isfinite(desc.gravity_factor) || !::isfinite(desc.friction) || desc.friction < 0 || + !::isfinite(desc.restitution) || desc.restitution < 0 || + !::isfinite(desc.pressure) || desc.pressure < 0 || + !::isfinite(desc.max_linear_velocity) || desc.max_linear_velocity <= 0 || desc.iterations > 64 || desc.self_collision_iterations > 8 || (desc.self_collision_distance > 0 && desc.self_collision_iterations == 0)) { RX_WARN("cloth rejected: invalid material or collision parameters"); @@ -2608,7 +2614,7 @@ ClothId PhysicsWorld::CreateCloth(const ClothDesc& desc, const Mat4& transform) return 0; } const bool pressure_capable = topology.closed && topology.component_count == 1 && - std::isfinite(topology.signed_volume) && + ::isfinite(topology.signed_volume) && topology.signed_volume > 1.0e-9f; if (desc.pressure > 0 && !pressure_capable) { RX_WARN("cloth rejected: pressure requires one closed, outward-wound volume"); @@ -2616,7 +2622,7 @@ ClothId PhysicsWorld::CreateCloth(const ClothDesc& desc, const Mat4& transform) } if (desc.uvs) { for (u32 i = 0; i < desc.vertex_count * 2; ++i) { - if (!std::isfinite(desc.uvs[i])) { + if (!::isfinite(desc.uvs[i])) { RX_WARN("cloth rejected: non-finite material UV"); return 0; } @@ -2625,7 +2631,7 @@ ClothId PhysicsWorld::CreateCloth(const ClothDesc& desc, const Mat4& transform) base::Vector pin_mask; pin_mask.resize(desc.vertex_count); - std::fill(pin_mask.begin(), pin_mask.end(), 0); + base::Fill(pin_mask.begin(), pin_mask.end(), 0); for (u32 i = 0; i < desc.pin_count; ++i) { const u32 vertex = desc.pins[i]; if (vertex >= desc.vertex_count || pin_mask[vertex]) { @@ -2647,14 +2653,14 @@ ClothId PhysicsWorld::CreateCloth(const ClothDesc& desc, const Mat4& transform) skinned.resize(desc.vertex_count); hard_skinned.resize(desc.vertex_count); backstopped.resize(desc.vertex_count); - std::fill(skinned.begin(), skinned.end(), 0); - std::fill(hard_skinned.begin(), hard_skinned.end(), 0); - std::fill(backstopped.begin(), backstopped.end(), 0); + base::Fill(skinned.begin(), skinned.end(), 0); + base::Fill(hard_skinned.begin(), hard_skinned.end(), 0); + base::Fill(backstopped.begin(), backstopped.end(), 0); for (u32 i = 0; i < desc.skin_constraint_count; ++i) { const ClothSkinConstraint& skin = desc.skin_constraints[i]; if (skin.vertex >= desc.vertex_count || skinned[skin.vertex] || pin_mask[skin.vertex] || - !std::isfinite(skin.max_distance) || skin.max_distance < 0 || - !std::isfinite(skin.backstop_distance) || !std::isfinite(skin.backstop_radius) || + !::isfinite(skin.max_distance) || skin.max_distance < 0 || + !::isfinite(skin.backstop_distance) || !::isfinite(skin.backstop_radius) || skin.backstop_radius < 0) { RX_WARN("cloth rejected: invalid skin constraint"); return 0; @@ -2662,7 +2668,7 @@ ClothId PhysicsWorld::CreateCloth(const ClothDesc& desc, const Mat4& transform) u32 weight_count = 0; for (const ClothSkinWeight& weight : skin.weights) { if (weight.weight == 0) continue; - if (!std::isfinite(weight.weight) || weight.weight < 0 || weight.joint >= desc.joint_count || + if (!::isfinite(weight.weight) || weight.weight < 0 || weight.joint >= desc.joint_count || weight_count >= JPH::SoftBodySharedSettings::Skinned::cMaxSkinWeights) { RX_WARN("cloth rejected: invalid skin weight"); return 0; @@ -2679,7 +2685,7 @@ ClothId PhysicsWorld::CreateCloth(const ClothDesc& desc, const Mat4& transform) } base::Vector skinned_face_counts; skinned_face_counts.resize(desc.vertex_count); - std::fill(skinned_face_counts.begin(), skinned_face_counts.end(), 0); + base::Fill(skinned_face_counts.begin(), skinned_face_counts.end(), 0); for (u32 face = 0; face < desc.index_count; face += 3) { const u32 a = desc.indices[face + 0]; const u32 b = desc.indices[face + 1]; @@ -2701,7 +2707,7 @@ ClothId PhysicsWorld::CreateCloth(const ClothDesc& desc, const Mat4& transform) inverse_masses.resize(desc.vertex_count); if (desc.inverse_masses) { for (u32 i = 0; i < desc.vertex_count; ++i) { - if (!std::isfinite(desc.inverse_masses[i]) || desc.inverse_masses[i] < 0) { + if (!::isfinite(desc.inverse_masses[i]) || desc.inverse_masses[i] < 0) { RX_WARN("cloth rejected: inverse masses must be finite and non-negative"); return 0; } @@ -2710,22 +2716,22 @@ ClothId PhysicsWorld::CreateCloth(const ClothDesc& desc, const Mat4& transform) } else { base::Vector masses; masses.resize(desc.vertex_count); - std::fill(masses.begin(), masses.end(), 0.0); + base::Fill(masses.begin(), masses.end(), 0.0); for (size_t i = 0; i < topology.indices.size(); i += 3) { const u32 a = topology.indices[i + 0]; const u32 b = topology.indices[i + 1]; const u32 c = topology.indices[i + 2]; const f64 triangle_mass = static_cast(topology.triangle_areas[i / 3]) * desc.areal_density; - if (!std::isfinite(triangle_mass)) return 0; + if (!::isfinite(triangle_mass)) return 0; masses[a] += triangle_mass / 3.0; masses[b] += triangle_mass / 3.0; masses[c] += triangle_mass / 3.0; } for (u32 i = 0; i < desc.vertex_count; ++i) { const f64 inverse_mass = 1.0 / masses[i]; - if (masses[i] <= 1.0e-8 || !std::isfinite(masses[i]) || - !std::isfinite(inverse_mass) || inverse_mass > std::numeric_limits::max()) { + if (masses[i] <= 1.0e-8 || !::isfinite(masses[i]) || + !::isfinite(inverse_mass) || inverse_mass > FLT_MAX) { RX_WARN("cloth rejected: every vertex must belong to a non-degenerate face"); return 0; } @@ -2785,7 +2791,7 @@ ClothId PhysicsWorld::CreateCloth(const ClothDesc& desc, const Mat4& transform) // extra non-diagonal springs in distance-bend mode retain bend compliance. if (desc.uvs) { auto is_topology_edge = [&](u32 a, u32 b) { - if (a > b) std::swap(a, b); + if (a > b) base::Swap(a, b); size_t low = 0, high = topology.edges.size() / 2; while (low < high) { const size_t middle = (low + high) / 2; @@ -2800,9 +2806,9 @@ ClothId PhysicsWorld::CreateCloth(const ClothDesc& desc, const Mat4& transform) topology.edges[low * 2 + 1] == b; }; for (JPH::SoftBodySharedSettings::Edge& edge : shared->mEdgeConstraints) { - const f32 du = std::abs(desc.uvs[edge.mVertex[1] * 2 + 0] - + const f32 du = ::abs(desc.uvs[edge.mVertex[1] * 2 + 0] - desc.uvs[edge.mVertex[0] * 2 + 0]); - const f32 dv = std::abs(desc.uvs[edge.mVertex[1] * 2 + 1] - + const f32 dv = ::abs(desc.uvs[edge.mVertex[1] * 2 + 1] - desc.uvs[edge.mVertex[0] * 2 + 1]); const bool warp = du > 2.0f * dv; const bool weft = dv > 2.0f * du; @@ -2851,14 +2857,14 @@ ClothId PhysicsWorld::CreateCloth(const ClothDesc& desc, const Mat4& transform) JPH::SoftBodyCreationSettings body(shared, ToJolt(origin), JPH::Quat::sIdentity(), layers::kDynamic); - body.mNumIterations = std::max(desc.iterations, 1u); - body.mLinearDamping = std::max(desc.damping, 0.0f); + body.mNumIterations = rx::Max(desc.iterations, 1u); + body.mLinearDamping = rx::Max(desc.damping, 0.0f); body.mGravityFactor = desc.gravity_factor; - body.mFriction = std::max(desc.friction, 0.0f); - body.mRestitution = std::max(desc.restitution, 0.0f); + body.mFriction = rx::Max(desc.friction, 0.0f); + body.mRestitution = rx::Max(desc.restitution, 0.0f); body.mPressure = desc.pressure; body.mVertexRadius = desc.collision_radius; - body.mMaxLinearVelocity = std::max(desc.max_linear_velocity, 0.01f); + body.mMaxLinearVelocity = rx::Max(desc.max_linear_velocity, 0.01f); body.mUpdatePosition = desc.update_position; body.mMakeRotationIdentity = true; body.mAllowSleeping = desc.allow_sleeping; @@ -2876,8 +2882,8 @@ ClothId PhysicsWorld::CreateCloth(const ClothDesc& desc, const Mat4& transform) entry.aerodynamic_drag = desc.aerodynamic_drag; entry.gravity_factor = desc.gravity_factor; entry.max_linear_velocity = desc.max_linear_velocity; - entry.collision_inverse_masses = std::move(inverse_masses); - entry.topology = std::move(topology); + entry.collision_inverse_masses = base::move(inverse_masses); + entry.topology = base::move(topology); entry.self_collision.distance = desc.self_collision_distance; entry.self_collision.relaxation = desc.self_collision_relaxation; entry.self_collision.max_velocity = desc.max_linear_velocity; @@ -2899,13 +2905,13 @@ ClothId PhysicsWorld::CreateCloth(const ClothDesc& desc, const Mat4& transform) static_cast(lock.GetBody().GetMotionProperties()); soft->SetEnableSkinConstraints(false); } - impl_->cloth.push_back(std::move(entry)); + impl_->cloth.push_back(base::move(entry)); return impl_->cloth.size(); } bool PhysicsWorld::SetClothTransform(ClothId id, const Mat4& transform, f32 dt) { if (!impl_ || id == 0 || id > impl_->cloth.size() || !IsFinite(transform) || - !std::isfinite(dt) || dt < 0) { + !::isfinite(dt) || dt < 0) { return false; } Impl::ClothEntry& entry = impl_->cloth[id - 1]; @@ -2919,7 +2925,7 @@ bool PhysicsWorld::SetClothTransform(ClothId id, const Mat4& transform, f32 dt) } bool PhysicsWorld::SetClothPinTargets(ClothId id, const Vec3* targets, u32 target_count, f32 dt) { - if (!impl_ || id == 0 || id > impl_->cloth.size() || !std::isfinite(dt) || dt < 0) return false; + if (!impl_ || id == 0 || id > impl_->cloth.size() || !::isfinite(dt) || dt < 0) return false; Impl::ClothEntry& entry = impl_->cloth[id - 1]; if (!entry.alive || target_count != entry.pinned.size() || (target_count > 0 && !targets)) { return false; @@ -2966,7 +2972,7 @@ bool PhysicsWorld::SetClothJointTransforms(ClothId id, const Mat4* world_joints, if (!IsFinite(world_joints[i])) return false; if (!changed) { for (u32 element = 0; element < 16; ++element) { - if (std::abs(world_joints[i].m[element] - entry.last_joint_transforms[i].m[element]) > + if (::abs(world_joints[i].m[element] - entry.last_joint_transforms[i].m[element]) > 1.0e-7f) { changed = true; break; @@ -3007,7 +3013,7 @@ void PhysicsWorld::SetClothWind(ClothId id, const Vec3& velocity) { } void PhysicsWorld::SetClothPressure(ClothId id, f32 pressure) { - if (!impl_ || id == 0 || id > impl_->cloth.size() || !std::isfinite(pressure) || pressure < 0) { + if (!impl_ || id == 0 || id > impl_->cloth.size() || !::isfinite(pressure) || pressure < 0) { return; } Impl::ClothEntry& entry = impl_->cloth[id - 1]; @@ -3111,38 +3117,38 @@ bool PhysicsWorld::GetBodyTransform(BodyId id, Vec3* position, f32 rotation[4]) void PhysicsWorld::WatchBodyContacts(BodyId id) { if (!impl_ || id == 0) return; JPH::BodyID body(static_cast(id - 1)); - std::lock_guard guard(impl_->contacts.mutex); + base::LockGuard guard(impl_->contacts.mutex); if (impl_->contacts.Find(body)) return; Impl::WatchedContacts entry; entry.body = body; - impl_->contacts.watched.push_back(std::move(entry)); + impl_->contacts.watched.push_back(base::move(entry)); impl_->contacts.watched_count.store(static_cast(impl_->contacts.watched.size()), - std::memory_order_release); + base::memory_order_release); } void PhysicsWorld::UnwatchBodyContacts(BodyId id) { if (!impl_ || id == 0) return; JPH::BodyID body(static_cast(id - 1)); - std::lock_guard guard(impl_->contacts.mutex); + base::LockGuard guard(impl_->contacts.mutex); base::Vector& watched = impl_->contacts.watched; for (size_t i = 0; i < watched.size(); ++i) { if (watched[i].body == body) { - watched[i] = std::move(watched.back()); + watched[i] = base::move(watched.back()); watched.pop_back(); break; } } impl_->contacts.watched_count.store(static_cast(watched.size()), - std::memory_order_release); + base::memory_order_release); } u32 PhysicsWorld::GetBodyContacts(BodyId id, BodyContact* out, u32 max_contacts) const { if (!impl_ || id == 0 || out == nullptr) return 0; JPH::BodyID body(static_cast(id - 1)); - std::lock_guard guard(impl_->contacts.mutex); + base::LockGuard guard(impl_->contacts.mutex); Impl::WatchedContacts* entry = impl_->contacts.Find(body); if (!entry) return 0; - const u32 count = std::min(max_contacts, static_cast(entry->contacts.size())); + const u32 count = rx::Min(max_contacts, static_cast(entry->contacts.size())); for (u32 i = 0; i < count; ++i) out[i] = entry->contacts[i]; return count; } diff --git a/engine/physics/physics_world.h b/engine/physics/physics_world.h index 9bba0838..ddbabbf7 100644 --- a/engine/physics/physics_world.h +++ b/engine/physics/physics_world.h @@ -1,10 +1,11 @@ #ifndef RX_PHYSICS_PHYSICS_WORLD_H_ #define RX_PHYSICS_PHYSICS_WORLD_H_ -#include -#include #include "asset/mesh.h" +#include "base/functional/function.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" #include "core/export.h" #include "core/math.h" #include "core/types.h" @@ -52,7 +53,7 @@ class RX_PHYSICS_EXPORT PhysicsWorld { public: // Returns true with the surface height and flow velocity when `position` // is over water. Flow drags floating bodies (rivers carry them). - using WaterHeightFn = std::function; + using WaterHeightFn = base::Function; PhysicsWorld(); ~PhysicsWorld(); @@ -69,7 +70,7 @@ class RX_PHYSICS_EXPORT PhysicsWorld { // tire-friction path samples each wheel's water into a per-vehicle cache on // the game thread and the in-step friction callback reads that cache, so a // callback touching thread-affine terrain data or physics queries is safe. - void set_water_height(WaterHeightFn fn) { water_height_ = std::move(fn); } + void set_water_height(WaterHeightFn fn) { water_height_ = base::move(fn); } // Global uniform wind velocity (m/s, world space) the force-based aero // simulators sample as their ambient airmass. Default zero (still air). Cars @@ -752,7 +753,7 @@ class RX_PHYSICS_EXPORT PhysicsWorld { f32 TractionControlThrottle(u32 vehicle_index, f32 forward); struct Impl; - std::unique_ptr impl_; + base::UniquePointer impl_; WaterHeightFn water_height_; Vec3 wind_{}; // global uniform wind velocity, m/s, world space f32 surface_wetness_ = 0; // global rain wetness, 0..1 diff --git a/engine/physics/shape_desc.h b/engine/physics/shape_desc.h index af2f9151..823fb210 100644 --- a/engine/physics/shape_desc.h +++ b/engine/physics/shape_desc.h @@ -7,8 +7,8 @@ // collision/ragdolls) and, eventually, NIF bhk blocks; nothing in here is // format-specific. -#include +#include "base/containers/vector.h" #include "core/math.h" #include "core/types.h" @@ -20,8 +20,8 @@ struct ShapeDesc { f32 radius = 0; // sphere; capsule Vec3 a{}, b{}; // capsule segment ends (local space) Vec3 half_extents{}; // box - std::vector vertices; // convex hull - std::vector children; // compound members / the placed child + base::Vector vertices; // convex hull + base::Vector children; // compound members / the placed child // kPlaced child placement: four float4 COLUMNS (basis c0, c1, c2, origin). f32 transform[16] = {}; }; diff --git a/engine/physics/water_waves.h b/engine/physics/water_waves.h index 7fcbe7d4..4eff5878 100644 --- a/engine/physics/water_waves.h +++ b/engine/physics/water_waves.h @@ -1,7 +1,7 @@ #ifndef RX_PHYSICS_WATER_WAVES_H_ #define RX_PHYSICS_WATER_WAVES_H_ -#include +#include #include "core/math.h" #include "core/types.h" @@ -53,10 +53,10 @@ inline f32 GerstnerWaveHeight(f32 x, f32 z, f32 t, Vec3* flow = nullptr, f32 flow_x = 0.0f, flow_z = 0.0f, vy = 0.0f; for (const GerstnerWaveParam& g : kGerstnerWaves) { const f32 k = kWaterTau / g.wavelength; - const f32 w = std::sqrt(kGravity * k); // deep-water dispersion + const f32 w = ::sqrt(kGravity * k); // deep-water dispersion const f32 phase = k * (g.dir_x * x + g.dir_z * z) + w * t; - const f32 s = std::sin(phase); - const f32 c = std::cos(phase); + const f32 s = ::sin(phase); + const f32 c = ::cos(phase); height += g.amplitude * s; // Horizontal displacement amplitude q*amp = chop / (4k); its time // derivative (orbital velocity) is -dir * q*amp * w * sin(phase). diff --git a/engine/placement/density_program.cc b/engine/placement/density_program.cc index 6a2934be..49826ad7 100644 --- a/engine/placement/density_program.cc +++ b/engine/placement/density_program.cc @@ -1,14 +1,15 @@ #include "placement/density_program.h" -#include -#include +#include +#include "base/containers/span.h" +#include "core/scalar.h" #include "placement/placement_math.h" #include "placement/world_data.h" namespace rx::placement { -f32 EvalDensityProgram(std::span ops, const WorldData& world, f32 world_x, +f32 EvalDensityProgram(base::Span ops, const WorldData& world, f32 world_x, f32 world_z) { f32 stack[kDensityStackDepth] = {}; u32 top = 0; @@ -46,39 +47,39 @@ f32 EvalDensityProgram(std::span ops, const WorldData& world, f } case DensityOpCode::kMin: { f32 y = pop(); - push(std::min(pop(), y)); + push(rx::Min(pop(), y)); break; } case DensityOpCode::kMax: { f32 y = pop(); - push(std::max(pop(), y)); + push(rx::Max(pop(), y)); break; } case DensityOpCode::kOneMinus: push(1.0f - pop()); break; case DensityOpCode::kClamp01: - push(std::clamp(pop(), 0.0f, 1.0f)); + push(rx::Clamp(pop(), 0.0f, 1.0f)); break; case DensityOpCode::kSmoothstep: { f32 x = pop(); f32 span = op.b - op.a; - f32 t = std::clamp(span != 0.0f ? (x - op.a) / span : 0.0f, 0.0f, 1.0f); + f32 t = rx::Clamp(span != 0.0f ? (x - op.a) / span : 0.0f, 0.0f, 1.0f); push(t * t * (3.0f - 2.0f * t)); break; } case DensityOpCode::kRange: { f32 x = pop(); f32 span = op.b - op.a; - push(std::clamp(span != 0.0f ? (x - op.a) / span : 0.0f, 0.0f, 1.0f)); + push(rx::Clamp(span != 0.0f ? (x - op.a) / span : 0.0f, 0.0f, 1.0f)); break; } case DensityOpCode::kPow: - push(std::pow(std::max(pop(), 0.0f), op.a)); + push(::pow(rx::Max(pop(), 0.0f), op.a)); break; } } - return std::clamp(top > 0 ? stack[top - 1] : 0.0f, 0.0f, 1.0f); + return rx::Clamp(top > 0 ? stack[top - 1] : 0.0f, 0.0f, 1.0f); } } // namespace rx::placement diff --git a/engine/placement/density_program.h b/engine/placement/density_program.h index 3ae101c6..20df2ca7 100644 --- a/engine/placement/density_program.h +++ b/engine/placement/density_program.h @@ -1,10 +1,10 @@ #ifndef RX_PLACEMENT_DENSITY_PROGRAM_H_ #define RX_PLACEMENT_DENSITY_PROGRAM_H_ -#include #include +#include "base/containers/span.h" #include "core/export.h" #include "core/types.h" @@ -69,7 +69,7 @@ class RX_PLACEMENT_EXPORT DensityProgram { } DensityProgram& Pow(f32 exponent) { return Push({DensityOpCode::kPow, exponent}); } - std::span ops() const { return {ops_.data(), ops_.size()}; } + base::Span ops() const { return base::Span(ops_.data(), ops_.size()); } bool empty() const { return ops_.empty(); } private: @@ -83,7 +83,7 @@ class RX_PLACEMENT_EXPORT DensityProgram { // CPU reference evaluator; mirrors placement_density.cs.hlsl op for op. The // result is already saturated (density is a probability). -RX_PLACEMENT_EXPORT f32 EvalDensityProgram(std::span ops, +RX_PLACEMENT_EXPORT f32 EvalDensityProgram(base::Span ops, const WorldData& world, f32 world_x, f32 world_z); diff --git a/engine/placement/ecotope.h b/engine/placement/ecotope.h index 311f33a1..0dc169cc 100644 --- a/engine/placement/ecotope.h +++ b/engine/placement/ecotope.h @@ -1,10 +1,10 @@ #ifndef RX_PLACEMENT_ECOTOPE_H_ #define RX_PLACEMENT_ECOTOPE_H_ -#include #include +#include "base/strings/xstring.h" #include "core/types.h" #include "placement/density_program.h" @@ -15,7 +15,7 @@ namespace rx::placement { // effective diameter the object occupies in the placement pattern - a 6 m // tree and a 1 m fern run through the same algorithm at different scales. struct PlacementLayer { - std::string name; + base::String name; u64 mesh = 0; // renderer mesh id instances are drawn with f32 footprint = 1.0f; @@ -35,7 +35,7 @@ struct PlacementLayer { // footprint are stacked into one dither interval, which is what keeps a // pine and a fir from ever claiming the same sample point. struct Ecotope { - std::string name; + base::String name; base::Vector layers; }; diff --git a/engine/placement/gpu_placement.cc b/engine/placement/gpu_placement.cc index 08dc8077..488f6dcf 100644 --- a/engine/placement/gpu_placement.cc +++ b/engine/placement/gpu_placement.cc @@ -1,9 +1,11 @@ #include "placement/gpu_placement.h" -#include -#include +#include +#include "base/containers/span.h" +#include "base/memory/mem_ops.h" #include "core/log.h" +#include "core/scalar.h" #include "placement/placement_pattern.h" #include "render/rhi/command_list.h" #include "shaders/placement_density_cs_hlsl.h" @@ -149,22 +151,22 @@ bool GpuPlacement::Initialize(render::Device& device, const PlacementSystem& sys for (const DensityOp& op : layer.density.ops()) { u32 raw[4]; raw[0] = static_cast(op.op); - std::memcpy(&raw[1], &op.a, sizeof(f32)); - std::memcpy(&raw[2], &op.b, sizeof(f32)); - std::memcpy(&raw[3], &op.c, sizeof(f32)); + base::MemCopy(&raw[1], &op.a, sizeof(f32)); + base::MemCopy(&raw[2], &op.b, sizeof(f32)); + base::MemCopy(&raw[3], &op.c, sizeof(f32)); for (u32 v : raw) ops.push_back(v); } } if (ops.empty()) ops.resize(4, 0u); // dummy so the buffer exists ops_ = device.CreateBufferWithData( - {reinterpret_cast(ops.data()), ops.size() * sizeof(u32)}, + ByteSpan(reinterpret_cast(ops.data()), ops.size() * sizeof(u32)), render::kBufferUsageStorage); layers_ = device.CreateBufferWithData( - {reinterpret_cast(layers.data()), layers.size() * sizeof(LayerGpu)}, + ByteSpan(reinterpret_cast(layers.data()), layers.size() * sizeof(LayerGpu)), render::kBufferUsageStorage); pattern_ = device.CreateBufferWithData( - {reinterpret_cast(kPatternXY), sizeof(kPatternXY)}, + ByteSpan(reinterpret_cast(kPatternXY), sizeof(kPatternXY)), render::kBufferUsageStorage); world_sampler_ = device.GetSampler({ @@ -228,10 +230,10 @@ void GpuPlacement::SyncWorldData(render::Device& device, const WorldData& world) for (u32 map = 0; map < map_count; ++map) { if (synced_revisions_[map] == world.revision(map)) continue; - std::span texels = world.texels(map); + base::Span texels = world.texels(map); render::GpuBuffer staging = device.CreateBuffer( texels.size() * sizeof(f32), render::kBufferUsageTransferSrc, true); - std::memcpy(staging.mapped, texels.data(), texels.size() * sizeof(f32)); + base::MemCopy(staging.mapped, texels.data(), texels.size() * sizeof(f32)); const bool first_upload = synced_revisions_[map] == 0; device.ImmediateSubmit([&](render::CommandList& cmd) { cmd.Barrier(render::Transition(world_maps_, @@ -240,7 +242,7 @@ void GpuPlacement::SyncWorldData(render::Device& device, const WorldData& world) ResourceState::kCopyDst)); render::BufferTextureCopy copy; copy.array_layer = map; - cmd.CopyBufferToTexture(staging, world_maps_, {©, 1}); + cmd.CopyBufferToTexture(staging, world_maps_, base::Span(©, 1)); cmd.Barrier(render::Transition(world_maps_, ResourceState::kCopyDst, ResourceState::kShaderReadCompute)); }); @@ -250,7 +252,7 @@ void GpuPlacement::SyncWorldData(render::Device& device, const WorldData& world) } void GpuPlacement::RecordBatch(render::CommandList& cmd, const PlacementSystem& system, - std::span tiles, BufferSet& set) { + base::Span tiles, BufferSet& set) { const WorldData& world = system.world(); const f32 map_inv_extent = 1.0f / world.extent(); @@ -328,19 +330,19 @@ void GpuPlacement::RecordBatch(render::CommandList& cmd, const PlacementSystem& void GpuPlacement::RecordJobs(render::CommandList& cmd, PlacementSystem& system, u32 slot) { BufferSet& set = sets_[slot]; set.jobs.clear(); - std::span pending = system.pending(); - const u32 count = std::min(static_cast(pending.size()), max_jobs_); + base::Span pending = system.pending(); + const u32 count = rx::Min(static_cast(pending.size()), max_jobs_); if (count == 0) return; for (u32 i = 0; i < count; ++i) { set.jobs.push_back(pending[i]); system.MarkInFlight(pending[i]); } - RecordBatch(cmd, system, {set.jobs.data(), set.jobs.size()}, set); + RecordBatch(cmd, system, base::Span(set.jobs.data(), set.jobs.size()), set); } void GpuPlacement::ReadResults(const BufferSet& set, base::Vector& out) const { u32 count = *reinterpret_cast(set.counts.mapped); - count = std::min(count, point_capacity_); + count = rx::Min(count, point_capacity_); const auto* records = reinterpret_cast(set.instances.mapped); for (u32 i = 0; i < count; ++i) { PlacedInstance instance; @@ -367,12 +369,12 @@ void GpuPlacement::Consume(u32 slot, PlacementSystem& system, } void GpuPlacement::GenerateImmediate(render::Device& device, PlacementSystem& system, - std::span tiles, + base::Span tiles, base::Vector& out) { BufferSet& set = sets_[kBufferSets - 1]; for (u32 offset = 0; offset < tiles.size(); offset += max_jobs_) { - const u32 count = std::min(static_cast(tiles.size()) - offset, max_jobs_); - std::span batch = tiles.subspan(offset, count); + const u32 count = rx::Min(static_cast(tiles.size()) - offset, max_jobs_); + base::Span batch = tiles.subspan(offset, count); device.ImmediateSubmit( [&](render::CommandList& cmd) { RecordBatch(cmd, system, batch, set); }); ReadResults(set, out); diff --git a/engine/placement/gpu_placement.h b/engine/placement/gpu_placement.h index 6516816e..446f451f 100644 --- a/engine/placement/gpu_placement.h +++ b/engine/placement/gpu_placement.h @@ -3,6 +3,7 @@ #include +#include "base/containers/span.h" #include "core/export.h" #include "core/types.h" #include "placement/placement.h" @@ -41,7 +42,7 @@ class RX_PLACEMENT_GPU_EXPORT GpuPlacement { // the batch through ImmediateSubmit and reads the results back before // returning. Uses a reserved buffer set, safe while frames are idle. void GenerateImmediate(render::Device& device, PlacementSystem& system, - std::span tiles, base::Vector& out); + base::Span tiles, base::Vector& out); bool initialized() const { return initialized_; } @@ -58,7 +59,7 @@ class RX_PLACEMENT_GPU_EXPORT GpuPlacement { }; void RecordBatch(render::CommandList& cmd, const PlacementSystem& system, - std::span tiles, BufferSet& set); + base::Span tiles, BufferSet& set); void ReadResults(const BufferSet& set, base::Vector& out) const; bool initialized_ = false; diff --git a/engine/placement/pattern_gen.cc b/engine/placement/pattern_gen.cc index 8652dfc8..44f04abe 100644 --- a/engine/placement/pattern_gen.cc +++ b/engine/placement/pattern_gen.cc @@ -12,12 +12,12 @@ // Build: g++ -std=c++20 -O2 pattern_gen.cc -o pattern_gen // Usage: ./pattern_gen [output_header_path] (default: placement_pattern.h) -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include "base/containers/vector.h" +#include "base/strings/xstring.h" namespace { @@ -63,7 +63,7 @@ inline float torusDist2(float ax, float ay, float bx, float by) { } // namespace int main(int argc, char** argv) { - const std::string outPath = (argc > 1) ? argv[1] : "placement_pattern.h"; + const base::String outPath = (argc > 1) ? argv[1] : "placement_pattern.h"; constexpr int N = 256; // Candidate pool: stratified jitter, one point per cell of a G x G grid. @@ -79,7 +79,7 @@ int main(int argc, char** argv) { float mind2; // min toroidal dist^2 to the current selected set bool selected; }; - std::vector pool(kPool); + base::Vector pool(kPool); const float inv = 1.0f / static_cast(G); int idx = 0; @@ -92,11 +92,11 @@ int main(int argc, char** argv) { // guard against exactly 1.0 from float rounding if (x >= 1.0f) x = 0.9999990f; if (y >= 1.0f) y = 0.9999990f; - pool[idx++] = Cand{x, y, std::numeric_limits::infinity(), false}; + pool[idx++] = Cand{x, y, INFINITY, false}; } } - std::vector outX(N), outY(N); + base::Vector outX(N), outY(N); // First point: arbitrary, from the PRNG. int firstIdx = static_cast(rng.next() % static_cast(kPool)); @@ -137,14 +137,14 @@ int main(int argc, char** argv) { } // Write the generated header - FILE* f = std::fopen(outPath.c_str(), "wb"); + FILE* f = ::fopen(outPath.c_str(), "wb"); if (!f) { - std::fprintf(stderr, "pattern_gen: cannot open %s for writing\n", + ::fprintf(stderr, "pattern_gen: cannot open %s for writing\n", outPath.c_str()); return 1; } - std::fprintf(f, + ::fprintf(f, "// Generated by pattern_gen.cc - do not edit by hand.\n" "#pragma once\n" "#include \n" @@ -156,14 +156,14 @@ int main(int argc, char** argv) { N); for (int i = 0; i < N; ++i) { - std::fprintf(f, " %.6gf, %.6gf,\n", outX[i], outY[i]); + ::fprintf(f, " %.6gf, %.6gf,\n", outX[i], outY[i]); } - std::fprintf(f, + ::fprintf(f, "};\n" "} // namespace rx::placement\n"); - std::fclose(f); - std::fprintf(stderr, "pattern_gen: wrote %d points to %s\n", N, outPath.c_str()); + ::fclose(f); + ::fprintf(stderr, "pattern_gen: wrote %d points to %s\n", N, outPath.c_str()); return 0; } diff --git a/engine/placement/placement.cc b/engine/placement/placement.cc index eb482b0c..4c16e8ff 100644 --- a/engine/placement/placement.cc +++ b/engine/placement/placement.cc @@ -1,8 +1,9 @@ #include "placement/placement.h" -#include -#include +#include +#include "base/memory/move.h" +#include "core/scalar.h" #include "placement/density_program.h" #include "placement/placement_math.h" #include "placement/placement_pattern.h" @@ -24,7 +25,7 @@ void PlacementSystem::Compile() { sorted.reserve(layers_.size()); base::Vector taken; taken.resize(layers_.size(), false); - for (std::size_t i = 0; i < layers_.size(); ++i) { + for (size_t i = 0; i < layers_.size(); ++i) { if (taken[i]) continue; PlacementStack stack; stack.footprint = layers_[i].footprint; @@ -32,7 +33,7 @@ void PlacementSystem::Compile() { stack.stream_radius = stack.tile_size * (static_cast(config_.radius_tiles) + 0.5f); stack.first_layer = static_cast(sorted.size()); - for (std::size_t j = i; j < layers_.size(); ++j) { + for (size_t j = i; j < layers_.size(); ++j) { if (taken[j] || layers_[j].footprint != stack.footprint) continue; if (stack.layer_count == kMaxStackLayers) break; sorted.push_back(layers_[j]); @@ -41,7 +42,7 @@ void PlacementSystem::Compile() { } stacks_.push_back(stack); } - layers_ = std::move(sorted); + layers_ = base::move(sorted); } Vec3 PlacementSystem::TileOrigin(const TileKey& key) const { @@ -74,7 +75,7 @@ void PlacementSystem::Update(const Vec3& viewer) { f32 cz = (static_cast(tile.key.z) + 0.5f) * stack.tile_size; f32 dx = viewer.x - cx; f32 dz = viewer.z - cz; - bool outside = std::sqrt(dx * dx + dz * dz) > stack.stream_radius; + bool outside = ::sqrt(dx * dx + dz * dz) > stack.stream_radius; if (tile.state == 2 && (outside || tile.stale)) evicted_.push_back(tile.key); } @@ -83,8 +84,8 @@ void PlacementSystem::Update(const Vec3& viewer) { for (u32 s = 0; s < stacks_.size() && budget > 0; ++s) { const PlacementStack& stack = stacks_[s]; i32 r = static_cast(config_.radius_tiles); - i32 center_x = static_cast(std::floor(viewer.x / stack.tile_size)); - i32 center_z = static_cast(std::floor(viewer.z / stack.tile_size)); + i32 center_x = static_cast(::floor(viewer.x / stack.tile_size)); + i32 center_z = static_cast(::floor(viewer.z / stack.tile_size)); for (i32 dz = -r; dz <= r && budget > 0; ++dz) { for (i32 dx = -r; dx <= r && budget > 0; ++dx) { TileKey key{s, center_x + dx, center_z + dz}; @@ -92,7 +93,7 @@ void PlacementSystem::Update(const Vec3& viewer) { f32 cz = (static_cast(key.z) + 0.5f) * stack.tile_size; f32 ddx = viewer.x - cx; f32 ddz = viewer.z - cz; - if (std::sqrt(ddx * ddx + ddz * ddz) > stack.stream_radius) continue; + if (::sqrt(ddx * ddx + ddz * ddz) > stack.stream_radius) continue; if (Find(key)) continue; pending_.push_back(key); --budget; @@ -119,7 +120,7 @@ void PlacementSystem::MarkLive(const TileKey& key) { } void PlacementSystem::Release(const TileKey& key) { - for (std::size_t i = 0; i < tiles_.size(); ++i) { + for (size_t i = 0; i < tiles_.size(); ++i) { if (tiles_[i].key != key) continue; tiles_[i] = tiles_[tiles_.size() - 1]; tiles_.pop_back(); @@ -155,7 +156,7 @@ void PlacementSystem::EmitTileCpu(const TileKey& key, base::Vector density; - density.resize(static_cast(kDensityResolution) * kDensityResolution * + density.resize(static_cast(kDensityResolution) * kDensityResolution * stack.layer_count, 0.0f); for (u32 tz = 0; tz < kDensityResolution; ++tz) { @@ -166,8 +167,8 @@ void PlacementSystem::EmitTileCpu(const TileKey& key, base::Vector(layer) * kDensityResolution + tz) * + cumulative = rx::Min(cumulative, 1.0f); + density[(static_cast(layer) * kDensityResolution + tz) * kDensityResolution + tx] = cumulative; } @@ -190,13 +191,13 @@ void PlacementSystem::EmitTileCpu(const TileKey& key, base::Vector(i) + 0.5f) / static_cast(kPatternPointCount); - u32 texel_x = std::min(static_cast(local_x / texel_size), kDensityResolution - 1); - u32 texel_z = std::min(static_cast(local_z / texel_size), kDensityResolution - 1); + u32 texel_x = rx::Min(static_cast(local_x / texel_size), kDensityResolution - 1); + u32 texel_z = rx::Min(static_cast(local_z / texel_size), kDensityResolution - 1); u32 selected = kMaxStackLayers; f32 below = 0.0f; for (u32 layer = 0; layer < stack.layer_count; ++layer) { - f32 cumulative = density[(static_cast(layer) * kDensityResolution + + f32 cumulative = density[(static_cast(layer) * kDensityResolution + texel_z) * kDensityResolution + texel_x]; @@ -223,7 +224,7 @@ void PlacementSystem::EmitTileCpu(const TileKey& key, base::VectorSample(height_map_, world_x, world_z + h) - world_->Sample(height_map_, world_x, world_z - h); Vec3 normal{-dhx, 2.0f * h, -dhz}; - f32 nlen = std::sqrt(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z); + f32 nlen = ::sqrt(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z); point.normal = {normal.x / nlen, normal.y / nlen, normal.z / nlen}; u32 seed = InstanceSeed(config_.seed, key.x, key.z, i, layer_index); diff --git a/engine/placement/placement.h b/engine/placement/placement.h index 5a746f3e..e1c5315e 100644 --- a/engine/placement/placement.h +++ b/engine/placement/placement.h @@ -1,11 +1,9 @@ #ifndef RX_PLACEMENT_PLACEMENT_H_ #define RX_PLACEMENT_PLACEMENT_H_ -#include -#include - #include +#include "base/containers/span.h" #include "core/export.h" #include "core/math.h" #include "core/types.h" @@ -37,7 +35,6 @@ struct TileKey { i32 z = 0; bool operator==(const TileKey&) const = default; - auto operator<=>(const TileKey&) const = default; }; // One placed object as produced by the PLACEMENT stage (and the CPU @@ -89,14 +86,14 @@ class RX_PLACEMENT_EXPORT PlacementSystem { const WorldData& world() const { return *world_; } u32 height_map() const { return height_map_; } const PlacementConfig& config() const { return config_; } - std::span layers() const { return {layers_.data(), layers_.size()}; } - std::span stacks() const { return {stacks_.data(), stacks_.size()}; } + base::Span layers() const { return base::Span(layers_.data(), layers_.size()); } + base::Span stacks() const { return base::Span(stacks_.data(), stacks_.size()); } // Streaming bookkeeping. Update() diffs the wanted set around the viewer // against live/in-flight tiles and refreshes pending()/evicted(). void Update(const Vec3& viewer); - std::span pending() const { return {pending_.data(), pending_.size()}; } - std::span evicted() const { return {evicted_.data(), evicted_.size()}; } + base::Span pending() const { return base::Span(pending_.data(), pending_.size()); } + base::Span evicted() const { return base::Span(evicted_.data(), evicted_.size()); } void MarkInFlight(const TileKey& key); void MarkLive(const TileKey& key); // Forgets a tile entirely (after its instances are destroyed). diff --git a/engine/placement/placement_math.h b/engine/placement/placement_math.h index 9ad84611..832ede8e 100644 --- a/engine/placement/placement_math.h +++ b/engine/placement/placement_math.h @@ -1,7 +1,7 @@ #ifndef RX_PLACEMENT_PLACEMENT_MATH_H_ #define RX_PLACEMENT_PLACEMENT_MATH_H_ -#include +#include #include "core/math.h" #include "core/types.h" @@ -46,8 +46,8 @@ inline f32 ValueNoise(f32 x, f32 z, f32 feature_size, u32 seed) { if (feature_size <= 0.0f) return 0.5f; f32 fx = x / feature_size; f32 fz = z / feature_size; - f32 ix = std::floor(fx); - f32 iz = std::floor(fz); + f32 ix = ::floor(fx); + f32 iz = ::floor(fz); f32 tx = Smoothstep01(fx - ix); f32 tz = Smoothstep01(fz - iz); i32 cx = static_cast(ix); @@ -78,19 +78,19 @@ inline Mat4 BuildPlacementTransform(const OrientedPoint& point, f32 yaw, f32 sca Vec3 up{0.0f, 1.0f, 0.0f}; Vec3 axis{up.x + (point.normal.x - up.x) * tilt, up.y + (point.normal.y - up.y) * tilt, up.z + (point.normal.z - up.z) * tilt}; - f32 len = std::sqrt(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z); + f32 len = ::sqrt(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z); if (len < 1e-5f) { axis = up; } else { axis = {axis.x / len, axis.y / len, axis.z / len}; } // Orthonormal basis around `axis`, yawed by `yaw`. - f32 c = std::cos(yaw); - f32 s = std::sin(yaw); - Vec3 ref = std::fabs(axis.y) < 0.99f ? Vec3{0.0f, 1.0f, 0.0f} : Vec3{1.0f, 0.0f, 0.0f}; + f32 c = ::cos(yaw); + f32 s = ::sin(yaw); + Vec3 ref = ::fabs(axis.y) < 0.99f ? Vec3{0.0f, 1.0f, 0.0f} : Vec3{1.0f, 0.0f, 0.0f}; Vec3 tangent{ref.y * axis.z - ref.z * axis.y, ref.z * axis.x - ref.x * axis.z, ref.x * axis.y - ref.y * axis.x}; - f32 tlen = std::sqrt(tangent.x * tangent.x + tangent.y * tangent.y + tangent.z * tangent.z); + f32 tlen = ::sqrt(tangent.x * tangent.x + tangent.y * tangent.y + tangent.z * tangent.z); tangent = {tangent.x / tlen, tangent.y / tlen, tangent.z / tlen}; // tangent x axis, so [x=tangent, y=axis, z=bitangent] is right-handed // (instance groups reject mirrored transforms). diff --git a/engine/placement/placement_pattern.h b/engine/placement/placement_pattern.h index aa5bba07..7ed1af6a 100644 --- a/engine/placement/placement_pattern.h +++ b/engine/placement/placement_pattern.h @@ -1,6 +1,6 @@ // Generated by pattern_gen.cc - do not edit by hand. #pragma once -#include +#include namespace rx::placement { // Ordered-dither sample pattern: unit-tile positions ordered so that any // prefix is an evenly spaced point set. threshold(i) = (i + 0.5f) / kPatternPoints. diff --git a/engine/placement/world_data.cc b/engine/placement/world_data.cc index 21e83ba2..f1476834 100644 --- a/engine/placement/world_data.cc +++ b/engine/placement/world_data.cc @@ -1,32 +1,35 @@ +#include "base/containers/span.h" +#include "base/memory/move.h" +#include "base/strings/xstring.h" +#include "core/scalar.h" #include "placement/world_data.h" -#include -#include +#include namespace rx::placement { WorldData::WorldData(f32 origin_x, f32 origin_z, f32 extent_m, u32 resolution) : origin_x_(origin_x), origin_z_(origin_z), extent_(extent_m), resolution_(resolution) {} -u32 WorldData::AddMap(std::string name, f32 fill) { +u32 WorldData::AddMap(base::String name, f32 fill) { Map map; - map.name = std::move(name); - map.texels.resize(static_cast(resolution_) * resolution_, fill); - maps_.push_back(std::move(map)); + map.name = base::move(name); + map.texels.resize(static_cast(resolution_) * resolution_, fill); + maps_.push_back(base::move(map)); revisions_.push_back(1); return static_cast(maps_.size() - 1); } -std::span WorldData::texels(u32 map) { - return {maps_[map].texels.data(), maps_[map].texels.size()}; +base::Span WorldData::texels(u32 map) { + return base::Span(maps_[map].texels.data(), maps_[map].texels.size()); } -std::span WorldData::texels(u32 map) const { - return {maps_[map].texels.data(), maps_[map].texels.size()}; +base::Span WorldData::texels(u32 map) const { + return base::Span(maps_[map].texels.data(), maps_[map].texels.size()); } f32& WorldData::At(u32 map, u32 x, u32 z) { - return maps_[map].texels[static_cast(z) * resolution_ + x]; + return maps_[map].texels[static_cast(z) * resolution_ + x]; } f32 WorldData::Sample(u32 map, f32 world_x, f32 world_z) const { @@ -35,15 +38,15 @@ f32 WorldData::Sample(u32 map, f32 world_x, f32 world_z) const { f32 step = meters_per_texel(); f32 u = (world_x - origin_x_) / step - 0.5f; f32 v = (world_z - origin_z_) / step - 0.5f; - f32 fu = std::floor(u); - f32 fv = std::floor(v); + f32 fu = ::floor(u); + f32 fv = ::floor(v); f32 tu = u - fu; f32 tv = v - fv; i32 max_texel = static_cast(resolution_) - 1; auto texel = [&](i32 x, i32 z) { - x = std::clamp(x, 0, max_texel); - z = std::clamp(z, 0, max_texel); - return maps_[map].texels[static_cast(z) * resolution_ + static_cast(x)]; + x = rx::Clamp(x, 0, max_texel); + z = rx::Clamp(z, 0, max_texel); + return maps_[map].texels[static_cast(z) * resolution_ + static_cast(x)]; }; i32 x0 = static_cast(fu); i32 z0 = static_cast(fv); @@ -56,17 +59,17 @@ void WorldData::PaintDisc(u32 map, f32 center_x, f32 center_z, f32 radius, f32 v if (radius <= 0.0f) return; f32 step = meters_per_texel(); i32 max_texel = static_cast(resolution_) - 1; - i32 x0 = std::clamp(static_cast((center_x - radius - origin_x_) / step), 0, max_texel); - i32 x1 = std::clamp(static_cast((center_x + radius - origin_x_) / step) + 1, 0, max_texel); - i32 z0 = std::clamp(static_cast((center_z - radius - origin_z_) / step), 0, max_texel); - i32 z1 = std::clamp(static_cast((center_z + radius - origin_z_) / step) + 1, 0, max_texel); + i32 x0 = rx::Clamp(static_cast((center_x - radius - origin_x_) / step), 0, max_texel); + i32 x1 = rx::Clamp(static_cast((center_x + radius - origin_x_) / step) + 1, 0, max_texel); + i32 z0 = rx::Clamp(static_cast((center_z - radius - origin_z_) / step), 0, max_texel); + i32 z1 = rx::Clamp(static_cast((center_z + radius - origin_z_) / step) + 1, 0, max_texel); for (i32 z = z0; z <= z1; ++z) { for (i32 x = x0; x <= x1; ++x) { f32 wx = origin_x_ + (static_cast(x) + 0.5f) * step; f32 wz = origin_z_ + (static_cast(z) + 0.5f) * step; - f32 d = std::sqrt((wx - center_x) * (wx - center_x) + (wz - center_z) * (wz - center_z)); + f32 d = ::sqrt((wx - center_x) * (wx - center_x) + (wz - center_z) * (wz - center_z)); if (d >= radius) continue; - f32 strength = std::min(1.0f, (radius - d) / (radius * 0.25f + 1e-5f)); + f32 strength = rx::Min(1.0f, (radius - d) / (radius * 0.25f + 1e-5f)); f32& texel = At(map, static_cast(x), static_cast(z)); texel = texel + (value - texel) * strength; } diff --git a/engine/placement/world_data.h b/engine/placement/world_data.h index 2232126c..5b918690 100644 --- a/engine/placement/world_data.h +++ b/engine/placement/world_data.h @@ -1,11 +1,11 @@ #ifndef RX_PLACEMENT_WORLD_DATA_H_ #define RX_PLACEMENT_WORLD_DATA_H_ -#include -#include #include +#include "base/containers/span.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/types.h" @@ -27,18 +27,18 @@ class RX_PLACEMENT_EXPORT WorldData { // Adds a map filled with `fill` and returns its index (the slot density // programs reference). - u32 AddMap(std::string name, f32 fill = 0.0f); + u32 AddMap(base::String name, f32 fill = 0.0f); u32 map_count() const { return static_cast(maps_.size()); } - const std::string& map_name(u32 map) const { return maps_[map].name; } + const base::String& map_name(u32 map) const { return maps_[map].name; } u32 resolution() const { return resolution_; } f32 origin_x() const { return origin_x_; } f32 origin_z() const { return origin_z_; } f32 extent() const { return extent_; } f32 meters_per_texel() const { return extent_ / static_cast(resolution_); } - std::span texels(u32 map); - std::span texels(u32 map) const; + base::Span texels(u32 map); + base::Span texels(u32 map) const; f32& At(u32 map, u32 x, u32 z); // Bilinear sample at a world position, clamped at the region border. @@ -75,7 +75,7 @@ class RX_PLACEMENT_EXPORT WorldData { u32 resolution_ = 0; struct Map { - std::string name; + base::String name; base::Vector texels; }; base::Vector maps_; diff --git a/engine/render/atmosphere/clouds.cc b/engine/render/atmosphere/clouds.cc index 1366e67c..37421858 100644 --- a/engine/render/atmosphere/clouds.cc +++ b/engine/render/atmosphere/clouds.cc @@ -1,7 +1,8 @@ #include "render/atmosphere/clouds.h" -#include +#include +#include "base/memory/mem_ops.h" #include "core/log.h" #include "render/rhi/device.h" #include "shaders/clouds_cs_hlsl.h" @@ -78,7 +79,7 @@ ResourceHandle Clouds::AddToGraph(RenderGraph& graph, ResourceHandle color, Reso }, [this, color, depth, out, extent, frame, slot](PassContext& ctx) { const CloudCamera camera{frame.inv_view_proj}; - std::memcpy(camera_[slot].mapped, &camera, sizeof(camera)); + base::MemCopy(camera_[slot].mapped, &camera, sizeof(camera)); CloudPush push{}; push.camera_pos[0] = frame.camera_pos.x; diff --git a/engine/render/atmosphere/cloudscape.cc b/engine/render/atmosphere/cloudscape.cc index 6c56dab2..dfb87a46 100644 --- a/engine/render/atmosphere/cloudscape.cc +++ b/engine/render/atmosphere/cloudscape.cc @@ -1,10 +1,12 @@ #include "render/atmosphere/cloudscape.h" -#include -#include -#include +#include +#include +#include +#include "base/memory/mem_ops.h" #include "core/log.h" +#include "core/scalar.h" #include "render/rhi/device.h" #include "shaders/cloudscape_apply_cs_hlsl.h" #include "shaders/cloudscape_funnel_cs_hlsl.h" @@ -90,9 +92,9 @@ bool SameMapState(const CloudscapeMapState &a, const CloudscapeMapState &b) { bool MapStateDiscontinuous(const CloudscapeMapState &a, const CloudscapeMapState &b) { - return a.seed != b.seed || std::abs(a.coverage - b.coverage) > 0.25f || - std::abs(a.cloud_type - b.cloud_type) > 0.25f || - std::abs(a.precipitation - b.precipitation) > 0.25f; + return a.seed != b.seed || ::abs(a.coverage - b.coverage) > 0.25f || + ::abs(a.cloud_type - b.cloud_type) > 0.25f || + ::abs(a.precipitation - b.precipitation) > 0.25f; } bool DensityFieldDiscontinuous(const CloudscapeControls &previous, @@ -116,21 +118,21 @@ bool DensityFieldDiscontinuous(const CloudscapeControls &previous, return true; } if (!endpoint_relabel && - std::abs(current.map_blend - previous.map_blend) > 0.25f) + ::abs(current.map_blend - previous.map_blend) > 0.25f) return true; - const f32 thickness = std::max(previous.top - previous.bottom, 1.0f); + const f32 thickness = rx::Max(previous.top - previous.bottom, 1.0f); const Vec2 map_delta = current.map_offset - previous.map_offset; - const f32 wind_alignment = std::cos(current.wind_yaw - previous.wind_yaw); - return std::abs(current.bottom - previous.bottom) > std::max(250.0f, thickness * 0.15f) || - std::abs(current.top - previous.top) > std::max(500.0f, thickness * 0.15f) || - std::abs(current.density - previous.density) > 0.25f || - std::abs(current.anvil - previous.anvil) > 0.25f || - std::abs(current.darkness - previous.darkness) > 0.25f || + const f32 wind_alignment = ::cos(current.wind_yaw - previous.wind_yaw); + return ::abs(current.bottom - previous.bottom) > rx::Max(250.0f, thickness * 0.15f) || + ::abs(current.top - previous.top) > rx::Max(500.0f, thickness * 0.15f) || + ::abs(current.density - previous.density) > 0.25f || + ::abs(current.anvil - previous.anvil) > 0.25f || + ::abs(current.darkness - previous.darkness) > 0.25f || map_delta.x * map_delta.x + map_delta.y * map_delta.y > 250000.0f || wind_alignment < 0.94f || - std::abs(current.vertical_skew - previous.vertical_skew) > 250.0f || - std::abs(current.turbulence - previous.turbulence) > 0.25f; + ::abs(current.vertical_skew - previous.vertical_skew) > 250.0f || + ::abs(current.turbulence - previous.turbulence) > 0.25f; } } // namespace @@ -259,7 +261,7 @@ ResourceHandle Cloudscape::AddFunnelToGraph(RenderGraph &graph, push.jitter[0] = frame.jitter[0]; push.jitter[1] = frame.jitter[1]; const u32 param_slot = frame.frame_index % kFramesInFlight; - std::memcpy(funnel_params_[param_slot].mapped, &push, sizeof(push)); + base::MemCopy(funnel_params_[param_slot].mapped, &push, sizeof(push)); graph.AddPass( "cloudscape_funnel", [&](RenderGraph::PassBuilder &b) { @@ -306,12 +308,12 @@ ResourceHandle Cloudscape::AddHazeToGraph(RenderGraph &graph, push.sun_color[2] = frame.sun_color.z; push.sun_color[3] = frame.flash; const CloudscapeControls &c = frame.controls; - push.wind[0] = std::cos(c.wind_yaw); - push.wind[1] = std::sin(c.wind_yaw); + push.wind[0] = ::cos(c.wind_yaw); + push.wind[1] = ::sin(c.wind_yaw); push.wind[2] = c.vertical_skew; push.wind[3] = c.darkness; push.fog[0] = c.fog_density; - push.fog[1] = std::max(c.fog_height, 0.1f); + push.fog[1] = rx::Max(c.fog_height, 0.1f); push.fog[2] = c.fog_ground; push.fog[3] = c.anvil; push.map[0] = c.map_offset.x; @@ -323,7 +325,7 @@ ResourceHandle Cloudscape::AddHazeToGraph(RenderGraph &graph, push.shell[0] = c.bottom; push.shell[1] = c.top; const u32 param_slot = frame.frame_index % kFramesInFlight; - std::memcpy(haze_params_[param_slot].mapped, &push, sizeof(push)); + base::MemCopy(haze_params_[param_slot].mapped, &push, sizeof(push)); graph.AddPass( "cloudscape_haze", [&](RenderGraph::PassBuilder &b) { @@ -387,8 +389,8 @@ void Cloudscape::AddShadowToGraph(RenderGraph &graph, ResourceHandle sun_shadow, push.sun_dir[2] = sun.z; push.near_plane = 0.1f; const CloudscapeControls &c = frame.controls; - push.wind[0] = std::cos(c.wind_yaw); - push.wind[1] = std::sin(c.wind_yaw); + push.wind[0] = ::cos(c.wind_yaw); + push.wind[1] = ::sin(c.wind_yaw); push.wind[2] = c.wind_speed; push.wind[3] = c.vertical_skew; push.shape[0] = c.bottom; @@ -403,7 +405,7 @@ void Cloudscape::AddShadowToGraph(RenderGraph &graph, ResourceHandle sun_shadow, push.jitter[1] = frame.jitter[1]; push.strength = strength; const u32 param_slot = frame.frame_index % kFramesInFlight; - std::memcpy(shadow_params_[param_slot].mapped, &push, sizeof(push)); + base::MemCopy(shadow_params_[param_slot].mapped, &push, sizeof(push)); graph.AddPass( "cloudscape_shadow", [&](RenderGraph::PassBuilder &b) { @@ -492,10 +494,10 @@ ResourceHandle Cloudscape::AddToGraph(RenderGraph &graph, ResourceHandle color, Vec3 current_sun = Normalize(frame.sun_direction); bool lighting_cut = has_last_frame_ && (Dot(current_sun, last_sun_direction_) < 0.98f || - std::abs(frame.sun_intensity - last_sun_intensity_) > 0.5f || + ::abs(frame.sun_intensity - last_sun_intensity_) > 0.5f || Dot(frame.sun_color - last_sun_color_, frame.sun_color - last_sun_color_) > 0.04f || - std::abs(frame.ambient - last_ambient_) > 0.1f); + ::abs(frame.ambient - last_ambient_) > 0.1f); bool history = history_valid_ && contiguous && !frame.reset_history && !camera_cut && !density_cut && !lighting_cut; MarchPush march_push{}; @@ -515,8 +517,8 @@ ResourceHandle Cloudscape::AddToGraph(RenderGraph &graph, ResourceHandle color, march_push.sun_color[2] = frame.sun_color.z; march_push.sun_color[3] = frame.ambient; const CloudscapeControls &c = frame.controls; - march_push.wind[0] = std::cos(c.wind_yaw); - march_push.wind[1] = std::sin(c.wind_yaw); + march_push.wind[0] = ::cos(c.wind_yaw); + march_push.wind[1] = ::sin(c.wind_yaw); march_push.wind[2] = c.wind_speed; march_push.wind[3] = c.vertical_skew; march_push.shape[0] = c.bottom; @@ -532,11 +534,11 @@ ResourceHandle Cloudscape::AddToGraph(RenderGraph &graph, ResourceHandle color, march_push.prev_jitter[0] = last_jitter_[0]; march_push.prev_jitter[1] = last_jitter_[1]; march_push.frame_index = frame.frame_index; - march_push.steps = std::clamp(frame.steps, 8u, 128u); + march_push.steps = rx::Clamp(frame.steps, 8u, 128u); march_push.flags = history ? 1u : 0u; march_push.darkness = c.darkness; const u32 param_slot = frame.frame_index % kFramesInFlight; - std::memcpy(march_params_[param_slot].mapped, &march_push, sizeof(march_push)); + base::MemCopy(march_params_[param_slot].mapped, &march_push, sizeof(march_push)); graph.AddPass( "cloudscape_march", [&](RenderGraph::PassBuilder &b) { diff --git a/engine/render/atmosphere/cloudscape_textures.cc b/engine/render/atmosphere/cloudscape_textures.cc index b4213abc..32d09a99 100644 --- a/engine/render/atmosphere/cloudscape_textures.cc +++ b/engine/render/atmosphere/cloudscape_textures.cc @@ -1,9 +1,9 @@ #include "render/atmosphere/cloudscape_textures.h" -#include -#include +#include #include "core/log.h" +#include "core/scalar.h" #include "shaders/cloudscape_base_noise_cs_hlsl.h" #include "shaders/cloudscape_curl_cs_hlsl.h" #include "shaders/cloudscape_detail_noise_cs_hlsl.h" @@ -33,7 +33,7 @@ bool StateEqual(const CloudscapeMapState& a, const CloudscapeMapState& b) { f32 QuantizeBlend(f32 blend) { constexpr f32 kSteps = 64.0f; - return std::round(std::clamp(blend, 0.0f, 1.0f) * kSteps) / kSteps; + return ::round(rx::Clamp(blend, 0.0f, 1.0f) * kSteps) / kSteps; } } // namespace diff --git a/engine/render/atmosphere/environment.cc b/engine/render/atmosphere/environment.cc index adc12dfd..d369d267 100644 --- a/engine/render/atmosphere/environment.cc +++ b/engine/render/atmosphere/environment.cc @@ -1,8 +1,10 @@ #include "render/atmosphere/environment.h" -#include -#include +#include +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" #include "core/log.h" #include "render/atmosphere/ltc_tables.h" #include "render/pipeline/mesh_pipeline.h" @@ -51,8 +53,8 @@ struct LutPush { } // namespace -std::unique_ptr EnvironmentSystem::Create(Device& device) { - auto env = std::unique_ptr(new EnvironmentSystem(device)); +base::UniquePointer EnvironmentSystem::Create(Device& device) { + auto env = base::UniquePointer(new EnvironmentSystem(device)); env->sampler_ = device.GetSampler({.min_filter = Filter::kLinear, .mag_filter = Filter::kLinear, @@ -149,7 +151,7 @@ bool EnvironmentSystem::CreateDummies() { dummy_volume_ = device_.CreateBuffer(512, kBufferUsageUniform, true); dummy_storage_ = device_.CreateBuffer(512, kBufferUsageStorage, true); if (!dummy_volume_.mapped) return false; - std::memset(dummy_volume_.mapped, 0, 512); + base::MemSet(dummy_volume_.mapped, 0, 512); device_.ImmediateSubmit([&](CommandList& cmd) { for (GpuImage* image : {&white_, &black_array_, &flat_normal_, &black_}) { @@ -184,8 +186,8 @@ bool EnvironmentSystem::CreateDummies() { if (!ltc_matrix_ || !ltc_amplitude_) return false; GpuBuffer ltc_staging = device_.CreateBuffer(kLtcBytes * 2, kBufferUsageTransferSrc, true); if (!ltc_staging.mapped) return false; - std::memcpy(ltc_staging.mapped, kLtc1, kLtcBytes); - std::memcpy(static_cast(ltc_staging.mapped) + kLtcBytes, kLtc2, kLtcBytes); + base::MemCopy(ltc_staging.mapped, kLtc1, kLtcBytes); + base::MemCopy(static_cast(ltc_staging.mapped) + kLtcBytes, kLtc2, kLtcBytes); device_.ImmediateSubmit([&](CommandList& cmd) { BufferTextureCopy region1{.buffer_offset = 0, .mip = 0, .extent = {kLtcLutSize, kLtcLutSize}}; @@ -193,8 +195,8 @@ bool EnvironmentSystem::CreateDummies() { .extent = {kLtcLutSize, kLtcLutSize}}; cmd.Barrier(Transition(ltc_matrix_, ResourceState::kUndefined, ResourceState::kCopyDst)); cmd.Barrier(Transition(ltc_amplitude_, ResourceState::kUndefined, ResourceState::kCopyDst)); - cmd.CopyBufferToTexture(ltc_staging, ltc_matrix_, {®ion1, 1}); - cmd.CopyBufferToTexture(ltc_staging, ltc_amplitude_, {®ion2, 1}); + cmd.CopyBufferToTexture(ltc_staging, ltc_matrix_, base::Span(®ion1, 1)); + cmd.CopyBufferToTexture(ltc_staging, ltc_amplitude_, base::Span(®ion2, 1)); cmd.Barrier(Transition(ltc_matrix_, ResourceState::kCopyDst, ResourceState::kShaderReadFragment)); cmd.Barrier(Transition(ltc_amplitude_, ResourceState::kCopyDst, @@ -217,7 +219,7 @@ bool EnvironmentSystem::CreatePipelines() { } ComputePipelineDesc desc; desc.shader = shader; - desc.sets.push_back(std::move(set)); + desc.sets.push_back(base::move(set)); desc.push_constant_size = push_size; desc.debug_name = name; *pipeline = device_.CreateComputePipeline(desc); @@ -494,7 +496,7 @@ bool EnvironmentSystem::SetEnvironmentMap(const f32* rgba, u32 width, u32 height has_envmap_ = false; return false; } - std::memcpy(staging.mapped, rgba, bytes); + base::MemCopy(staging.mapped, rgba, bytes); // Host-visible memory is not guaranteed coherent, so the write has to be // flushed before the copy reads it; the other staging uploads in the engine // (material_system, vk_device) do the same. @@ -502,7 +504,7 @@ bool EnvironmentSystem::SetEnvironmentMap(const f32* rgba, u32 width, u32 height device_.ImmediateSubmit([&](CommandList& cmd) { BufferTextureCopy region{.buffer_offset = 0, .mip = 0, .extent = {width, height}}; cmd.Barrier(Transition(envmap_, ResourceState::kUndefined, ResourceState::kCopyDst)); - cmd.CopyBufferToTexture(staging, envmap_, {®ion, 1}); + cmd.CopyBufferToTexture(staging, envmap_, base::Span(®ion, 1)); cmd.Barrier(Transition(envmap_, ResourceState::kCopyDst, ResourceState::kShaderReadAll)); }); device_.DestroyBuffer(staging); diff --git a/engine/render/atmosphere/environment.h b/engine/render/atmosphere/environment.h index bb2757bb..2d75080c 100644 --- a/engine/render/atmosphere/environment.h +++ b/engine/render/atmosphere/environment.h @@ -1,8 +1,8 @@ #ifndef RX_RENDER_ENVIRONMENT_H_ #define RX_RENDER_ENVIRONMENT_H_ -#include +#include "base/memory/unique_pointer.h" #include "core/math.h" #include "render/rhi/device.h" @@ -28,7 +28,7 @@ class EnvironmentSystem { static constexpr u32 kTransmittanceH = 64; static constexpr u32 kMultiScatterSize = 32; - static std::unique_ptr Create(Device& device); + static base::UniquePointer Create(Device& device); ~EnvironmentSystem(); // Builds the sky background pipeline; needs the mesh pipeline's set 0 diff --git a/engine/render/atmosphere/froxel_fog.cc b/engine/render/atmosphere/froxel_fog.cc index 18c71859..8bb27871 100644 --- a/engine/render/atmosphere/froxel_fog.cc +++ b/engine/render/atmosphere/froxel_fog.cc @@ -1,7 +1,8 @@ #include "render/atmosphere/froxel_fog.h" -#include +#include +#include "base/memory/mem_ops.h" #include "core/log.h" #include "render/gi/raytracing.h" #include "shaders/froxel_apply_cs_hlsl.h" @@ -119,7 +120,7 @@ bool FroxelFog::Initialize(Device& device, bool ray_query) { .address_w = AddressMode::kClampToEdge}); dummy_uniform_ = device.CreateBuffer(512, kBufferUsageUniform, true); if (!dummy_uniform_.mapped) return false; - std::memset(dummy_uniform_.mapped, 0, 512); + base::MemSet(dummy_uniform_.mapped, 0, 512); // One per in-flight frame: the pass rewrites it while the previous frame may // still be reading its own copy. @@ -182,7 +183,7 @@ void FroxelFog::AddToGraph(RenderGraph& graph, ResourceHandle lit, ResourceHandl }, [this, slot, cascade_atlas_handle, raytracing, tlas_slot, rt, frame](PassContext& ctx) { const ScatterCamera camera{frame.inv_view_proj, frame.prev_view_proj}; - std::memcpy(camera_[slot].mapped, &camera, sizeof(camera)); + base::MemCopy(camera_[slot].mapped, &camera, sizeof(camera)); ScatterPush push{}; push.camera_pos[0] = frame.camera_pos.x; @@ -207,7 +208,7 @@ void FroxelFog::AddToGraph(RenderGraph& graph, ResourceHandle lit, ResourceHandl push.volume_params[2] = static_cast(kSizeZ); // 0 none / 1 cascade / 2 ray query, matching the shader's constants. push.volume_params[3] = rt ? 2.0f : (frame.csm_active ? 1.0f : 0.0f); - std::memcpy(push.cluster_params, frame.cluster_params, sizeof(push.cluster_params)); + base::MemCopy(push.cluster_params, frame.cluster_params, sizeof(push.cluster_params)); push.screen_size[0] = frame.screen_size[0]; push.screen_size[1] = frame.screen_size[1]; push.screen_size[3] = frame.start_distance; @@ -229,7 +230,7 @@ void FroxelFog::AddToGraph(RenderGraph& graph, ResourceHandle lit, ResourceHandl Bind::Combined(8, cascade_view, frame.comparison_sampler)}; if (rt) items.push_back(Bind::Accel(9, raytracing->tlas(tlas_slot))); items.push_back(Bind::Uniform(10, camera_[slot], 0, sizeof(ScatterCamera))); - ctx.cmd->BindTransient(0, {items.data(), items.size()}); + ctx.cmd->BindTransient(0, base::Span(items.data(), items.size())); ctx.cmd->Push(push); ctx.cmd->Dispatch((kSizeX + 3) / 4, (kSizeY + 3) / 4, (kSizeZ + 3) / 4); ctx.cmd->MemoryBarrier(BarrierScope::kComputeWrite, BarrierScope::kComputeRead); diff --git a/engine/render/atmosphere/lightning.cc b/engine/render/atmosphere/lightning.cc index 67ccdae4..fc76abfc 100644 --- a/engine/render/atmosphere/lightning.cc +++ b/engine/render/atmosphere/lightning.cc @@ -1,9 +1,9 @@ #include "render/atmosphere/lightning.h" -#include -#include +#include #include "core/log.h" +#include "core/scalar.h" #include "render/core/settings.h" #include "render/pipeline/mesh_pipeline.h" #include "shaders/lightning_bolt_ps_hlsl.h" @@ -81,7 +81,7 @@ u32 LightningSystem::AppendLights(PointLight* dst, u32 remaining_capacity, if (remaining_capacity == 0) return 0; if (weather.strike_age < 0.0f || weather.strike_age >= kStrikeDuration) return 0; f32 env = Envelope(weather.strike_age, weather.strike_seed); - f32 intensity = env * std::clamp(weather.strike_energy, 0.0f, 1.0f) * kFlashIntensity; + f32 intensity = env * rx::Clamp(weather.strike_energy, 0.0f, 1.0f) * kFlashIntensity; if (intensity <= 1.0f) return 0; // One cool blue-white omni at the channel's mid height. Because it rides // the normal frame-light path it clusters, claims local-shadow faces, fills @@ -126,7 +126,7 @@ void LightningSystem::AddToGraph(RenderGraph& graph, ResourceHandle color, Resou push.strike_pos[2] = frame.strike_pos.z; push.age = frame.strike_age; push.seed = frame.strike_seed; - push.energy = std::clamp(frame.strike_energy, 0.0f, 1.0f); + push.energy = rx::Clamp(frame.strike_energy, 0.0f, 1.0f); push.jitter[0] = frame.jitter[0]; push.jitter[1] = frame.jitter[1]; diff --git a/engine/render/atmosphere/lightning_envelope.h b/engine/render/atmosphere/lightning_envelope.h index 1c6f1532..ce726b54 100644 --- a/engine/render/atmosphere/lightning_envelope.h +++ b/engine/render/atmosphere/lightning_envelope.h @@ -1,9 +1,9 @@ #ifndef RX_RENDER_LIGHTNING_ENVELOPE_H_ #define RX_RENDER_LIGHTNING_ENVELOPE_H_ -#include -#include +#include +#include "core/scalar.h" #include "core/types.h" namespace rx::render { @@ -14,7 +14,7 @@ inline constexpr f32 kLightningStrikeDuration = 0.45f; // Kept header-only so gameplay/weather code can schedule matching flashes // without linking the GPU renderer implementation. inline f32 LightningEnvelope(f32 age, u32 seed) { - if (!std::isfinite(age) || age < 0.0f || age >= kLightningStrikeDuration) + if (!::isfinite(age) || age < 0.0f || age >= kLightningStrikeDuration) return 0.0f; auto pcg_hash = [](u32 v) { v = v * 747796405u + 2891336453u; @@ -31,17 +31,17 @@ inline f32 LightningEnvelope(f32 age, u32 seed) { f32 hz = static_cast(c) * kInv; f32 hw = static_cast(d) * kInv; - f32 envelope = std::exp(-age * 26.0f); + f32 envelope = ::exp(-age * 26.0f); f32 t1 = 0.10f + 0.08f * hx; if (age > t1) - envelope += (0.5f + 0.4f * hy) * std::exp(-(age - t1) * 30.0f); + envelope += (0.5f + 0.4f * hy) * ::exp(-(age - t1) * 30.0f); if (hz > 0.35f) { f32 t2 = 0.22f + 0.10f * hw; if (age > t2) - envelope += (0.35f + 0.30f * hx) * std::exp(-(age - t2) * 30.0f); + envelope += (0.35f + 0.30f * hx) * ::exp(-(age - t2) * 30.0f); } return envelope * - std::clamp((kLightningStrikeDuration - age) * 20.0f, 0.0f, 1.0f); + rx::Clamp((kLightningStrikeDuration - age) * 20.0f, 0.0f, 1.0f); } } // namespace rx::render diff --git a/engine/render/atmosphere/precip_occlusion.cc b/engine/render/atmosphere/precip_occlusion.cc index 06b941e1..1843df80 100644 --- a/engine/render/atmosphere/precip_occlusion.cc +++ b/engine/render/atmosphere/precip_occlusion.cc @@ -1,7 +1,8 @@ #include "render/atmosphere/precip_occlusion.h" -#include +#include +#include "base/functional/function.h" #include "core/log.h" namespace rx::render { @@ -41,9 +42,9 @@ void PrecipOcclusion::BeginFrame(const Vec3& eye, u32 frame_index) { // Quantize the anchor to the coarse cell: the projection window only ever // moves in whole cells (which are whole texels), so map content never // shimmers under camera motion; between cells the map is simply reused. - f32 ax = std::floor(eye.x / kAnchorCell + 0.5f) * kAnchorCell; - f32 ay = std::floor(eye.y / kAnchorCell + 0.5f) * kAnchorCell; - f32 az = std::floor(eye.z / kAnchorCell + 0.5f) * kAnchorCell; + f32 ax = ::floor(eye.x / kAnchorCell + 0.5f) * kAnchorCell; + f32 ay = ::floor(eye.y / kAnchorCell + 0.5f) * kAnchorCell; + f32 az = ::floor(eye.z / kAnchorCell + 0.5f) * kAnchorCell; if (ax != center_[0] || ay != center_[1] || az != center_[2]) dirty_ = true; // Cheap steady-state refresh so doors/moving cover eventually update. if (frame_index % kRefreshFrames == 0) dirty_ = true; @@ -60,7 +61,7 @@ void PrecipOcclusion::Params(f32 out[4]) const { } void PrecipOcclusion::AddToGraph(RenderGraph& graph, - const std::function& draw) { + const base::Function& draw) { if (!available() || !dirty_) return; dirty_ = false; @@ -91,7 +92,7 @@ void PrecipOcclusion::AddToGraph(RenderGraph& graph, map_, rendered_ ? ResourceState::kShaderReadAll : ResourceState::kUndefined, ResourceState::kDepthTarget); rendered_ = true; - ctx.cmd->TextureBarriers({&to_write, 1}); + ctx.cmd->TextureBarriers(base::Span(&to_write, 1)); DepthAttachment depth{ .view = map_.view, .load = LoadOp::kClear, .store = StoreOp::kStore, .clear = 1.0f}; @@ -106,7 +107,7 @@ void PrecipOcclusion::AddToGraph(RenderGraph& graph, // compute; kShaderReadAll covers both. TextureBarrier to_read = Transition(map_, ResourceState::kDepthTarget, ResourceState::kShaderReadAll); - ctx.cmd->TextureBarriers({&to_read, 1}); + ctx.cmd->TextureBarriers(base::Span(&to_read, 1)); ctx.cmd->MemoryBarrier(BarrierScope::kAllCommands, BarrierScope::kComputeRead); }); } diff --git a/engine/render/atmosphere/precip_occlusion.h b/engine/render/atmosphere/precip_occlusion.h index f2732945..7f362376 100644 --- a/engine/render/atmosphere/precip_occlusion.h +++ b/engine/render/atmosphere/precip_occlusion.h @@ -1,8 +1,8 @@ #ifndef RX_RENDER_PRECIP_OCCLUSION_H_ #define RX_RENDER_PRECIP_OCCLUSION_H_ -#include +#include "base/functional/function.h" #include "core/math.h" #include "render/core/render_graph.h" #include "render/rhi/device.h" @@ -55,7 +55,7 @@ class PrecipOcclusion { // view-proj and records the casters, same contract as ShadowPass::Render's // callback (the caller binds the depth-only pipelines and pushes matrices). void AddToGraph(RenderGraph& graph, - const std::function& draw); + const base::Function& draw); private: GpuImage map_; // persistent D16, parked shader-read between renders diff --git a/engine/render/atmosphere/precip_volume.cc b/engine/render/atmosphere/precip_volume.cc index 530924b5..747cb0ef 100644 --- a/engine/render/atmosphere/precip_volume.cc +++ b/engine/render/atmosphere/precip_volume.cc @@ -1,10 +1,11 @@ #include "render/atmosphere/precip_volume.h" -#include -#include -#include +#include +#include +#include "base/memory/mem_ops.h" #include "core/log.h" +#include "core/scalar.h" #include "render/gi/raytracing.h" #include "shaders/precip_splash_ps_hlsl.h" #include "shaders/precip_splash_vs_hlsl.h" @@ -147,7 +148,7 @@ void PrecipVolume::AddToGraph(RenderGraph& graph, ResourceHandle color, Resource ResourceHandle motion, RayTracingContext* raytracing, u32 tlas_slot, const Frame& frame) { if (!available()) return; - f32 intensity = std::clamp(frame.intensity, 0.0f, 1.0f); + f32 intensity = rx::Clamp(frame.intensity, 0.0f, 1.0f); u32 max_drops = frame.snow ? kMaxSnow : kMaxRain; u32 drop_count = static_cast(static_cast(max_drops) * intensity); // Splash cells thin by intensity in the shader; keep the instance count flat @@ -170,7 +171,7 @@ void PrecipVolume::AddToGraph(RenderGraph& graph, ResourceHandle color, Resource [this, color, depth, motion, raytracing, tlas_slot, frame, drop_count, splash_count, rt, slot](PassContext& ctx) { const PrecipCamera camera{frame.view_proj, frame.prev_view_proj}; - std::memcpy(camera_[slot].mapped, &camera, sizeof(camera)); + base::MemCopy(camera_[slot].mapped, &camera, sizeof(camera)); PrecipPush push{}; push.cam_right[0] = frame.cam_right.x; @@ -197,7 +198,7 @@ void PrecipVolume::AddToGraph(RenderGraph& graph, ResourceHandle color, Resource push.wind[1] = frame.wind[1]; push.wind[2] = frame.gustiness; push.wind[3] = frame.lightning; - std::memcpy(push.occl, frame.occl, sizeof(push.occl)); + base::MemCopy(push.occl, frame.occl, sizeof(push.occl)); push.jitter[0] = frame.jitter[0]; push.jitter[1] = frame.jitter[1]; push.dt = frame.dt; @@ -224,7 +225,7 @@ void PrecipVolume::AddToGraph(RenderGraph& graph, ResourceHandle color, Resource items.push_back(InGeneral(Bind::Combined(2, froxel_view, froxel_sampler))); if (rt) items.push_back(Bind::Accel(3, raytracing->tlas(tlas_slot))); items.push_back(Bind::Uniform(4, camera_[slot], 0, sizeof(PrecipCamera))); - ctx.cmd->BindTransient(0, {items.data(), items.size()}); + ctx.cmd->BindTransient(0, base::Span(items.data(), items.size())); ctx.cmd->Push(push); ctx.cmd->Draw(4, drop_count, 0, 0); diff --git a/engine/render/atmosphere/surface_weather.cc b/engine/render/atmosphere/surface_weather.cc index 91857eac..7ac02f38 100644 --- a/engine/render/atmosphere/surface_weather.cc +++ b/engine/render/atmosphere/surface_weather.cc @@ -1,7 +1,8 @@ #include "render/atmosphere/surface_weather.h" -#include +#include +#include "base/memory/mem_ops.h" #include "core/log.h" #include "render/rhi/device.h" #include "shaders/surface_weather_cs_hlsl.h" @@ -82,7 +83,7 @@ ResourceHandle SurfaceWeather::AddToGraph(RenderGraph& graph, ResourceHandle col [this, color, normals, depth, out, sky_view, sky_sampler, extent, frame, slot](PassContext& ctx) { const SurfaceCamera camera{frame.inv_view_proj}; - std::memcpy(camera_[slot].mapped, &camera, sizeof(camera)); + base::MemCopy(camera_[slot].mapped, &camera, sizeof(camera)); SurfacePush push{}; push.camera_pos[0] = frame.camera_pos.x; @@ -92,7 +93,7 @@ ResourceHandle SurfaceWeather::AddToGraph(RenderGraph& graph, ResourceHandle col push.params[1] = frame.snow_cover; push.params[2] = frame.time; push.params[3] = frame.rain; - std::memcpy(push.occl, frame.occl, sizeof(push.occl)); + base::MemCopy(push.occl, frame.occl, sizeof(push.occl)); push.occl2[0] = frame.occlusion ? frame.occl_range : 0.0f; push.size[0] = extent.width; push.size[1] = extent.height; diff --git a/engine/render/atmosphere/volumetric_fog.cc b/engine/render/atmosphere/volumetric_fog.cc index 6158989a..dc764ba7 100644 --- a/engine/render/atmosphere/volumetric_fog.cc +++ b/engine/render/atmosphere/volumetric_fog.cc @@ -1,7 +1,8 @@ #include "render/atmosphere/volumetric_fog.h" -#include +#include +#include "base/memory/mem_ops.h" #include "core/log.h" #include "render/gi/raytracing.h" #include "render/rhi/device.h" @@ -77,7 +78,7 @@ ResourceHandle VolumetricFog::AddToGraph(RenderGraph& graph, RayTracingContext& }, [this, &raytracing, tlas_slot, color, depth, fogged, extent, frame, slot](PassContext& ctx) { const FogCamera camera{frame.inv_view_proj}; - std::memcpy(camera_[slot].mapped, &camera, sizeof(camera)); + base::MemCopy(camera_[slot].mapped, &camera, sizeof(camera)); FogPush push{}; push.camera_pos[0] = frame.camera_pos.x; diff --git a/engine/render/core/bindless.cc b/engine/render/core/bindless.cc index 3fa9c29b..bf669075 100644 --- a/engine/render/core/bindless.cc +++ b/engine/render/core/bindless.cc @@ -1,14 +1,16 @@ #include "render/core/bindless.h" -#include -#include +#include +#include "base/algorithm.h" +#include "base/memory/mem_ops.h" +#include "base/memory/unique_pointer.h" #include "core/log.h" namespace rx::render { -std::unique_ptr BindlessRegistry::Create(Device& device) { - auto registry = std::unique_ptr(new BindlessRegistry(device)); +base::UniquePointer BindlessRegistry::Create(Device& device) { + auto registry = base::UniquePointer(new BindlessRegistry(device)); if (!registry->Initialize()) return nullptr; return registry; } @@ -106,7 +108,7 @@ u32 BindlessRegistry::RegisterMaterial(const MaterialRecord& record) { return kInvalidIndex; } u32 index = material_count_++; - std::memcpy(static_cast(material_table_.mapped) + index * sizeof(MaterialRecord), &record, + base::MemCopy(static_cast(material_table_.mapped) + index * sizeof(MaterialRecord), &record, sizeof(record)); return index; } @@ -162,11 +164,11 @@ u32 BindlessRegistry::RegisterMesh(const GpuBuffer& vertices, const GpuBuffer& i index_srv.array_index = record.index_srv; device_.UpdateBindingSet(set_, {vertex_srv, index_srv}); if (geometry_count != 0) { - std::memcpy(static_cast(geometry_table_.mapped) + + base::MemCopy(static_cast(geometry_table_.mapped) + geometry_offset * sizeof(GeometryRecord), geometries, geometry_count * sizeof(GeometryRecord)); } - std::memcpy(static_cast(mesh_table_.mapped) + index * sizeof(MeshRecord), &record, + base::MemCopy(static_cast(mesh_table_.mapped) + index * sizeof(MeshRecord), &record, sizeof(record)); active_meshes_[index] = 1; mesh_geometry_counts_[index] = geometry_count; @@ -180,10 +182,13 @@ void BindlessRegistry::ReleaseMesh(u32 index) { const u32 geometry_count = mesh_geometry_counts_[index]; if (geometry_count != 0) { free_geometry_ranges_.push_back({record->geometry_offset, geometry_count}); - std::sort(free_geometry_ranges_.begin(), free_geometry_ranges_.end(), - [](const GeometryRange& a, const GeometryRange& b) { - return a.offset < b.offset; - }); + // Free ranges are disjoint and never empty, so offsets are unique and any + // correct sort agrees. + base::Sort(free_geometry_ranges_.data(), + free_geometry_ranges_.data() + free_geometry_ranges_.size(), + [](const GeometryRange& a, const GeometryRange& b) { + return a.offset < b.offset; + }); for (size_t i = 1; i < free_geometry_ranges_.size();) { GeometryRange& previous = free_geometry_ranges_[i - 1]; const GeometryRange current = free_geometry_ranges_[i]; diff --git a/engine/render/core/bindless.h b/engine/render/core/bindless.h index 86f9709c..0eb7773c 100644 --- a/engine/render/core/bindless.h +++ b/engine/render/core/bindless.h @@ -1,10 +1,10 @@ #ifndef RX_RENDER_BINDLESS_H_ #define RX_RENDER_BINDLESS_H_ -#include #include +#include "base/memory/unique_pointer.h" #include "core/types.h" #include "render/rhi/device.h" @@ -97,7 +97,7 @@ class BindlessRegistry { // RX_MATERIAL_FLAG_HUMAN so the raster and RT flag namespaces agree. static constexpr u32 kMaterialHuman = 1u << 22; - static std::unique_ptr Create(Device& device); + static base::UniquePointer Create(Device& device); ~BindlessRegistry(); BindlessRegistry(const BindlessRegistry&) = delete; diff --git a/engine/render/core/dynamic_resolution.cc b/engine/render/core/dynamic_resolution.cc index a85909aa..4123b192 100644 --- a/engine/render/core/dynamic_resolution.cc +++ b/engine/render/core/dynamic_resolution.cc @@ -1,7 +1,8 @@ +#include "core/scalar.h" #include "render/core/dynamic_resolution.h" -#include -#include +#include +#include namespace rx::render { @@ -14,7 +15,7 @@ bool DynamicResolution::Update(f32 gpu_ms) { } const f32 target = settings_.target_ms; - const f32 min_scale = std::clamp(settings_.min_scale, 0.25f, 1.0f); + const f32 min_scale = rx::Clamp(settings_.min_scale, 0.25f, 1.0f); if (ema_ms_ > target * kOvershoot) { under_ = 0; @@ -25,13 +26,13 @@ bool DynamicResolution::Update(f32 gpu_ms) { if (++over_ < kDownFrames) return false; // Cost is ~quadratic in the per-axis scale: jump to the step predicted to // land just under the target instead of walking down one step per cycle. - f32 fit = scale_ * std::sqrt(target * 0.95f / ema_ms_); - f32 next = std::floor(fit / kStep) * kStep; - return Apply(std::clamp(next, min_scale, scale_ - kStep)); + f32 fit = scale_ * ::sqrt(target * 0.95f / ema_ms_); + f32 next = ::floor(fit / kStep) * kStep; + return Apply(rx::Clamp(next, min_scale, scale_ - kStep)); } over_ = 0; - f32 raised = std::min(1.0f, scale_ + kStep); + f32 raised = rx::Min(1.0f, scale_ + kStep); if (raised > scale_ + 1e-4f) { // The predicted cost at the next step up must still fit with margin, or // the controller would oscillate across the target. @@ -46,8 +47,8 @@ bool DynamicResolution::Update(f32 gpu_ms) { } bool DynamicResolution::Apply(f32 next) { - next = std::round(next / kStep) * kStep; // stay on the grid, no float drift - if (std::abs(next - scale_) < 1e-4f) { + next = ::round(next / kStep) * kStep; // stay on the grid, no float drift + if (::abs(next - scale_) < 1e-4f) { over_ = under_ = 0; return false; } diff --git a/engine/render/core/presets.cc b/engine/render/core/presets.cc index fdb1a719..cbb275c7 100644 --- a/engine/render/core/presets.cc +++ b/engine/render/core/presets.cc @@ -1,23 +1,22 @@ +#include "base/strings/xstring.h" #include "render/core/presets.h" -#include -#include +#include #include namespace rx::render { namespace { -std::string Lower(std::string s) { - std::transform(s.begin(), s.end(), s.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); +base::String Lower(base::String s) { + for (char& c : s) c = static_cast(::tolower(static_cast(c))); return s; } -bool IsMobileGpu(const std::string& name) { - const std::string n = Lower(name); +bool IsMobileGpu(const base::String& name) { + const base::String n = Lower(name); for (const char* marker : {"adreno", "mali", "powervr", "apple", "xclipse", "immortalis", "vivante"}) { - if (n.find(marker) != std::string::npos) return true; + if (n.find(marker) != base::String::npos) return true; } return false; } @@ -28,7 +27,7 @@ f32 Degrees(f32 deg) { return deg / 57.29578f; } // The renderer falls back to taa if the chosen backend is not compiled or the // gpu cannot run it, so this only expresses a preference. UpscalerKind PreferredUpscaler(const DeviceCaps& caps) { - return Lower(caps.adapter_name).find("nvidia") != std::string::npos ? UpscalerKind::kDlss + return Lower(caps.adapter_name).find("nvidia") != base::String::npos ? UpscalerKind::kDlss : UpscalerKind::kFsr3; } @@ -185,7 +184,7 @@ RenderSettings PresetSettings(QualityPreset preset, const DeviceCaps& caps) { // every non-rt tier (and forced-low on capable gpus) still casts sun shadows. if (!s.rt_shadows) s.shadow_maps = true; if (s.upscaler == UpscalerKind::kDlss && - Lower(caps.adapter_name).find("nvidia") == std::string::npos) { + Lower(caps.adapter_name).find("nvidia") == base::String::npos) { s.upscaler = UpscalerKind::kFsr3; } if (s.aa_mode == AntiAliasingMode::kUpscaler && s.upscaler == UpscalerKind::kNone) { @@ -220,8 +219,8 @@ const char* PresetName(QualityPreset preset) { return "auto"; } -QualityPreset ParsePreset(const std::string& name) { - const std::string n = Lower(name); +QualityPreset ParsePreset(const base::String& name) { + const base::String n = Lower(name); if (n == "android" || n == "mobile") return QualityPreset::kAndroid; if (n == "steamdeck" || n == "deck") return QualityPreset::kSteamDeck; if (n == "low" || n == "lowend") return QualityPreset::kLowEnd; diff --git a/engine/render/core/presets.h b/engine/render/core/presets.h index 3a1fe545..e479a8bd 100644 --- a/engine/render/core/presets.h +++ b/engine/render/core/presets.h @@ -1,8 +1,8 @@ #ifndef RX_RENDER_PRESETS_H_ #define RX_RENDER_PRESETS_H_ -#include +#include "base/strings/xstring.h" #include "core/export.h" #include "core/types.h" #include "render/rhi/device.h" @@ -37,7 +37,7 @@ inline QualityPreset ResolvePreset(QualityPreset preset, const DeviceCaps& caps) } RX_RENDER_EXPORT const char* PresetName(QualityPreset preset); -RX_RENDER_EXPORT QualityPreset ParsePreset(const std::string& name); // kAuto on no match +RX_RENDER_EXPORT QualityPreset ParsePreset(const base::String& name); // kAuto on no match } // namespace rx::render diff --git a/engine/render/core/render_graph.cc b/engine/render/core/render_graph.cc index 62afa273..c81c9b07 100644 --- a/engine/render/core/render_graph.cc +++ b/engine/render/core/render_graph.cc @@ -1,5 +1,7 @@ #include "render/core/render_graph.h" +#include "base/memory/move.h" +#include "base/strings/xstring.h" #include "core/log.h" #include "render/rhi/device.h" @@ -102,10 +104,10 @@ ResourceHandle RenderGraph::CreateTexture(const TransientTextureDesc& desc) { return static_cast(resources_.size()); } -ResourceHandle RenderGraph::ImportImage(std::string name, const GpuImage& image, +ResourceHandle RenderGraph::ImportImage(base::String name, const GpuImage& image, ResourceState* state) { Resource resource; - resource.desc.name = std::move(name); + resource.desc.name = base::move(name); resource.desc.format = image.format; resource.image = image; resource.imported = true; @@ -132,10 +134,10 @@ ResourceHandle RenderGraph::ImportBackbuffer(const GpuImage& image, ResourceStat return static_cast(resources_.size()); } -void RenderGraph::AddPass(std::string name, SetupFn setup, ExecuteFn execute) { - Pass pass{.name = std::move(name), .builder = {}, .execute = std::move(execute), .barriers = {}}; +void RenderGraph::AddPass(base::String name, SetupFn setup, ExecuteFn execute) { + Pass pass{.name = base::move(name), .builder = {}, .execute = base::move(execute), .barriers = {}}; setup(pass.builder); - passes_.push_back(std::move(pass)); + passes_.push_back(base::move(pass)); } bool RenderGraph::Compile(Device& device, TransientPool& pool) { @@ -199,7 +201,7 @@ bool RenderGraph::Compile(Device& device, TransientPool& pool) { } entry.barriers = static_cast(pass.barriers.size()); stats_.barrier_count += entry.barriers; - stats_.passes.push_back(std::move(entry)); + stats_.passes.push_back(base::move(entry)); } for (const Resource& resource : resources_) { Stats::Resource entry; @@ -213,7 +215,7 @@ bool RenderGraph::Compile(Device& device, TransientPool& pool) { stats_.transient_bytes += entry.bytes; ++stats_.transient_count; } - stats_.resources.push_back(std::move(entry)); + stats_.resources.push_back(base::move(entry)); } return true; } @@ -223,7 +225,7 @@ CommandList* RenderGraph::Execute(PassContext& ctx) { auto run_pass = [&](Pass& pass) { if (pass_begin_) pass_begin_(*ctx.cmd, pass.name.c_str()); if (!pass.barriers.empty()) { - ctx.cmd->TextureBarriers({pass.barriers.data(), pass.barriers.size()}); + ctx.cmd->TextureBarriers(base::Span(pass.barriers.data(), pass.barriers.size())); } if (pass.execute) pass.execute(ctx); if (pass_end_) pass_end_(*ctx.cmd); @@ -258,7 +260,7 @@ CommandList* RenderGraph::Execute(PassContext& ctx) { if (!do_async) { for (Pass& pass : passes_) run_pass(pass); if (!final_barriers_.empty()) { - ctx.cmd->TextureBarriers({final_barriers_.data(), final_barriers_.size()}); + ctx.cmd->TextureBarriers(base::Span(final_barriers_.data(), final_barriers_.size())); } return ctx.cmd; } @@ -297,7 +299,7 @@ CommandList* RenderGraph::Execute(PassContext& ctx) { if (!passes_[i].builder.async) run_pass(passes_[i]); } if (!final_barriers_.empty()) { - ctx.cmd->TextureBarriers({final_barriers_.data(), final_barriers_.size()}); + ctx.cmd->TextureBarriers(base::Span(final_barriers_.data(), final_barriers_.size())); } return ctx.cmd; } diff --git a/engine/render/core/render_graph.h b/engine/render/core/render_graph.h index a2e7331b..df2b3bb9 100644 --- a/engine/render/core/render_graph.h +++ b/engine/render/core/render_graph.h @@ -1,11 +1,12 @@ #ifndef RX_RENDER_RENDER_GRAPH_H_ #define RX_RENDER_RENDER_GRAPH_H_ -#include -#include #include +#include "base/functional/function.h" +#include "base/memory/move.h" +#include "base/strings/xstring.h" #include "core/types.h" #include "render/rhi/command_list.h" #include "render/rhi/resources.h" @@ -33,7 +34,7 @@ enum class ResourceUsage : u8 { }; struct TransientTextureDesc { - std::string name; + base::String name; Format format = Format::kRGBA16Float; u32 width = 0; u32 height = 0; @@ -103,16 +104,16 @@ class RenderGraph { }; // These callbacks accept arbitrary application captures, including - // non-trivially relocatable types such as std::string and std::function. - using SetupFn = std::function; - using ExecuteFn = std::function; + // non-trivially relocatable types such as base::String and base::Function. + using SetupFn = base::Function; + using ExecuteFn = base::Function; ResourceHandle CreateTexture(const TransientTextureDesc& desc); // Persistent images (TAA history, path-trace ping-pongs) enter the graph // here. The graph reads the starting state from *state and writes the state // the image is left in back to it, so the owner can re-import next frame. - ResourceHandle ImportImage(std::string name, const GpuImage& image, ResourceState* state); + ResourceHandle ImportImage(base::String name, const GpuImage& image, ResourceState* state); // Imported swapchain image. Contents are discarded on first use and the // graph appends a transition to `final_state` after the last pass. Override @@ -121,15 +122,15 @@ class RenderGraph { ResourceHandle ImportBackbuffer(const GpuImage& image, ResourceState final_state = ResourceState::kPresent); - void AddPass(std::string name, SetupFn setup, ExecuteFn execute); + void AddPass(base::String name, SetupFn setup, ExecuteFn execute); // Optional per-pass brackets (gpu profiler timestamps + debug labels). Begin // runs before the pass barriers, end after the pass executes. - using PassBegin = std::function; - using PassEnd = std::function; + using PassBegin = base::Function; + using PassEnd = base::Function; void SetPassHooks(PassBegin begin, PassEnd end) { - pass_begin_ = std::move(begin); - pass_end_ = std::move(end); + pass_begin_ = base::move(begin); + pass_end_ = base::move(end); } bool Compile(Device& device, TransientPool& pool); @@ -147,13 +148,13 @@ class RenderGraph { // and totals. Captured at the end of Compile. struct Stats { struct Pass { - std::string name; + base::String name; u32 reads = 0; u32 writes = 0; u32 barriers = 0; }; struct Resource { - std::string name; + base::String name; u32 width = 0; u32 height = 0; u64 bytes = 0; @@ -190,7 +191,7 @@ class RenderGraph { }; struct Pass { - std::string name; + base::String name; PassBuilder builder; ExecuteFn execute; base::Vector barriers; diff --git a/engine/render/core/renderer.cc b/engine/render/core/renderer.cc index 8b0dda5f..9f441a75 100644 --- a/engine/render/core/renderer.cc +++ b/engine/render/core/renderer.cc @@ -1,11 +1,10 @@ #include "render/core/renderer.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include + #include @@ -13,8 +12,18 @@ #include "asset/primitives.h" #include "asset/texture_compress.h" +#include "base/containers/span.h" +#include "base/containers/vector.h" +#include "base/functional/function.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/optional.h" +#include "base/strings/xstring.h" +#include "base/time/time.h" #include "core/log.h" #include "core/memory/memory_tracker.h" +#include "core/scalar.h" #include "render/util/exr_write.h" #include "shaders/blit_ps_slang.h" #include "shaders/cloud_shadow_cs_hlsl.h" @@ -29,6 +38,7 @@ #include "shaders/pick_id_ps_hlsl.h" #include "shaders/pick_id_vs_hlsl.h" #include "shaders/sss_blur_cs_hlsl.h" +#include "core/sort.h" namespace rx::render { namespace { @@ -235,8 +245,8 @@ u32 SelectLod(const GpuMesh &mesh, f32 distance) { u32 lod_count = 1u + static_cast(mesh.lods.size()); if (lod_count <= 1) return 0; - f32 unit = std::max(mesh.bounds_radius, 0.25f) * 2.5f; - u32 lod = static_cast(distance / std::max(unit, 0.5f)); + f32 unit = rx::Max(mesh.bounds_radius, 0.25f) * 2.5f; + u32 lod = static_cast(distance / rx::Max(unit, 0.5f)); return lod < lod_count ? lod : lod_count - 1; } @@ -263,32 +273,32 @@ bool SupportsStaticInstances(const GpuMesh &mesh, return true; } -bool HasUniformScale(std::span transforms) { +bool HasUniformScale(base::Span transforms) { for (const Mat4 &transform : transforms) { const f32 *m = transform.m; for (u32 i = 0; i < 16; ++i) - if (!std::isfinite(m[i])) + if (!::isfinite(m[i])) return false; - if (std::abs(m[3]) > 1e-5f || std::abs(m[7]) > 1e-5f || - std::abs(m[11]) > 1e-5f || std::abs(m[15] - 1.0f) > 1e-5f) + if (::abs(m[3]) > 1e-5f || ::abs(m[7]) > 1e-5f || + ::abs(m[11]) > 1e-5f || ::abs(m[15] - 1.0f) > 1e-5f) return false; - const f32 sx = std::sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]); - const f32 sy = std::sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6]); - const f32 sz = std::sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]); - const f32 tolerance = std::max({sx, sy, sz}) * 1e-4f; - if (sx <= 1e-6f || std::abs(sx - sy) > tolerance || - std::abs(sx - sz) > tolerance) + const f32 sx = ::sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]); + const f32 sy = ::sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6]); + const f32 sz = ::sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]); + const f32 tolerance = rx::Max({sx, sy, sz}) * 1e-4f; + if (sx <= 1e-6f || ::abs(sx - sy) > tolerance || + ::abs(sx - sz) > tolerance) return false; const f32 orthogonal_tolerance = sx * sx * 1e-4f; const f32 determinant = m[0] * (m[5] * m[10] - m[6] * m[9]) - m[4] * (m[1] * m[10] - m[2] * m[9]) + m[8] * (m[1] * m[6] - m[2] * m[5]); if (determinant <= 0 || - std::abs(m[0] * m[4] + m[1] * m[5] + m[2] * m[6]) > + ::abs(m[0] * m[4] + m[1] * m[5] + m[2] * m[6]) > orthogonal_tolerance || - std::abs(m[0] * m[8] + m[1] * m[9] + m[2] * m[10]) > + ::abs(m[0] * m[8] + m[1] * m[9] + m[2] * m[10]) > orthogonal_tolerance || - std::abs(m[4] * m[8] + m[5] * m[9] + m[6] * m[10]) > + ::abs(m[4] * m[8] + m[5] * m[9] + m[6] * m[10]) > orthogonal_tolerance) return false; } @@ -297,7 +307,7 @@ bool HasUniformScale(std::span transforms) { f32 InstanceGroupDistance(const InstanceStore::Group &group, const Vec3 &eye) { const Vec3 delta = eye - group.bounds_center; - return std::max(std::sqrt(Dot(delta, delta)) - group.bounds_radius, 0.0f) / + return rx::Max(::sqrt(Dot(delta, delta)) - group.bounds_radius, 0.0f) / group.lod_scale; } @@ -320,7 +330,7 @@ void ExtractFrustumPlanes(const Mat4 &vp, f32 out[5][4]) { }; for (int i = 0; i < 5; ++i) { f32 len = - std::sqrt(p[i][0] * p[i][0] + p[i][1] * p[i][1] + p[i][2] * p[i][2]); + ::sqrt(p[i][0] * p[i][0] + p[i][1] * p[i][1] + p[i][2] * p[i][2]); if (len < 1e-8f) len = 1.0f; for (int c = 0; c < 4; ++c) @@ -383,7 +393,7 @@ f32 MaskedSubmeshOpacity(const base::Vector &verts, c.position[2] - a.position[2]}; f32 cx = e1[1] * e2[2] - e1[2] * e2[1], cy = e1[2] * e2[0] - e1[0] * e2[2], cz = e1[0] * e2[1] - e1[1] * e2[0]; - f32 area = 0.5f * std::sqrt(cx * cx + cy * cy + cz * cz); + f32 area = 0.5f * ::sqrt(cx * cx + cy * cy + cz * cz); weighted += static_cast(mean_a) * area; area_sum += area; } @@ -470,7 +480,7 @@ bool Renderer::InitializeCommon(const RendererDesc &desc, Window *window, // RX_RHI=vulkan|d3d12|null|auto overrides the graphics backend. Backend backend = desc.backend; if (const char *name = RhiBackend.get()) { - std::string value = name; + base::String value = name; if (value == "vulkan") backend = Backend::kVulkan; else if (value == "d3d12") @@ -512,8 +522,8 @@ bool Renderer::InitializeCommon(const RendererDesc &desc, Window *window, device.EndPipelineBatch(); } } pipeline_batch{*device_}; - auto t_batch0 = std::chrono::steady_clock::now(); - const char *pso_batch_env = std::getenv("RX_PSO_BATCH"); + auto t_batch0 = base::TimeTicks::Now(); + const char *pso_batch_env = ::getenv("RX_PSO_BATCH"); if (!pso_batch_env || pso_batch_env[0] != '0') device_->BeginPipelineBatch(); @@ -526,7 +536,7 @@ bool Renderer::InitializeCommon(const RendererDesc &desc, Window *window, swapchain_ = window ? device_->CreateSwapchain(output_width_, output_height_, settings_.vsync, swapchain_hdr_request_) - : std::make_unique( + : base::MakeUnique( Format::kBGRA8Unorm, Extent2D{output_width_, output_height_}); if (!swapchain_ || !CreateFrameResources()) return false; @@ -562,7 +572,7 @@ bool Renderer::InitializeCommon(const RendererDesc &desc, Window *window, : "no textureCompressionBC"); } - transient_pool_ = std::make_unique(*device_); + transient_pool_ = base::MakeUnique(*device_); // The bindless registry has no ray-tracing dependency (buffers + an // update-after-bind set): the forward terrain splat and textured particles // sample through it too, so it exists on every real device. Mesh/geometry @@ -570,7 +580,7 @@ bool Renderer::InitializeCommon(const RendererDesc &desc, Window *window, bindless_ = BindlessRegistry::Create(*device_); if (!bindless_) return false; - material_system_ = MaterialSystem::Create(*device_, bindless_.get()); + material_system_ = MaterialSystem::Create(*device_, bindless_.Get_UseOnlyIfYouKnowWhatYouareDoing()); if (!material_system_) return false; environment_ = EnvironmentSystem::Create(*device_); @@ -950,10 +960,10 @@ bool Renderer::InitializeCommon(const RendererDesc &desc, Window *window, // Debug captures without window manager screenshots: // RX_SCREENSHOT=/tmp/frame.png:12 saves the frame at t=12s. if (const char *spec = Screenshot.get()) { - std::string value = spec; + base::String value = spec; size_t colon = value.find_last_of(':'); - if (colon != std::string::npos) { - screenshot_at_ = std::atof(value.c_str() + colon + 1); + if (colon != base::String::npos) { + screenshot_at_ = ::atof(value.c_str() + colon + 1); value.resize(colon); } screenshot_path_ = value; @@ -961,8 +971,8 @@ bool Renderer::InitializeCommon(const RendererDesc &desc, Window *window, // RX_SEQ=prefix:startsec:count[:stride] dumps a burst of composited frames. if (const char *spec = Sequence.get()) { - std::string value = spec; - base::Vector fields; + base::String value = spec; + base::Vector fields; size_t start = 0; for (size_t i = 0; i <= value.size(); ++i) { if (i == value.size() || value[i] == ':') { @@ -972,10 +982,10 @@ bool Renderer::InitializeCommon(const RendererDesc &desc, Window *window, } if (fields.size() >= 3) { seq_prefix_ = fields[0]; - seq_at_ = std::atof(fields[1].c_str()); - seq_count_ = std::atoi(fields[2].c_str()); + seq_at_ = ::atof(fields[1].c_str()); + seq_count_ = ::atoi(fields[2].c_str()); seq_stride_ = - fields.size() >= 4 ? std::max(1, std::atoi(fields[3].c_str())) : 1; + fields.size() >= 4 ? rx::Max(1, ::atoi(fields[3].c_str())) : 1; } else { RX_WARN( "RX_SEQ ignored, expected prefix:startsec:count[:stride], got '{}'", @@ -986,10 +996,10 @@ bool Renderer::InitializeCommon(const RendererDesc &desc, Window *window, // RX_HDR=/tmp/frame.hdr:12 exports the linear-hdr frame (radiance rgbe) at // t=12s. if (const char *spec = Hdr.get()) { - std::string value = spec; + base::String value = spec; size_t colon = value.find_last_of(':'); - if (colon != std::string::npos) { - hdr_at_ = std::atof(value.c_str() + colon + 1); + if (colon != base::String::npos) { + hdr_at_ = ::atof(value.c_str() + colon + 1); value.resize(colon); } hdr_path_ = value; @@ -1045,8 +1055,8 @@ bool Renderer::InitializeCommon(const RendererDesc &desc, Window *window, // lighting/shadow tests (normalized; y clamped below the horizon). if (const char *sd = SunDir.get()) { Vec3 d{}; - if (std::sscanf(sd, "%f,%f,%f", &d.x, &d.y, &d.z) == 3) { - f32 len = std::sqrt(d.x * d.x + d.y * d.y + d.z * d.z); + if (::sscanf(sd, "%f,%f,%f", &d.x, &d.y, &d.z) == 3) { + f32 len = ::sqrt(d.x * d.x + d.y * d.y + d.z * d.z); if (len > 1e-4f) settings_.sun_direction = {d.x / len, d.y / len, d.z / len}; } @@ -1144,15 +1154,15 @@ bool Renderer::InitializeCommon(const RendererDesc &desc, Window *window, if (ProceduralGrassOpt.overridden()) settings_.procedural_grass = ProceduralGrassOpt; if (PathtraceSpp.overridden()) - settings_.path_trace_spp = static_cast(std::max(1, int(PathtraceSpp))); + settings_.path_trace_spp = static_cast(rx::Max(1, int(PathtraceSpp))); if (PathtraceAccum.overridden()) settings_.path_trace_accum = - static_cast(std::max(1, int(PathtraceAccum))); + static_cast(rx::Max(1, int(PathtraceAccum))); if (PathtraceRecon.overridden()) settings_.path_trace_recon = PathtraceRecon; if (PathtraceReconDebug.overridden()) settings_.path_trace_recon_debug = - static_cast(std::max(0, int(PathtraceReconDebug))); + static_cast(rx::Max(0, int(PathtraceReconDebug))); if (PathtraceRestir.overridden()) settings_.path_trace_restir = PathtraceRestir; if (PathtraceRestirDi.overridden()) @@ -1216,18 +1226,16 @@ bool Renderer::InitializeCommon(const RendererDesc &desc, Window *window, // is what makes the software path possible, but the ~85 MiB RCGI cache itself // is deferred so a device that never enables it pays nothing. - auto t_batch1 = std::chrono::steady_clock::now(); + auto t_batch1 = base::TimeTicks::Now(); if (!pipeline_batch.End()) { RX_ERROR("pipeline batch reported failed compilations"); return false; } - auto t_batch2 = std::chrono::steady_clock::now(); + auto t_batch2 = base::TimeTicks::Now(); RX_INFO( "renderer init: {} ms (pipeline batch joined in {} ms)", - std::chrono::duration_cast(t_batch1 - t_batch0) - .count(), - std::chrono::duration_cast(t_batch2 - t_batch1) - .count()); + (t_batch1 - t_batch0).InMilliseconds(), + (t_batch2 - t_batch1).InMilliseconds()); // Grass is optional and creates nonstandard push-constant layouts. Build its // baseline pipelines outside the startup batch so a device that cannot @@ -1252,12 +1260,12 @@ bool Renderer::InitializeCommon(const RendererDesc &desc, Window *window, // batch (lazily in ApplySettings during RenderFrame), so a failure there // returns a null system and is handled non-fatally at that call site too.) if (want_sdf) { - sdf_scene_ = std::make_unique(*device_); - sdf_clipmap_ = std::make_unique(*device_); + sdf_scene_ = base::MakeUnique(*device_); + sdf_clipmap_ = base::MakeUnique(*device_); if (!sdf_clipmap_->Initialize()) { RX_WARN("sdf: clipmap unavailable, disabling the SDF path"); - sdf_clipmap_.reset(); - sdf_scene_.reset(); + sdf_clipmap_.Reset(); + sdf_scene_.Reset(); } else { sdf_available_ = true; // immutable for the session once creation succeeds } @@ -1282,7 +1290,7 @@ void Renderer::ClearEnvironmentMap() { environment_->ClearEnvironmentMap(); } -void Renderer::CaptureScreenshot(const std::string &path) { +void Renderer::CaptureScreenshot(const base::String &path) { screenshot_path_ = path; screenshot_at_ = -1; } @@ -1351,7 +1359,7 @@ void Renderer::DumpFgImage(const GpuImage &image, ResourceState state, } } -void Renderer::WriteBackbufferPng(const std::string &path) { +void Renderer::WriteBackbufferPng(const base::String &path) { device_->WaitIdle(); Extent2D extent = swapchain_->extent(); u64 size = static_cast(extent.width) * extent.height * 4; @@ -1475,11 +1483,11 @@ void Renderer::UpdateRenderResolution() { // No upscaler: render at output * render_scale. >1 supersamples (the post // pass samples this image into the swapchain, so it downscales for free). // Dynamic resolution multiplies in as a <=1 factor while active. - f32 rs = std::clamp(settings_.render_scale * drs_.scale(), 0.25f, 2.0f); + f32 rs = rx::Clamp(settings_.render_scale * drs_.scale(), 0.25f, 2.0f); render_width_ = - std::max(1u, static_cast(static_cast(output_width_) * rs)); + rx::Max(1u, static_cast(static_cast(output_width_) * rs)); render_height_ = - std::max(1u, static_cast(static_cast(output_height_) * rs)); + rx::Max(1u, static_cast(static_cast(output_height_) * rs)); } } @@ -1558,7 +1566,7 @@ void Renderer::ApplySettings() { material_system_->set_layout(), environment_->env_set_layout(), bindless_ ? bindless_->set_layout() : BindingLayoutHandle{}, want_msaa); if (rebuilt) { - mesh_pipeline_ = std::move(rebuilt); + mesh_pipeline_ = base::move(rebuilt); applied_msaa_samples_ = want_msaa; RX_INFO("msaa: mesh pipelines rebuilt at {}x", want_msaa); } else { @@ -1599,7 +1607,7 @@ void Renderer::ApplySettings() { drs_.scale() != applied_dynamic_scale_; if (upscaler_changed) { device_->WaitIdle(); - upscaler_.reset(); + upscaler_.Reset(); if (settings_.upscaler != UpscalerKind::kNone) { if (!CreateUpscalerWithFallback()) { RX_WARN("upscaler unavailable, falling back to taa"); @@ -1649,7 +1657,7 @@ void Renderer::ApplySettings() { {.radius = settings_.ao_radius, .intensity = settings_.ao_intensity * 1.8f, .power = 1.5f, - .sample_count = std::clamp(settings_.ao_rays * 8u, 4u, 32u)}); + .sample_count = rx::Clamp(settings_.ao_rays * 8u, 4u, 32u)}); shadow_.Configure({.cascade_count = ShadowPass::kMaxCascades, .resolution = settings_.shadow_resolution, .distance = settings_.shadow_distance}); @@ -1695,7 +1703,7 @@ void Renderer::ApplySettings() { RX_ERROR("rcgi: creation failed; feature unavailable this session"); if (rcgi_) light_grid_.Destroy(*device_); - rcgi_.reset(); + rcgi_.Reset(); rcgi_create_failed_ = true; // do not retry every frame } } @@ -1730,11 +1738,11 @@ void Renderer::UploadVirtualGeometryMesh(const asset::Mesh &mesh) { vgeo_.Upload(*device_, mesh); } -void Renderer::SetVirtualGeometryInstances(std::span transforms) { +void Renderer::SetVirtualGeometryInstances(base::Span transforms) { vgeo_.SetInstances(transforms); } -void Renderer::SetInteriorVolumes(std::span volumes) { +void Renderer::SetInteriorVolumes(base::Span volumes) { interior_volumes_.assign(volumes.begin(), volumes.end()); } @@ -1814,22 +1822,22 @@ u32 Renderer::BakeImposter(const asset::Mesh &mesh) { materials.push_back({base.image, base.alpha_cutoff}); } } - return imposters_.Bake(*device_, mesh, {materials.data(), materials.size()}); + return imposters_.Bake(*device_, mesh, base::Span(materials.data(), materials.size())); } void Renderer::SetImposterInstances( - std::span instances) { + base::Span instances) { if (!device_ || device_->is_stub()) return; imposters_.SetInstances(*device_, instances); } InstanceGroupHandle -Renderer::CreateInstanceGroup(u64 mesh, std::span transforms) { +Renderer::CreateInstanceGroup(u64 mesh, base::Span transforms) { if (!device_ || device_->is_stub()) return {}; const GpuMesh *gpu = meshes_.find(mesh); - if (!gpu || !SupportsStaticInstances(*gpu, material_system_.get()) || + if (!gpu || !SupportsStaticInstances(*gpu, material_system_.Get_UseOnlyIfYouKnowWhatYouareDoing()) || mesh_emitters_.find(mesh) || !HasUniformScale(transforms)) return {}; InstanceGroupHandle handle = instances_.Create( @@ -1840,7 +1848,7 @@ Renderer::CreateInstanceGroup(u64 mesh, std::span transforms) { } bool Renderer::UpdateInstanceGroup(InstanceGroupHandle handle, - std::span transforms) { + base::Span transforms) { if (!device_ || device_->is_stub() || handle.index >= instances_.groups().size()) return false; @@ -1881,7 +1889,7 @@ bool Renderer::UploadMesh(const asset::Mesh &mesh, u64 id_salt) { } e.texture = index; // now a bindless index, 0xffffffff = untextured } - mesh_emitters_[mesh_key] = std::move(emitters); + mesh_emitters_[mesh_key] = base::move(emitters); }; // Emitter-only NIFs (smoke columns, dust wisps) carry no geometry but still // need their particle pools: register the emitters and accept the upload so @@ -1973,13 +1981,13 @@ bool Renderer::UploadMesh(const asset::Mesh &mesh, u64 id_salt) { f32 *out = deltas.data() + t * verts * 9; for (size_t v = 0; v < verts; ++v, out += 9) { if (target.position_deltas.size() >= (v + 1) * 3) { - std::memcpy(out, &target.position_deltas[v * 3], sizeof(f32) * 3); + base::MemCopy(out, &target.position_deltas[v * 3], sizeof(f32) * 3); } if (target.normal_deltas.size() >= (v + 1) * 3) { - std::memcpy(out + 3, &target.normal_deltas[v * 3], sizeof(f32) * 3); + base::MemCopy(out + 3, &target.normal_deltas[v * 3], sizeof(f32) * 3); } if (target.tangent_deltas.size() >= (v + 1) * 3) { - std::memcpy(out + 6, &target.tangent_deltas[v * 3], sizeof(f32) * 3); + base::MemCopy(out + 6, &target.tangent_deltas[v * 3], sizeof(f32) * 3); } } } @@ -2023,7 +2031,7 @@ bool Renderer::UploadMesh(const asset::Mesh &mesh, u64 id_salt) { GpuLod glod; glod.vertex_offset = vertex_bases[i]; build_submeshes(src->lods[i], index_bases[i], glod.submeshes); - gpu.lods.push_back(std::move(glod)); + gpu.lods.push_back(base::move(glod)); } gpu.all_blend = true; bool all_water = !gpu.submeshes.empty(); @@ -2038,15 +2046,15 @@ bool Renderer::UploadMesh(const asset::Mesh &mesh, u64 id_salt) { f32 min_y = lod.vertices[0].position[1], max_y = min_y; f32 min_z = lod.vertices[0].position[2], max_z = min_z; for (const asset::Vertex &vertex : lod.vertices) { - min_x = std::min(min_x, vertex.position[0]); - max_x = std::max(max_x, vertex.position[0]); - min_y = std::min(min_y, vertex.position[1]); - max_y = std::max(max_y, vertex.position[1]); - min_z = std::min(min_z, vertex.position[2]); - max_z = std::max(max_z, vertex.position[2]); - } - const f32 horizontal_extent = std::max(max_x - min_x, max_z - min_z); - const f32 planar_tolerance = std::max(0.02f, horizontal_extent * 1e-4f); + min_x = rx::Min(min_x, vertex.position[0]); + max_x = rx::Max(max_x, vertex.position[0]); + min_y = rx::Min(min_y, vertex.position[1]); + max_y = rx::Max(max_y, vertex.position[1]); + min_z = rx::Min(min_z, vertex.position[2]); + max_z = rx::Max(max_z, vertex.position[2]); + } + const f32 horizontal_extent = rx::Max(max_x - min_x, max_z - min_z); + const f32 planar_tolerance = rx::Max(0.02f, horizontal_extent * 1e-4f); gpu.planar_water = horizontal_extent > 1.0f && max_y - min_y <= planar_tolerance; if (gpu.planar_water) { @@ -2057,7 +2065,7 @@ bool Renderer::UploadMesh(const asset::Mesh &mesh, u64 id_salt) { gpu.water_height = (min_y + max_y) * 0.5f; } } - std::memcpy(gpu.bounds_center, mesh.bounds_center, sizeof(f32) * 3); + base::MemCopy(gpu.bounds_center, mesh.bounds_center, sizeof(f32) * 3); gpu.bounds_radius = mesh.bounds_radius; gpu.no_rt = mesh.exclude_from_rt; gpu.terrain_lod = mesh.terrain_lod; @@ -2222,7 +2230,7 @@ bool Renderer::UploadMesh(const asset::Mesh &mesh, u64 id_salt) { all_verts, all_indices, submesh, material_system_->material_base_alpha(submesh.material)) : 1.0f; - const f32 s = std::sqrt(std::clamp(opacity, 0.02f, 1.0f)); + const f32 s = ::sqrt(rx::Clamp(opacity, 0.02f, 1.0f)); const u32 base_index = static_cast(approx_indices.size()); for (u32 e = 0; e + 3 <= submesh.index_count; e += 3) { const u32 idx[3] = {all_indices[submesh.index_offset + e], @@ -2294,7 +2302,7 @@ bool Renderer::UploadMesh(const asset::Mesh &mesh, u64 id_salt) { if (GpuMesh *previous = meshes_.find(mesh_key)) { device_->WaitIdle(); // uploads happen at load time; never per frame skinned_rt_.InvalidateMesh( - *device_, raytracing_.get(), mesh_key, + *device_, raytracing_.Get_UseOnlyIfYouKnowWhatYouareDoing(), mesh_key, retired_bindless_meshes_[(frame_index_ + 1) % kFramesInFlight]); if (raytracing_) { raytracing_->RemoveBlas(mesh_key); @@ -2333,7 +2341,7 @@ bool Renderer::UploadMesh(const asset::Mesh &mesh, u64 id_salt) { meshes_[mesh_key] = gpu; instances_.RefreshMesh(*device_, mesh_key, gpu.bounds_center, gpu.bounds_radius, - SupportsStaticInstances(gpu, material_system_.get()) && + SupportsStaticInstances(gpu, material_system_.Get_UseOnlyIfYouKnowWhatYouareDoing()) && mesh.emitters.empty()); // NIF particle emitters ride along with the mesh; every placed draw of it // feeds a cpu pool (see emitter_sim_ in BuildFrameGraph). @@ -2396,7 +2404,7 @@ bool Renderer::UploadMesh(const asset::Mesh &mesh, u64 id_salt) { if (material_system_) { MaterialSystem::MaterialColor mc = material_system_->material_color(sm.material); - u64 w = std::max(sm.index_count, 1); + u64 w = rx::Max(sm.index_count, 1); for (int k = 0; k < 3; ++k) { albedo[k] += mc.albedo[k] * static_cast(w); emissive[k] += mc.emissive[k] * static_cast(w); @@ -2478,7 +2486,7 @@ bool Renderer::UpdateDynamicMesh(const asset::Mesh &mesh, u64 id_salt) { raytracing_->RemoveBlasDeferred(key); GpuBuffer previous = gpu->vertices; gpu->vertices = replacement; - std::memcpy(gpu->bounds_center, mesh.bounds_center, + base::MemCopy(gpu->bounds_center, mesh.bounds_center, sizeof(gpu->bounds_center)); gpu->bounds_radius = mesh.bounds_radius; device_->DestroyBufferDeferred(previous); @@ -2500,7 +2508,7 @@ bool Renderer::UpdateDynamicMesh(const asset::Mesh &mesh, u64 id_salt) { } instances_.RefreshMesh( *device_, mesh.id.hash ^ id_salt, gpu->bounds_center, gpu->bounds_radius, - SupportsStaticInstances(*gpu, material_system_.get()) && + SupportsStaticInstances(*gpu, material_system_.Get_UseOnlyIfYouKnowWhatYouareDoing()) && mesh.emitters.empty()); ++scene_revision_; return true; @@ -2602,7 +2610,7 @@ bool Renderer::RemoveDynamicMesh(asset::AssetId mesh, u64 id_salt) { } meshes_.erase(key); skinned_rt_.InvalidateMesh( - *device_, raytracing_.get(), key, + *device_, raytracing_.Get_UseOnlyIfYouKnowWhatYouareDoing(), key, retired_bindless_meshes_[(frame_index_ + 1) % kFramesInFlight]); ++scene_revision_; return true; @@ -2696,7 +2704,7 @@ u32 Renderer::AcquireSkinnedRt() { return skinned_rt_.Acquire(); } void Renderer::ReleaseSkinnedRt(u32 actor) { if (!device_) return; - skinned_rt_.Release(*device_, raytracing_.get(), actor, + skinned_rt_.Release(*device_, raytracing_.Get_UseOnlyIfYouKnowWhatYouareDoing(), actor, retired_bindless_meshes_[(frame_index_ + 1) % kFramesInFlight]); } @@ -2900,7 +2908,7 @@ void Renderer::RenderFrame(const FrameView &view) { PassContext ctx; ctx.cmd = cmd; - ctx.device = device_.get(); + ctx.device = device_.Get_UseOnlyIfYouKnowWhatYouareDoing(); ctx.graph = &graph_; // With per-pass detail off the whole frame gets one bracket so // gpu_frame_ms() (dynamic resolution's input) stays fed. @@ -2915,8 +2923,8 @@ void Renderer::RenderFrame(const FrameView &view) { const bool sequence_due = !seq_prefix_.empty() && seq_written_ < seq_count_ && time_seconds_ >= seq_at_; bool dump_due = false; - if (const char *dump = std::getenv("RX_FRAMEGEN_DUMP")) { - u64 dump_frame = std::strtoull(dump, nullptr, 10); + if (const char *dump = ::getenv("RX_FRAMEGEN_DUMP")) { + u64 dump_frame = ::strtoull(dump, nullptr, 10); dump_due = fg_frame && (frame_index_ == dump_frame || frame_index_ == dump_frame + 1); } @@ -2949,7 +2957,7 @@ void Renderer::RenderFrame(const FrameView &view) { { TextureBarrier to_read = Transition(backbuffer, ResourceState::kPresent, ResourceState::kShaderReadCompute); - final_cmd->TextureBarriers({&to_read, 1}); + final_cmd->TextureBarriers(base::Span(&to_read, 1)); } FrameGenInputs fin; fin.backbuffer = &backbuffer; @@ -2983,7 +2991,7 @@ void Renderer::RenderFrame(const FrameView &view) { if (view.hud_draw || view.ui_draw) { ColorAttachment ui_color{.view = target.view, .load = LoadOp::kLoad}; final_cmd->BeginRendering( - {.extent = target.extent, .colors = {&ui_color, 1}}); + {.extent = target.extent, .colors = base::Span(&ui_color, 1)}); if (view.hud_draw) view.hud_draw(*final_cmd); if (view.ui_draw) @@ -3019,8 +3027,8 @@ void Renderer::RenderFrame(const FrameView &view) { // Debug: RX_FRAMEGEN_DUMP= writes real frame N, the interpolated // N->N+1 midpoint and real frame N+1 as pngs in the working directory. - if (const char *dump = std::getenv("RX_FRAMEGEN_DUMP")) { - u64 dump_frame = std::strtoull(dump, nullptr, 10); + if (const char *dump = ::getenv("RX_FRAMEGEN_DUMP")) { + u64 dump_frame = ::strtoull(dump, nullptr, 10); if (frame_index_ == dump_frame && capture_ready) { DumpFgImage(capture_image_, ResourceState::kCopySrc, true, "fg_dump_real0.png"); @@ -3069,7 +3077,7 @@ void Renderer::RenderFrame(const FrameView &view) { if (sequence_due) { if (seq_frame_ctr_ % seq_stride_ == 0 && capture_ready) { char path[512]; - std::snprintf(path, sizeof(path), "%s_%04d.png", seq_prefix_.c_str(), + ::snprintf(path, sizeof(path), "%s_%04d.png", seq_prefix_.c_str(), seq_written_); WriteBackbufferPng(path); ++seq_written_; @@ -3152,7 +3160,7 @@ namespace { // right*x + up*y). Unlisted characters draw nothing. void AppendGlyphBillboard(char c, f32 ox, f32 oy, f32 scale, const Vec3 &origin, const Vec3 &right, const Vec3 &up, u32 rgba, - std::vector &out) { + base::Vector &out) { auto seg = [&](f32 x0, f32 y0, f32 x1, f32 y1) { out.push_back({origin + right * ((ox + x0) * scale) + up * ((oy + y0) * scale), origin + right * ((ox + x1) * scale) + up * ((oy + y1) * scale), rgba}); @@ -3204,13 +3212,13 @@ void AppendGlyphBillboard(char c, f32 ox, f32 oy, f32 scale, const Vec3 &origin, } void TessellateWorldText(const WorldText &t, const Vec3 &right, const Vec3 &up, - std::vector &out) { + base::Vector &out) { const f32 scale = t.size / 6.0f; const f32 advance = 5.0f; // grid units per glyph cell const f32 line_h = 8.0f; // grid units per line size_t line_index = 0; size_t start = 0; - const std::string &s = t.text; + const base::String &s = t.text; for (size_t i = 0; i <= s.size(); ++i) { if (i != s.size() && s[i] != '\n') continue; @@ -3240,7 +3248,7 @@ void Renderer::DrawDebugLines(CommandList &cmd, const FrameView &view, text_right = Vec3{1, 0, 0}; text_right = Normalize(text_right); const Vec3 text_up = Normalize(Cross(text_right, Normalize(view.camera.target - view.camera.eye))); - std::vector text_depth, text_overlay; + base::Vector text_depth, text_overlay; for (const WorldText &t : view.world_texts) TessellateWorldText(t, text_right, text_up, t.overlay ? text_overlay : text_depth); @@ -3324,9 +3332,9 @@ void Renderer::RequestPick(u32 x, u32 y) { pick_y_ = y; } -std::optional Renderer::TakePickResult() { +base::Optional Renderer::TakePickResult() { if (!pick_result_ready_) - return std::nullopt; + return base::nullopt; pick_result_ready_ = false; return PickResult{pick_result_id_}; } @@ -3399,7 +3407,7 @@ void Renderer::RenderPickPass(const FrameView &view) { DepthAttachment depth{ .view = pick_depth_image_.view, .load = LoadOp::kClear, .clear = 0.0f}; cmd.BeginRendering({.extent = {render_width_, render_height_}, - .colors = {&color, 1}, + .colors = base::Span(&color, 1), .depth = &depth}); cmd.SetViewport(0, 0, static_cast(render_width_), static_cast(render_height_)); @@ -3424,7 +3432,7 @@ void Renderer::RenderPickPass(const FrameView &view) { // Read back the whole id target and sample the requested pixel (in output // pixels, scaled to render resolution). - std::vector pixels(static_cast(render_width_) * render_height_); + base::Vector pixels(static_cast(render_width_) * render_height_); if (!device_->ReadbackImage(pick_id_image_, ResourceState::kColorTarget, pixels.data(), pixels.size() * sizeof(u32))) { return; @@ -3757,7 +3765,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, : ms_dummy_hiz_.view; items.push_back(Bind::SampledView(2, hiz)); } - device_->UpdateBindingSet(globals_set, {items.data(), items.size()}); + device_->UpdateBindingSet(globals_set, base::Span(items.data(), items.size())); }; // Water + transparency over an opaque base. A lambda (rather than inline) so @@ -3779,10 +3787,11 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, ResourceHandle sun_shadow, ResourceHandle shadow_atlas, bool csm_on, u32 shadow_slot, u32 water_tlas_slot, bool globals_written, ResourceHandle rcgi_irr = kInvalidResource) -> ResourceHandle { - std::sort(transparent.begin(), transparent.end(), - [](const TransparentDraw &a, const TransparentDraw &b) { - return a.distance_sq > b.distance_sq; - }); + // Stable: equal distances keep submission order. + rx::StableSort(transparent.data(), transparent.data() + transparent.size(), + [](const TransparentDraw &a, const TransparentDraw &b) { + return a.distance_sq > b.distance_sq; + }); // Transparency renders into a copy of the opaque result and refracts by // sampling the original, which never returns to attachment layout @@ -3829,7 +3838,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, water_tlas_slot, use_rt_frag, ddgi_active, water_pipeline_active, csm_on, shadow_slot, shadow_atlas, globals_set, globals_written, update_globals_set, frame_slot, adaptive_water_item, - adaptive_water_submesh, transparent = std::move(transparent), rcgi_irr, + adaptive_water_submesh, transparent = base::move(transparent), rcgi_irr, rcgi_world, &frame, &view](PassContext &ctx) { if (!globals_written) { update_globals_set(ctx, kInvalidResource, false, @@ -3906,14 +3915,14 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, if (const GpuMesh *adaptive_mesh = meshes_.find(adaptive_water_item->mesh)) { const f32 aspect = static_cast(render_width_) / - static_cast(std::max(render_height_, 1u)); + static_cast(rx::Max(render_height_, 1u)); Mat4 vp = PerspectiveReversedZ(view.camera.fov_y, aspect, 0.1f) * LookAt(view.camera.eye, view.camera.target, {0, 1, 0}); Vec3 camera_local = TransformPoint( Inverse(adaptive_water_item->transform), view.camera.eye); AdaptiveWaterMesh::UpdateParams params; params.local_to_clip = vp * adaptive_water_item->transform; - std::copy_n(adaptive_mesh->water_bounds, 4, params.bounds); + for (int k = 0; k < 4; ++k) params.bounds[k] = adaptive_mesh->water_bounds[k]; params.camera_local = camera_local; params.height = adaptive_mesh->water_height; params.time = static_cast(time_seconds_); @@ -3934,7 +3943,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, DepthAttachment depth_attachment{.view = ctx.graph->image(depth).view, .load = LoadOp::kLoad}; ctx.cmd->BeginRendering({.extent = {render_width_, render_height_}, - .colors = {colors, 2}, + .colors = base::Span(colors, 2), .depth = &depth_attachment}); // Effect-shader fire/glows use the additive blend pipeline; the same @@ -4068,10 +4077,10 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, Vec3 wc{m[0] * c[0] + m[4] * c[1] + m[8] * c[2] + m[12], m[1] * c[0] + m[5] * c[1] + m[9] * c[2] + m[13], m[2] * c[0] + m[6] * c[1] + m[10] * c[2] + m[14]}; - f32 sx = std::sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]); - f32 sy = std::sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6]); - f32 sz = std::sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]); - f32 radius = mesh->bounds_radius * std::max(sx, std::max(sy, sz)); + f32 sx = ::sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]); + f32 sy = ::sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6]); + f32 sz = ::sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]); + f32 radius = mesh->bounds_radius * rx::Max(sx, rx::Max(sy, sz)); if (radius > 0.0f && SphereOutsideFrustum(touch_planes, wc, radius)) continue; for (const GpuSubmesh &submesh : mesh->submeshes) { @@ -4103,7 +4112,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, // FSR-style phase count grows with the scale factor squared. f32 scale = static_cast(output_width_) / static_cast(render_width_); - sample_count = static_cast(std::ceil(8.0f * scale * scale)); + sample_count = static_cast(::ceil(8.0f * scale * scale)); } JitterSequence::Sample(frame_index_, sample_count, &jitter_x, &jitter_y); } @@ -4215,9 +4224,9 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, // Dynamic point lights: copy into the host-visible frame buffer (capped). u32 light_count = - std::min(static_cast(view.lights.size()), kMaxFrameLights); + rx::Min(static_cast(view.lights.size()), kMaxFrameLights); if (light_count > 0) { - std::memcpy(frame.lights.mapped, view.lights.data(), + base::MemCopy(frame.lights.mapped, view.lights.data(), light_count * sizeof(PointLight)); } // Active lightning strike: append the positioned flash light after the copy @@ -4239,9 +4248,9 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, local_shadows_active_ = local_shadows_.face_count() > 0; } u32 decal_count = - std::min(static_cast(view.decals.size()), kMaxFrameDecals); + rx::Min(static_cast(view.decals.size()), kMaxFrameDecals); if (decal_count > 0) { - std::memcpy(frame.decals.mapped, view.decals.data(), + base::MemCopy(frame.decals.mapped, view.decals.data(), decal_count * sizeof(Decal)); } globals.light_count = light_count; @@ -4332,8 +4341,8 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, if (settings_.skin_dynamics) { constexpr f32 kTwoPi = 6.28318530718f; f32 phase = static_cast(time_seconds_) * settings_.skin_heart_rate * kTwoPi; - globals.skin_dynamics[0] = std::fmod(phase, kTwoPi); - globals.skin_dynamics[1] = std::clamp(settings_.skin_perfusion, -0.5f, 0.5f); + globals.skin_dynamics[0] = ::fmod(phase, kTwoPi); + globals.skin_dynamics[1] = rx::Clamp(settings_.skin_perfusion, -0.5f, 0.5f); globals.skin_dynamics[2] = settings_.skin_pulse_amplitude; globals.skin_dynamics[3] = settings_.skin_tension_gain; globals.flags |= kFrameFlagSkinDynamics; @@ -4349,21 +4358,21 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, { // Froxel slicing: exponential view-z between the near plane and 500 m. constexpr f32 kNear = 0.1f, kFar = 500.0f; - f32 scale = static_cast(kClusterSlices) / std::log2(kFar / kNear); + f32 scale = static_cast(kClusterSlices) / ::log2(kFar / kNear); globals.cluster_params[0] = scale; - globals.cluster_params[1] = -std::log2(kNear) * scale; + globals.cluster_params[1] = -::log2(kNear) * scale; globals.cluster_params[2] = static_cast(render_width_) / static_cast(kClusterTilesX); globals.cluster_params[3] = static_cast(render_height_) / static_cast(kClusterTilesY); } - std::memcpy(frame.globals.mapped, &globals, sizeof(globals)); + base::MemCopy(frame.globals.mapped, &globals, sizeof(globals)); prev_view_proj_ = view_proj; has_prev_frame_ = true; // Skinning palette for every skinned draw this frame, read by device address. if (!view.bone_matrices.empty() && frame.bone_palette.mapped) { - u32 count = std::min(static_cast(view.bone_matrices.size()), + u32 count = rx::Min(static_cast(view.bone_matrices.size()), kMaxFrameBones); #ifndef NDEBUG // The skinning path blends raw upper-3x3 blocks and carries normals with @@ -4374,11 +4383,11 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, if (!warned_anisotropic_bone) { for (u32 i = 0; i < count && !warned_anisotropic_bone; ++i) { const f32* m = view.bone_matrices[i].m; - const f32 sx = std::sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]); - const f32 sy = std::sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6]); - const f32 sz = std::sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]); - const f32 lo = std::min({sx, sy, sz}); - const f32 hi = std::max({sx, sy, sz}); + const f32 sx = ::sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]); + const f32 sy = ::sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6]); + const f32 sz = ::sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]); + const f32 lo = rx::Min({sx, sy, sz}); + const f32 hi = rx::Max({sx, sy, sz}); if (lo > 1e-6f && hi > lo * 1.01f) { RX_WARN( "bone {} carries anisotropic scale ({:.3f}/{:.3f}/{:.3f}); skinned " @@ -4389,7 +4398,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, } } #endif - std::memcpy(frame.bone_palette.mapped, view.bone_matrices.data(), + base::MemCopy(frame.bone_palette.mapped, view.bone_matrices.data(), count * sizeof(Mat4)); } // Last frame's poses, in the palette's second half. Written every frame like @@ -4398,18 +4407,18 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, // PrevSkinOffset) and never reads this range at all. prev_bone_base_ = 0; if (!view.prev_bone_matrices.empty() && frame.bone_palette.mapped) { - const u32 count = std::min(static_cast(view.prev_bone_matrices.size()), + const u32 count = rx::Min(static_cast(view.prev_bone_matrices.size()), kMaxFrameBones); prev_bone_base_ = kMaxFrameBones; - std::memcpy(static_cast(frame.bone_palette.mapped) + + base::MemCopy(static_cast(frame.bone_palette.mapped) + static_cast(prev_bone_base_) * sizeof(Mat4), view.prev_bone_matrices.data(), count * sizeof(Mat4)); } // Active morph target weights for every morphed draw, read by device address. if (!view.morph_weights.empty() && frame.morph_weights.mapped) { - u32 count = std::min(static_cast(view.morph_weights.size()), + u32 count = rx::Min(static_cast(view.morph_weights.size()), kMaxFrameMorphWeights); - std::memcpy(frame.morph_weights.mapped, view.morph_weights.data(), + base::MemCopy(frame.morph_weights.mapped, view.morph_weights.data(), count * sizeof(MorphWeight)); } // Per-draw transforms for every pass that walks view.draws this frame. One @@ -4514,7 +4523,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, // Baked decal layers: assign tiles to the receivers on screen and record the // stamps queued for them. Records nothing in a frame where nothing changed. - decal_baker_.AddToGraph(graph_, {decal_targets_.data(), decal_targets_.size()}, + decal_baker_.AddToGraph(graph_, base::Span(decal_targets_.data(), decal_targets_.size()), frame_slot, frame_index_, decal_atlas_view_, decal_normal_atlas_view_); @@ -4571,11 +4580,11 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, f32 night = settings_.night; if (night < 0.0f) { // legacy fallback: infer from the sun's elevation f32 to_sun_y = -applied_sun_direction_.y; - night = std::clamp((0.04f - to_sun_y) / 0.14f, 0.0f, 1.0f); + night = rx::Clamp((0.04f - to_sun_y) / 0.14f, 0.0f, 1.0f); night = night * night * (3.0f - 2.0f * night); } env_aurora = - settings_.weather.aurora_intensity * std::clamp(night, 0.0f, 1.0f); + settings_.weather.aurora_intensity * rx::Clamp(night, 0.0f, 1.0f); } // An active aurora writhes: refresh the cubemap whenever its 0.4 s animation // step ticks over, so the curtains also move in the IBL and reflections. The @@ -4583,8 +4592,8 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, // the aurora off does not leave the last green bake in the IBL forever. constexpr f64 kAuroraBakeStep = 0.4; if (env_aurora > 0.0f && - std::floor(time_seconds_ / kAuroraBakeStep) != - std::floor((time_seconds_ - view.frame_delta_seconds) / + ::floor(time_seconds_ / kAuroraBakeStep) != + ::floor((time_seconds_ - view.frame_delta_seconds) / kAuroraBakeStep)) { environment_dirty_ = true; } @@ -4637,7 +4646,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, const f32 wy = mm[1] * c.x + mm[5] * c.y + mm[9] * c.z + mm[13]; const f32 wz = mm[2] * c.x + mm[6] * c.y + mm[10] * c.z + mm[14]; const f32 dx = wx - eye.x, dy = wy - eye.y, dz = wz - eye.z; - return std::sqrt(dx * dx + dy * dy + dz * dz); + return ::sqrt(dx * dx + dy * dy + dz * dz); }; // Resolves the RT LOD for one instance: LOD0 inside the near radius (raster // and rays agree there, avoiding the self-intersection disparity the AC @@ -4760,7 +4769,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, const base::Vector *visible = group_cull ? &rt_cull_.UpdateGroup( gi, group.generation, group.revision, - {group.transforms.data(), group.transforms.size()}, + base::Span(group.transforms.data(), group.transforms.size()), mesh_center(*mesh), mesh->bounds_radius) : nullptr; for (u32 ii = 0; ii < group.transforms.size(); ++ii) { @@ -4788,7 +4797,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, } if (path_trace) { path_scene_moved = pt_scene_history_.Update( - instances, {view.bone_matrices.data(), view.bone_matrices.size()}); + instances, base::Span(view.bone_matrices.data(), view.bone_matrices.size())); } else { pt_scene_history_ = {}; } @@ -4830,7 +4839,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, b.Async(); // build next frame's slot on the compute queue }, [this, tlas_build_slot, frame_index = frame_index_, - instances = std::move(instances)](PassContext &ctx) { + instances = base::move(instances)](PassContext &ctx) { raytracing_->BuildTlas(*ctx.cmd, tlas_build_slot, frame_index, instances); }); @@ -4875,7 +4884,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, } } sdf_clipmap_->AddComposeToGraph(graph_, *sdf_scene_, - std::move(sdf_instances), view.camera.eye, + base::move(sdf_instances), view.camera.eye, frame_index_); } @@ -4915,10 +4924,10 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, Vec3 rcgi_sun_col = settings_.interior ? settings_.interior_directional_color : applied_sun_color_; - rcgi_->AddToGraph(graph_, raytracing_.get(), tlas_slot, light_grid_, + rcgi_->AddToGraph(graph_, raytracing_.Get_UseOnlyIfYouKnowWhatYouareDoing(), tlas_slot, light_grid_, frame.lights, view.camera.eye, rcgi_sun_dir, rcgi_sun_int, rcgi_sun_col, frame_index_, rcgi_cfg, rcgi_async, - rcgi_software ? sdf_clipmap_.get() : nullptr); + rcgi_software ? sdf_clipmap_.Get_UseOnlyIfYouKnowWhatYouareDoing() : nullptr); } // The path tracer takes over the whole frame: it writes scene_color directly @@ -4937,12 +4946,12 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, pt.sun_radius = settings_.sun_angular_radius; pt.frame_index = frame_index_; bool moved = - std::memcmp(&view_proj, &pt_prev_view_proj_, sizeof(Mat4)) != 0; + base::MemCompare(&view_proj, &pt_prev_view_proj_, sizeof(Mat4)) != 0; bool lit_changed = settings_.sun_intensity != pt_prev_sun_intensity_ || settings_.sun_angular_radius != pt_prev_sun_radius_ || - std::memcmp(&settings_.sun_direction, &pt_prev_sun_direction_, sizeof(Vec3)) != 0 || - std::memcmp(&settings_.sun_color, &pt_prev_sun_color_, sizeof(Vec3)) != 0; + base::MemCompare(&settings_.sun_direction, &pt_prev_sun_direction_, sizeof(Vec3)) != 0 || + base::MemCompare(&settings_.sun_color, &pt_prev_sun_color_, sizeof(Vec3)) != 0; bool scene_changed = scene_revision_ != pt_prev_scene_revision_; bool denoised_path = false; @@ -4979,7 +4988,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, rf.sun_intensity = settings_.sun_intensity; rf.sun_color = settings_.sun_color; rf.sun_radius = settings_.sun_angular_radius; - rf.pixel_spread = 2.0f * std::tan(view.camera.fov_y * 0.5f) / + rf.pixel_spread = 2.0f * ::tan(view.camera.fov_y * 0.5f) / static_cast(render_height_); rf.spp = settings_.path_trace_spp; rf.frame_index = frame_index_; @@ -5041,7 +5050,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, denoised_path = true; pt.spp = settings_.path_trace_spp; // Ray-cone spread for texture lod: vertical fov radians per pixel. - pt.pixel_spread = 2.0f * std::tan(view.camera.fov_y * 0.5f) / + pt.pixel_spread = 2.0f * ::tan(view.camera.fov_y * 0.5f) / static_cast(render_height_); PathTracer::GbufferTargets t; auto guide = [&](const char *name, Format format) { @@ -5198,12 +5207,12 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, mesh->bounds_center[1], mesh->bounds_center[2]}); const f32 *m = item.transform.m; - f32 sx = std::sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]); - f32 sy = std::sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6]); + f32 sx = ::sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]); + f32 sy = ::sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6]); f32 sz = - std::sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]); + ::sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]); f32 wr = - mesh->bounds_radius * std::max(sx, std::max(sy, sz)); + mesh->bounds_radius * rx::Max(sx, rx::Max(sy, sz)); Vec3 d{wc.x - face.light_pos.x, wc.y - face.light_pos.y, wc.z - face.light_pos.z}; f32 reach = face.light_radius + wr; @@ -5349,7 +5358,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, mesh->bounds_center[1], mesh->bounds_center[2]}); Vec3 d = view.camera.eye - wc; - lod = SelectLod(*mesh, std::sqrt(d.x * d.x + d.y * d.y + d.z * d.z)); + lod = SelectLod(*mesh, ::sqrt(d.x * d.x + d.y * d.y + d.z * d.z)); } const base::Vector &lod_subs = lod == 0 ? mesh->submeshes : mesh->lods[lod - 1].submeshes; @@ -5413,11 +5422,11 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, // width clamp. m[5] is negative under the reversed-Z Y-flip projection. grass_frame.pixel_scale = proj.m[5] != 0.0f - ? 2.0f / (std::fabs(proj.m[5]) * static_cast(render_height_)) + ? 2.0f / (::fabs(proj.m[5]) * static_cast(render_height_)) : 0.0f; grass_active = procedural_grass_.Prepare( *view.grass_domain, - {view.grass_interactions.data(), view.grass_interactions.size()}, + base::Span(view.grass_interactions.data(), view.grass_interactions.size()), grass_frame, frame_slot); if (grass_active) procedural_grass_.AddGeneration(graph_, frame_slot); } @@ -5472,15 +5481,15 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, // Cpu frustum skip: a conservative world radius (bounds scaled by the // largest transform axis) lets off-screen instances cost no dispatch. const f32 *m = item.transform.m; - f32 sx = std::sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]); - f32 sy = std::sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6]); - f32 sz = std::sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]); - f32 ms_radius = mesh->bounds_radius * std::max(sx, std::max(sy, sz)); + f32 sx = ::sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]); + f32 sy = ::sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6]); + f32 sz = ::sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]); + f32 ms_radius = mesh->bounds_radius * rx::Max(sx, rx::Max(sy, sz)); if (ms_radius > 0.0f && SphereOutsideFrustum(ms_planes, ms_wc, ms_radius)) continue; u32 ms_lod = - SelectLod(*mesh, std::sqrt(ms_d.x * ms_d.x + ms_d.y * ms_d.y + + SelectLod(*mesh, ::sqrt(ms_d.x * ms_d.x + ms_d.y * ms_d.y + ms_d.z * ms_d.z)); const base::Vector &ms_subs = ms_lod == 0 ? mesh->submeshes : mesh->lods[ms_lod - 1].submeshes; @@ -5529,7 +5538,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, .clear = 0.0f}; // reversed z clears to far = 0 ctx.cmd->BeginRendering( {.extent = {render_width_, render_height_}, - .colors = {colors, 3}, + .colors = base::Span(colors, 3), .depth = &depth_attachment, .shading_rate = vrs_active_ ? vrs_.rate_view() : TextureView{}}); @@ -5576,7 +5585,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, push.draw_index = static_cast(&item - view.draws.data()) + 1u; if (mesh->terrain_lod) { - std::memcpy(push.detail_rect, view.detail_rect, + base::MemCopy(push.detail_rect, view.detail_rect, sizeof(push.detail_rect)); } if (draw_skinned && item.skin_offset >= 0) { @@ -5735,7 +5744,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, WaterField::UpdateParams wf{}; wf.camera_pos = view.camera.eye; wf.time = static_cast(time_seconds_); - wf.dt = std::min(view.frame_delta_seconds, + wf.dt = rx::Min(view.frame_delta_seconds, 1.0f / 30.0f); // clamp for stability wf.frame_slot = frame_slot; wf.fft_ocean = fft_ocean_active; @@ -5767,11 +5776,11 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, if (fluid_sim_active_) { FluidSim::UpdateParams fp{}; fp.domain = view.fluid_domain; - fp.dt = std::min(view.frame_delta_seconds, 1.0f / 30.0f); + fp.dt = rx::Min(view.frame_delta_seconds, 1.0f / 30.0f); fp.frame_slot = frame_slot; fp.sources = view.fluid_sources.data(); fp.source_count = - std::min(static_cast(view.fluid_sources.size()), FluidSim::kMaxSources); + rx::Min(static_cast(view.fluid_sources.size()), FluidSim::kMaxSources); fluid_sim_.AddToGraph(graph_, fp); } @@ -5878,7 +5887,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, view_proj = globals.view_proj, inv_view_proj = globals.inv_view_proj](PassContext &ctx) { const ContactCamera camera{view_proj, inv_view_proj}; - std::memcpy(contact_camera_[frame_slot].mapped, &camera, + base::MemCopy(contact_camera_[frame_slot].mapped, &camera, sizeof(camera)); struct ContactPush { f32 sun_dir[3]; @@ -5963,9 +5972,9 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, p.top = 4200.0f; // Same drift velocity as clouds.cs, so the shadows track the // deck. - p.wind = std::cos(settings_.weather.wind_yaw) * + p.wind = ::cos(settings_.weather.wind_yaw) * settings_.weather.wind_speed; - p.wind_z = std::sin(settings_.weather.wind_yaw) * + p.wind_z = ::sin(settings_.weather.wind_yaw) * settings_.weather.wind_speed; p.strength = 0.75f; ctx.cmd->BindPipeline(cloud_shadow_pipeline_); @@ -6144,10 +6153,10 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, p.near_plane = 0.1f; constexpr f32 kNear = 0.1f, kFar = 500.0f; p.slice_scale = - static_cast(kClusterSlices) / std::log2(kFar / kNear); - p.slice_bias = -std::log2(kNear) * p.slice_scale; + static_cast(kClusterSlices) / ::log2(kFar / kNear); + p.slice_bias = -::log2(kNear) * p.slice_scale; p.light_count = light_count; - p.tan_half_fov_y = std::tan(view.camera.fov_y * 0.5f); + p.tan_half_fov_y = ::tan(view.camera.fov_y * 0.5f); p.aspect = static_cast(render_width_) / static_cast(render_height_); p.decal_count = decal_count; @@ -6299,7 +6308,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, .view = ctx.graph->image(geom_depth).view, .load = LoadOp::kLoad}; // prepass depth, tested EQUAL ctx.cmd->BeginRendering({.extent = {render_width_, render_height_}, - .colors = {colors, 3}, + .colors = base::Span(colors, 3), .depth = &depth_attachment}); BindingSetHandle bindless_set = @@ -6336,7 +6345,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, push.draw_index = static_cast(&item - view.draws.data()) + 1u; if (mesh->terrain_lod) { - std::memcpy(push.detail_rect, view.detail_rect, + base::MemCopy(push.detail_rect, view.detail_rect, sizeof(push.detail_rect)); } if (draw_skinned && item.skin_offset >= 0) { @@ -6477,7 +6486,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, .view = ctx.graph->image(depth).view, .load = LoadOp::kLoad}; ctx.cmd->BeginRendering( {.extent = {render_width_, render_height_}, - .colors = {colors, 3}, + .colors = base::Span(colors, 3), .depth = &depth_attachment}); environment_->DrawSky(*ctx.cmd, globals_set); ctx.cmd->EndRendering(); @@ -6492,7 +6501,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, // so the graph barriers them; the callback opens its own dynamic-rendering // section. auto add_scene_hook = - [&](const std::function &hook, + [&](const base::Function &hook, ScenePhase phase, ResourceHandle color_h, ResourceHandle depth_h, ResourceHandle export_h, ResourceHandle motion_h) { const f32 jx = globals.jitter[0], jy = globals.jitter[1]; @@ -6584,10 +6593,10 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, // Pixels per meter at view depth 1. The projection bakes the vulkan // y-flip into m[5], so it is negative - take the magnitude. p.proj_scale = - std::abs(proj.m[5]) * 0.5f * static_cast(render_height_); + ::abs(proj.m[5]) * 0.5f * static_cast(render_height_); p.max_radius = 24.0f; p.strength = - std::getenv("RX_SSS_DEBUG") ? -1.0f : 1.0f; // <0 = mask debug view + ::getenv("RX_SSS_DEBUG") ? -1.0f : 1.0f; // <0 = mask debug view }; ResourceHandle sss_tmp = graph_.CreateTexture({.name = "sss_tmp", @@ -6692,8 +6701,8 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, settings_.weather.snow ? 0.0f : settings_.weather.precipitation; const f32 live_snow = settings_.weather.snow ? settings_.weather.precipitation : 0.0f; - const f32 surface_wetness = std::max(settings_.weather.wetness, live_rain); - const f32 surface_snow = std::max(settings_.weather.snow_cover, live_snow); + const f32 surface_wetness = rx::Max(settings_.weather.wetness, live_rain); + const f32 surface_snow = rx::Max(settings_.weather.snow_cover, live_snow); if ((surface_wetness > 0.0f || surface_snow > 0.0f) && !path_trace && normals != kInvalidResource) { SurfaceWeather::Frame sf; @@ -6794,9 +6803,9 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, cf.sun_color = settings_.sun_color; cf.coverage = settings_.cloud_coverage; cf.wind_x = - std::cos(settings_.weather.wind_yaw) * settings_.weather.wind_speed; + ::cos(settings_.weather.wind_yaw) * settings_.weather.wind_speed; cf.wind_z = - std::sin(settings_.weather.wind_yaw) * settings_.weather.wind_speed; + ::sin(settings_.weather.wind_yaw) * settings_.weather.wind_speed; lit = clouds_.AddToGraph(graph_, lit, depth_export, {render_width_, render_height_}, cf); } @@ -6852,7 +6861,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, ff.height_falloff = settings_.fog_height_falloff; ff.base_height = settings_.fog_base_height; ff.start_distance = settings_.froxel_start_distance; - std::memcpy(ff.cluster_params, globals.cluster_params, + base::MemCopy(ff.cluster_params, globals.cluster_params, sizeof(ff.cluster_params)); ff.screen_size[0] = static_cast(render_width_); ff.screen_size[1] = static_cast(render_height_); @@ -6887,7 +6896,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, ff.comparison_sampler = environment_->comparison_sampler(); froxel_fog_.AddToGraph(graph_, lit, depth_export, csm_active ? shadow_atlas : kInvalidResource, - raytracing_.get(), tlas_slot, + raytracing_.Get_UseOnlyIfYouKnowWhatYouareDoing(), tlas_slot, {render_width_, render_height_}, ff); } @@ -6921,7 +6930,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, Vec3 sun_col = applied_sun_color_ * applied_sun_intensity_; fur_.AddToGraph(graph_, lit, depth, model, view_proj, applied_sun_direction_, sun_col, - std::max(settings_.ambient, 0.12f), FurPass::Params{}); + rx::Max(settings_.ambient, 0.12f), FurPass::Params{}); } // Order-independent transparency (weighted blended) over the lit scene. @@ -6931,7 +6940,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, oit_light.lights = frame.lights; oit_light.cluster_counts = cluster_counts_; oit_light.cluster_indices = cluster_indices_; - std::memcpy(oit_light.cluster_params, globals.cluster_params, + base::MemCopy(oit_light.cluster_params, globals.cluster_params, sizeof(oit_light.cluster_params)); oit_light.froxel_volume = froxel_fog_.integrated().view; oit_light.froxel_sampler = froxel_fog_.volume_sampler(); @@ -6940,7 +6949,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, oit_light.froxel_enabled = froxel_on; lit = wboit_.AddToGraph(graph_, lit, depth, view.oit, view_proj, applied_sun_direction_, sun_col, - std::max(settings_.ambient, 0.12f), render_width_, + rx::Max(settings_.ambient, 0.12f), render_width_, render_height_, oit_light); } @@ -6963,16 +6972,16 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, vf.sun_direction = applied_sun_direction_; vf.sun_color = applied_sun_color_; vf.sun_intensity = applied_sun_intensity_; - vf.ambient = std::max(settings_.ambient, 0.02f); + vf.ambient = rx::Max(settings_.ambient, 0.02f); vf.time = static_cast(time_seconds_); vf.dt = view.frame_delta_seconds; vf.intensity = settings_.weather.precipitation; vf.snow = settings_.weather.snow; // Wind yaw is the direction the wind blows toward; decompose to xz. vf.wind[0] = - std::cos(settings_.weather.wind_yaw) * settings_.weather.wind_speed; + ::cos(settings_.weather.wind_yaw) * settings_.weather.wind_speed; vf.wind[1] = - std::sin(settings_.weather.wind_yaw) * settings_.weather.wind_speed; + ::sin(settings_.weather.wind_yaw) * settings_.weather.wind_speed; vf.gustiness = settings_.weather.gustiness; vf.lightning = settings_.weather.lightning; vf.jitter[0] = globals.jitter[0]; @@ -6986,7 +6995,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, vf.froxel_sampler = froxel_fog_.volume_sampler(); vf.rt_shadows = precip_rt; // matched to the TLAS build request above precip_volume_.AddToGraph(graph_, lit, depth_export, motion, - raytracing_.get(), tlas_slot, vf); + raytracing_.Get_UseOnlyIfYouKnowWhatYouareDoing(), tlas_slot, vf); precip_volume_drawn = true; } @@ -7020,13 +7029,13 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, pf.sun_direction = settings_.sun_direction; pf.sun_color = settings_.sun_color; pf.sun_intensity = settings_.sun_intensity; - pf.ambient = std::max(settings_.ambient, 0.15f); + pf.ambient = rx::Max(settings_.ambient, 0.15f); pf.near_plane = 0.1f; pf.soft_fade = 0.6f; pf.jitter[0] = globals.jitter[0]; pf.jitter[1] = globals.jitter[1]; // Lit translucency inputs: clustered lights + shadows + the fog volume. - std::memcpy(pf.cluster_params, globals.cluster_params, + base::MemCopy(pf.cluster_params, globals.cluster_params, sizeof(pf.cluster_params)); pf.froxel_near = FroxelFog::kNear; pf.froxel_far = FroxelFog::kFar; @@ -7098,7 +7107,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, view.camera.eye, frame_index_); } // Distant foliage imposters: instanced octahedral billboards with depth. - const char *imposters_env = std::getenv("RX_IMPOSTERS"); + const char *imposters_env = ::getenv("RX_IMPOSTERS"); if (imposters_.active() && (!imposters_env || imposters_env[0] != '0')) { ImposterPass::Frame imf; imf.view_proj = view_proj; @@ -7129,9 +7138,9 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, // Screen pixels per world unit at distance 1 (|proj.m5| carries the // vulkan y-flip, hence the fabs). vf.proj_scale = - std::fabs(proj.m[5]) * static_cast(render_height_) * 0.5f; - vf.proj_m00 = std::fabs(proj.m[0]); - vf.proj_m11 = std::fabs(proj.m[5]); + ::fabs(proj.m[5]) * static_cast(render_height_) * 0.5f; + vf.proj_m00 = ::fabs(proj.m[0]); + vf.proj_m11 = ::fabs(proj.m[5]); vf.error_pixels = VgeoError.get() > 0.0f ? VgeoError.get() : 1.0f; // Reversed-z infinite far: proj m[14] is the near plane distance. vf.near_plane = proj.m[14] > 0.0f ? proj.m[14] : 0.1f; @@ -7139,7 +7148,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, vf.depth = depth; vf.width = render_width_; vf.height = render_height_; - vf.debug = static_cast(std::max(VgeoDebug.get(), 0)); + vf.debug = static_cast(rx::Max(VgeoDebug.get(), 0)); vf.slot = frame_index_; vgeo_.AddToGraph(*device_, graph_, vf); } @@ -7234,7 +7243,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, da = {.view = ctx.graph->image(depth).view, .load = LoadOp::kLoad}; ctx.cmd->BeginRendering({.extent = {render_width_, render_height_}, - .colors = {&ca, 1}, + .colors = base::Span(&ca, 1), .depth = have_depth ? &da : nullptr}); DrawDebugLines(*ctx.cmd, view, view_proj, {render_width_, render_height_}); @@ -7304,7 +7313,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, .view = ctx.graph->image(pt_depth).view, .clear = 0.0f}; // reversed z clears to far = 0 ctx.cmd->BeginRendering({.extent = {render_width_, render_height_}, - .colors = {colors, 3}, + .colors = base::Span(colors, 3), .depth = &depth_attachment}); environment_->WriteEnvSet( @@ -7330,7 +7339,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, MeshPushConstants push{}; push.draw_index = static_cast(&item - view.draws.data()) + 1u; if (mesh->terrain_lod) { - std::memcpy(push.detail_rect, view.detail_rect, + base::MemCopy(push.detail_rect, view.detail_rect, sizeof(push.detail_rect)); } if (draw_skinned && item.skin_offset >= 0) { @@ -7527,7 +7536,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, .load = LoadOp::kDontCare, .store = StoreOp::kStore}; ctx.cmd->BeginRendering( - {.extent = {post_width, post_height}, .colors = {©, 1}}); + {.extent = {post_width, post_height}, .colors = base::Span(©, 1)}); ctx.cmd->BindPipeline(hdr_overlay_copy_pipeline_); ctx.cmd->BindTransient( 0, {Bind::Combined(0, source.view, hdr_overlay_sampler_)}); @@ -7539,7 +7548,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, [overlay_target](RenderGraph::PassBuilder& builder) { builder.Write(overlay_target, ResourceUsage::kColorAttachment); }, - [this, overlay = std::move(overlay), overlay_target, post_width, + [this, overlay = base::move(overlay), overlay_target, post_width, post_height](PassContext& ctx) { const GpuImage& color = ctx.graph->image(overlay_target); HdrOverlayContext hc; @@ -7721,7 +7730,7 @@ void Renderer::BuildFrameGraph(FrameResources &frame, u32 image_index, : SamplerHandle{}; ctx.cmd->BeginRendering( {.extent = ctx.graph->image(backbuffer).extent, - .colors = {&color, 1}}); + .colors = base::Span(&color, 1)}); if (view.hud_draw) view.hud_draw(*ctx.cmd); if (view.ui_draw) @@ -7845,7 +7854,7 @@ void Renderer::RecreateSwapchain() { if (width == 0 || height == 0) return; // minimized device_->WaitIdle(); - swapchain_.reset(); + swapchain_.Reset(); swapchain_starved_ = false; // a fresh swapchain is worth probing again swapchain_hdr_request_ = WantHdrSwapchain(); swapchain_ = device_->CreateSwapchain(width, height, settings_.vsync, @@ -7860,7 +7869,7 @@ void Renderer::RecreateSwapchain() { // and dropping a working upscale to taa on a window resize is not something // anyone would connect back to the resize. if (upscaler_) { - upscaler_.reset(); + upscaler_.Reset(); if (!CreateUpscalerWithFallback()) { settings_.upscaler = UpscalerKind::kNone; settings_.aa_mode = AntiAliasingMode::kTaa; @@ -7868,7 +7877,7 @@ void Renderer::RecreateSwapchain() { } } // The frame generator is sized for the swapchain; lazily recreated. - framegen_.reset(); + framegen_.Reset(); framegen_attempted_ = false; framegen_was_active_ = false; UpdateRenderResolution(); @@ -7887,7 +7896,7 @@ void Renderer::DestroySurface() { if (!device_ || device_->is_stub()) return; device_->WaitIdle(); - swapchain_.reset(); + swapchain_.Reset(); device_->DestroySurface(); } @@ -7939,8 +7948,8 @@ void Renderer::Shutdown() { light_grid_.Destroy(*device_); // rcgi_ (unique_ptr) frees itself // Free SDF GPU resources while the device is still valid (the unique_ptr // destructors call DestroyImage/DestroyBuffer/DestroyPipeline). - sdf_clipmap_.reset(); - sdf_scene_.reset(); + sdf_clipmap_.Reset(); + sdf_scene_.Reset(); device_->DestroyPipeline(hdr_pipeline_); hdr_pipeline_ = {}; device_->DestroyBuffer(hdr_readback_); @@ -8043,27 +8052,27 @@ void Renderer::Shutdown() { precip_volume_.Destroy(*device_); lightning_.Destroy(*device_); surface_weather_.Destroy(*device_); - water_.reset(); - fluid_surface_.reset(); - ddgi_.reset(); - rcgi_.reset(); // owns GPU resources through device_; destroy before device + water_.Reset(); + fluid_surface_.Reset(); + ddgi_.Reset(); + rcgi_.Reset(); // owns GPU resources through device_; destroy before device // teardown - environment_.reset(); - material_system_.reset(); - bindless_.reset(); - transient_pool_.reset(); + environment_.Reset(); + material_system_.Reset(); + bindless_.Reset(); + transient_pool_.Reset(); } graph_.Reset(); if (capture_image_.handle) device_->DestroyImage(capture_image_); // before device_ goes away - post_.reset(); - ui_blur_.reset(); // holds a Device& + backend handles; destroy before device_ - mesh_pipeline_.reset(); - swapchain_.reset(); - framegen_.reset(); // ffx contexts destroy through the device - upscaler_.reset(); - raytracing_.reset(); - device_.reset(); + post_.Reset(); + ui_blur_.Reset(); // holds a Device& + backend handles; destroy before device_ + mesh_pipeline_.Reset(); + swapchain_.Reset(); + framegen_.Reset(); // ffx contexts destroy through the device + upscaler_.Reset(); + raytracing_.Reset(); + device_.Reset(); } void Renderer::LogTextureMemory() const { diff --git a/engine/render/core/renderer.h b/engine/render/core/renderer.h index 4651db00..4ed8f95b 100644 --- a/engine/render/core/renderer.h +++ b/engine/render/core/renderer.h @@ -1,17 +1,18 @@ #ifndef RX_RENDER_RENDERER_H_ #define RX_RENDER_RENDERER_H_ -#include -#include -#include -#include -#include -#include #include #include #include "asset/mesh.h" +#include "base/containers/span.h" +#include "base/containers/vector.h" +#include "base/functional/function.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/optional.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/math.h" #include "core/window.h" @@ -104,7 +105,7 @@ namespace rx::render { struct VulkanDeviceExtras { // Enabled if the adapter advertises them; the granted set is reported in // Renderer::caps()->extra_extensions. - base::Vector extensions; + base::Vector extensions; }; struct RendererDesc { @@ -289,7 +290,7 @@ struct DebugLine { // blank. Filled per frame into FrameView::world_texts. No font atlas or asset. struct WorldText { Vec3 position{}; // world anchor - std::string text; // label content ('\n' = new line) + base::String text; // label content ('\n' = new line) f32 size = 1.0f; // glyph height, world units u32 rgba = 0xffffffff; // packed 0xRRGGBBAA f32 align = 0.5f; // per-line horizontal anchor: 0 left, 0.5 centre, 1 right @@ -383,8 +384,8 @@ struct FrameView { // Recorded inside the final ui pass with the backbuffer bound as the // color attachment. hud_draw (the libultragui HUD/menu) records first, then // ui_draw (the debug ImGui overlay) on top. - std::function hud_draw; - std::function ui_draw; + base::Function hud_draw; + base::Function ui_draw; // App-provided GPU passes recorded into the scene, depth-interleaved with // rx's own geometry (a game with its own GPU-driven pipeline: compute cull, @@ -394,26 +395,26 @@ struct FrameView { // render- graph pass (so barriers are handled) and only when set, on the // Vulkan backend. Zero cost when unset: no pass is added. See // SceneHookContext. - std::function scene_opaque; - std::function scene_transparent; + base::Function scene_opaque; + base::Function scene_transparent; // Color-only resolved HDR content. Unlike the scene hooks this runs after // temporal/depth-aware effects, making it suitable for sprites that should // remain crisp without producing motion vectors. Exposure, bloom and // tonemapping still run afterward. - std::function hdr_overlay; + base::Function hdr_overlay; // Debug line lists for this frame (non-owning; valid for the RenderFrame // call). debug_lines are depth-tested against the resolved scene depth; // overlay lines draw on top. Both are drawn just before the UI pass. Empty = // no line pass. - std::span debug_lines; - std::span debug_lines_overlay; + base::Span debug_lines; + base::Span debug_lines_overlay; // World-space text labels for this frame, drawn as camera-facing stroke-font // billboards in the debug-line pass. Empty = no text. Owned here (unlike the // debug-line spans) so callers can build them from temporaries. - std::vector world_texts; + base::Vector world_texts; // Backdrop blur: when a frosted (backdrop-blur) widget is present, the UI // sets needs_blur so the renderer captures + blurs the backbuffer before the @@ -430,16 +431,16 @@ struct FrameView { // frame. New container members must be added to the move-dance here. void Clear() { FrameView fresh; - fresh.draws = std::move(draws); - fresh.decals = std::move(decals); - fresh.decal_stamps = std::move(decal_stamps); - fresh.lights = std::move(lights); - fresh.bone_matrices = std::move(bone_matrices); - fresh.prev_bone_matrices = std::move(prev_bone_matrices); - fresh.particles = std::move(particles); - fresh.grass_interactions = std::move(grass_interactions); - fresh.oit = std::move(oit); - fresh.gaussians = std::move(gaussians); + fresh.draws = base::move(draws); + fresh.decals = base::move(decals); + fresh.decal_stamps = base::move(decal_stamps); + fresh.lights = base::move(lights); + fresh.bone_matrices = base::move(bone_matrices); + fresh.prev_bone_matrices = base::move(prev_bone_matrices); + fresh.particles = base::move(particles); + fresh.grass_interactions = base::move(grass_interactions); + fresh.oit = base::move(oit); + fresh.gaussians = base::move(gaussians); fresh.draws.clear(); fresh.decals.clear(); fresh.decal_stamps.clear(); @@ -450,7 +451,7 @@ struct FrameView { fresh.grass_interactions.clear(); fresh.oit.clear(); fresh.gaussians.clear(); - *this = std::move(fresh); + *this = base::move(fresh); } }; @@ -481,7 +482,7 @@ class RX_RENDER_EXPORT Renderer { // Saves the next presented frame as png. Also armed by the // RX_SCREENSHOT env var ("path.png:seconds") for headless captures. - void CaptureScreenshot(const std::string &path); + void CaptureScreenshot(const base::String &path); // Editor picking. RequestPick arms an entity-id pass for the next frame that // rasterizes the opaque draw list into an R32_UINT target and reads back the @@ -489,7 +490,7 @@ class RX_RENDER_EXPORT Renderer { // (1-2 frames later); poll TakePickResult, which returns and clears the // pending result when it is ready. pick_id 0 means background/unpickable. void RequestPick(u32 x, u32 y); - std::optional TakePickResult(); + base::Optional TakePickResult(); // Makes a mesh drawable, keyed by its asset id. Materials referenced by // submeshes should be uploaded first. No-op without a device. id_salt @@ -524,9 +525,9 @@ class RX_RENDER_EXPORT Renderer { // Updates preserve object motion by treating overlapping array indices as // stable identities; newly appended indices spawn with zero object velocity. InstanceGroupHandle CreateInstanceGroup(u64 mesh, - std::span transforms); + base::Span transforms); bool UpdateInstanceGroup(InstanceGroupHandle handle, - std::span transforms); + base::Span transforms); void DestroyInstanceGroup(InstanceGroupHandle handle); // Same per-domain salt as UploadMesh; it must match so a mesh's submesh // material references resolve to this domain's materials/textures. @@ -544,7 +545,7 @@ class RX_RENDER_EXPORT Renderer { void UploadVirtualGeometryMesh(const asset::Mesh &mesh); // World transforms the virtual-geometry mesh draws with (default: one // identity instance). The gpu culls every cluster of every instance. - void SetVirtualGeometryInstances(std::span transforms); + void SetVirtualGeometryInstances(base::Span transforms); // Planar world-xz-projected albedo for the virtual-geometry resolve: a full // RGBA8 mip chain (size x size at mip 0, levels concatenated). void SetVirtualGeometryAlbedo(ByteSpan rgba_mips, u32 size, f32 world_to_uv); @@ -553,7 +554,7 @@ class RX_RENDER_EXPORT Renderer { // probes and gather samples indoor/outdoor against these and refuses to blend // across the boundary, killing the outdoor-probe-through-a-doorway leak. Cheap; // forward every frame or on change. Empty span disables classification. - void SetInteriorVolumes(std::span volumes); + void SetInteriorVolumes(base::Span volumes); // Seeds simulated hair strands on a head sphere (--demo strands). void SeedHairStrands(const Vec3 &head_center, f32 head_radius, u32 strands, f32 length); @@ -586,7 +587,7 @@ class RX_RENDER_EXPORT Renderer { u32 BakeImposter(const asset::Mesh &mesh); // The distant instances drawn as billboards. Replaces the previous set, so // a game re-splitting near/far as the camera moves calls this again. - void SetImposterInstances(std::span instances); + void SetImposterInstances(base::Span instances); // Live tunables. Mutate freely; RenderFrame diffs against the applied // state and reconfigures, including full upscaler swaps. @@ -645,7 +646,7 @@ class RX_RENDER_EXPORT Renderer { const DeviceCaps *caps() const; bool raytracing_available() const { return rt_available_; } - Device *device() { return device_.get(); } + Device *device() { return device_.Get_UseOnlyIfYouKnowWhatYouareDoing(); } Format swapchain_format() const; u32 swapchain_image_count() const; u32 render_width() const { return render_width_; } @@ -664,7 +665,7 @@ class RX_RENDER_EXPORT Renderer { u32 mesh_count() const { return static_cast(meshes_.size()); } size_t instance_group_count() const { return instances_.group_count(); } size_t instance_count() const { return instances_.instance_count(); } - const MaterialSystem *materials() const { return material_system_.get(); } + const MaterialSystem *materials() const { return material_system_.Get_UseOnlyIfYouKnowWhatYouareDoing(); } // One line of resident material-texture memory and what import-time // compression did to get there. Called after a --shot capture; a headless // run has no overlay, and this is the number a memory change is judged on. @@ -794,15 +795,15 @@ class RX_RENDER_EXPORT Renderer { // The HDR request the current swapchain was built with; when WantHdrSwapchain // diverges (OS toggle flipped, setting changed) the frame loop rebuilds. bool swapchain_hdr_request_ = false; - std::unique_ptr device_; - std::unique_ptr swapchain_; - std::unique_ptr transient_pool_; - std::unique_ptr bindless_; + base::UniquePointer device_; + base::UniquePointer swapchain_; + base::UniquePointer transient_pool_; + base::UniquePointer bindless_; base::Vector retired_bindless_meshes_[kFramesInFlight]; - std::unique_ptr material_system_; - std::unique_ptr environment_; - std::unique_ptr ddgi_; - std::unique_ptr + base::UniquePointer material_system_; + base::UniquePointer environment_; + base::UniquePointer ddgi_; + base::UniquePointer rcgi_; // idTech8-style radiance-cached GI (RX_RCGI), lazily created bool rcgi_create_failed_ = false; // lazy creation failed once; do not retry bool rcgi_sw_unavailable_logged_ = @@ -822,13 +823,13 @@ class RX_RENDER_EXPORT Renderer { // quality preset can never turn the seeded software path off (see // RendererDesc::software_gi). bool sdf_available_ = false; - std::unique_ptr sdf_scene_; - std::unique_ptr sdf_clipmap_; - std::unique_ptr water_; - std::unique_ptr fluid_surface_; - std::unique_ptr mesh_pipeline_; - std::unique_ptr post_; - std::unique_ptr + base::UniquePointer sdf_scene_; + base::UniquePointer sdf_clipmap_; + base::UniquePointer water_; + base::UniquePointer fluid_surface_; + base::UniquePointer mesh_pipeline_; + base::UniquePointer post_; + base::UniquePointer ui_blur_; // frosted-glass backdrop blur for the UI base::UnorderedMap meshes_; FrameResources frames_[kFramesInFlight]; @@ -839,11 +840,11 @@ class RX_RENDER_EXPORT Renderer { BindingSetHandle env_scene_sets_[kFramesInFlight]; BindingSetHandle env_transparent_sets_[kFramesInFlight]; BindingSetHandle env_prepass_sets_[kFramesInFlight]; // dummies + ocean maps - std::unique_ptr upscaler_; + base::UniquePointer upscaler_; // FSR3 frame generation (RX_FRAMEGEN): lazily created when the FSR3 // upscaler is active (its dilated guides are reused); the present-rate // counters feed the periodic log line. - std::unique_ptr framegen_; + base::UniquePointer framegen_; bool framegen_attempted_ = false; bool framegen_was_active_ = false; bool fg_active_frame_ = @@ -869,7 +870,7 @@ class RX_RENDER_EXPORT Renderer { // Allocates capture_image_ at the swapchain's format/extent on first use. // False if the image cannot be created, so the caller skips the frame. bool EnsureCaptureImage(); - std::unique_ptr raytracing_; + base::UniquePointer raytracing_; // Compute skinning + refittable per-actor BLASes for skinned draws that carry // a DrawItem::rt_skin handle. Inert (no pipeline) without ray tracing. SkinnedRayTracing skinned_rt_; @@ -1045,18 +1046,18 @@ class RX_RENDER_EXPORT Renderer { GpuImage pick_depth_image_; // D32, render resolution u32 pick_image_w_ = 0, pick_image_h_ = 0; - void WriteBackbufferPng(const std::string &path); + void WriteBackbufferPng(const base::String &path); void WriteScreenshot(); void DumpFgImage(const GpuImage &image, ResourceState state, bool bgra, const char *path); void WriteHdr(); // reads back the captured linear hdr buffer to a .hdr file - std::string screenshot_path_; + base::String screenshot_path_; f64 screenshot_at_ = -1; // seconds; <0 means immediately when armed // Frame-burst capture (RX_SEQ=prefix:startsec:count[:stride]) for stitching // an animation clip from the inbuilt framebuffer capture. - std::string seq_prefix_; + base::String seq_prefix_; f64 seq_at_ = -1; int seq_count_ = 0; int seq_written_ = 0; @@ -1064,7 +1065,7 @@ class RX_RENDER_EXPORT Renderer { int seq_frame_ctr_ = 0; // Linear-hdr frame export (radiance .hdr). RX_HDR=[:seconds]. - std::string hdr_path_; + base::String hdr_path_; f64 hdr_at_ = -1; bool hdr_pending_ = false; // the copy pass ran this frame; read it back after submit diff --git a/engine/render/core/settings_ini.cc b/engine/render/core/settings_ini.cc index af4f7f30..fe0637d8 100644 --- a/engine/render/core/settings_ini.cc +++ b/engine/render/core/settings_ini.cc @@ -1,25 +1,26 @@ +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "base/containers/unordered_map.h" +#include "core/file_system.h" +#include "core/text_reader.h" +#include "core/text_writer.h" #include "render/core/settings_ini.h" -#include -#include -#include -#include -#include -#include -#include +#include +#include namespace rx::render { namespace { -std::string Trim(std::string_view sv) { +base::String Trim(base::StringRef sv) { size_t b = 0, e = sv.size(); - while (b < e && std::isspace(static_cast(sv[b]))) ++b; - while (e > b && std::isspace(static_cast(sv[e - 1]))) --e; - return std::string(sv.substr(b, e - b)); + while (b < e && ::isspace(static_cast(sv[b]))) ++b; + while (e > b && ::isspace(static_cast(sv[e - 1]))) --e; + return base::String(sv.substr(b, e - b)); } -std::string Lower(std::string s) { - for (char& c : s) c = static_cast(std::tolower(static_cast(c))); +base::String Lower(base::String s) { + for (char& c : s) c = static_cast(::tolower(static_cast(c))); return s; } @@ -34,7 +35,7 @@ const char* Name(AntiAliasingMode m) { } return "taa"; } -bool Parse(const std::string& v, AntiAliasingMode& out) { +bool Parse(const base::String& v, AntiAliasingMode& out) { if (v == "none") out = AntiAliasingMode::kNone; else if (v == "taa") out = AntiAliasingMode::kTaa; else if (v == "upscaler") out = AntiAliasingMode::kUpscaler; @@ -52,7 +53,7 @@ const char* Name(UpscalerKind k) { } return "none"; } -bool Parse(const std::string& v, UpscalerKind& out) { +bool Parse(const base::String& v, UpscalerKind& out) { if (v == "none") out = UpscalerKind::kNone; else if (v == "fsr3" || v == "fsr") out = UpscalerKind::kFsr3; else if (v == "dlss") out = UpscalerKind::kDlss; @@ -70,7 +71,7 @@ const char* Name(UpscalerQuality q) { } return "quality"; } -bool Parse(const std::string& v, UpscalerQuality& out) { +bool Parse(const base::String& v, UpscalerQuality& out) { if (v == "native" || v == "nativeaa" || v == "dlaa") out = UpscalerQuality::kNativeAa; else if (v == "quality") out = UpscalerQuality::kQuality; else if (v == "balanced") out = UpscalerQuality::kBalanced; @@ -88,7 +89,7 @@ const char* Name(HumanQualityTier t) { return "hero"; } -bool Parse(const std::string& v, HumanQualityTier& out) { +bool Parse(const base::String& v, HumanQualityTier& out) { if (v == "hero") out = HumanQualityTier::kHero; else if (v == "standard") out = HumanQualityTier::kStandard; else if (v == "distant") out = HumanQualityTier::kDistant; @@ -105,7 +106,7 @@ const char* Name(TonemapOperator t) { } return "aces"; } -bool Parse(const std::string& v, TonemapOperator& out) { +bool Parse(const base::String& v, TonemapOperator& out) { if (v == "aces") out = TonemapOperator::kAces; else if (v == "reinhard") out = TonemapOperator::kReinhard; else if (v == "none") out = TonemapOperator::kNone; @@ -118,9 +119,8 @@ const char* Bool(bool b) { return b ? "true" : "false"; } } // namespace -std::string SettingsToIni(const RenderSettings& s) { - std::ostringstream o; - o.imbue(std::locale::classic()); +base::String SettingsToIni(const RenderSettings& s) { + TextWriter o; o << "# Rx render preset. Editable. Load/save it from the debug ui\n" << "# (Renderer panel -> Platform preset). Unlisted keys keep their value.\n\n"; @@ -242,56 +242,55 @@ std::string SettingsToIni(const RenderSettings& s) { o << "adaptation_speed = " << s.adaptation_speed << "\n"; o << "exposure = " << s.exposure << "\n"; o << "tonemap = " << Name(s.tonemap) << "\n"; - return o.str(); + return o.Take(); } -int ApplyIni(std::string_view text, RenderSettings& s) { +int ApplyIni(base::StringRef text, RenderSettings& s) { // Collect "key = value" pairs (lowercased keys), ignoring sections/comments. - std::map kv; - std::istringstream in{std::string(text)}; - std::string line; - while (std::getline(in, line)) { - if (auto hash = line.find_first_of(";#"); hash != std::string::npos) line.resize(hash); - std::string t = Trim(line); - if (t.empty() || t.front() == '[') continue; + base::UnorderedMap kv; + LineReader lines(text); + base::StringRef piece; + while (lines.Next(&piece)) { + base::String line(piece.data(), piece.size()); + if (auto hash = line.find_first_of(";#"); hash != base::String::npos) line.resize(hash); + base::String t = Trim(line); + if (t.empty() || t[0] == '[') continue; auto eq = t.find('='); - if (eq == std::string::npos) continue; - kv[Lower(Trim(std::string_view(t).substr(0, eq)))] = Trim(std::string_view(t).substr(eq + 1)); + if (eq == base::String::npos) continue; + kv[Lower(Trim(base::StringRef(t).substr(0, eq)))] = Trim(base::StringRef(t).substr(eq + 1)); } if (kv.empty()) return 0; int applied = 0; auto take = [&](const char* key, auto&& fn) { - auto it = kv.find(key); - if (it != kv.end() && fn(it->second)) ++applied; + const base::String* value = kv.find(key); + if (value && fn(*value)) ++applied; }; - auto as_bool = [](const std::string& v, bool& out) { - const std::string l = Lower(v); + auto as_bool = [](const base::String& v, bool& out) { + const base::String l = Lower(v); if (l == "true" || l == "1" || l == "on" || l == "yes") { out = true; return true; } if (l == "false" || l == "0" || l == "off" || l == "no") { out = false; return true; } return false; }; - auto as_f32 = [](const std::string& v, f32& out) { + auto as_f32 = [](const base::String& v, f32& out) { f32 parsed = 0.0f; - auto [end, ec] = std::from_chars(v.data(), v.data() + v.size(), parsed); - if (ec != std::errc{} || end != v.data() + v.size() || !std::isfinite(parsed)) return false; + if (!ParseWholeF32(v, &parsed) || !::isfinite(parsed)) return false; out = parsed; return true; }; - auto as_u32 = [](const std::string& v, u32& out) { - if (v.empty() || v.front() == '-') return false; + auto as_u32 = [](const base::String& v, u32& out) { + if (v.empty() || v[0] == '-') return false; u32 parsed = 0; - auto [end, ec] = std::from_chars(v.data(), v.data() + v.size(), parsed); - if (ec != std::errc{} || end != v.data() + v.size()) return false; + if (!ParseWholeU32(v, &parsed)) return false; out = parsed; return true; }; - auto b = [&](const char* k, bool& f) { take(k, [&](const std::string& v) { return as_bool(v, f); }); }; - auto fl = [&](const char* k, f32& f) { take(k, [&](const std::string& v) { return as_f32(v, f); }); }; - auto u = [&](const char* k, u32& f) { take(k, [&](const std::string& v) { return as_u32(v, f); }); }; + auto b = [&](const char* k, bool& f) { take(k, [&](const base::String& v) { return as_bool(v, f); }); }; + auto fl = [&](const char* k, f32& f) { take(k, [&](const base::String& v) { return as_f32(v, f); }); }; + auto u = [&](const char* k, u32& f) { take(k, [&](const base::String& v) { return as_u32(v, f); }); }; auto en = [&](const char* k, auto& f) { - take(k, [&](const std::string& v) { return Parse(Lower(v), f); }); + take(k, [&](const base::String& v) { return Parse(Lower(v), f); }); }; en("aa_mode", s.aa_mode); @@ -400,20 +399,15 @@ int ApplyIni(std::string_view text, RenderSettings& s) { return applied; } -bool LoadSettingsIni(const std::filesystem::path& path, RenderSettings& s) { - std::ifstream f(path, std::ios::binary); - if (!f) return false; - std::ostringstream buf; - buf << f.rdbuf(); - ApplyIni(buf.str(), s); +bool LoadSettingsIni(base::StringRef path, RenderSettings& s) { + base::String text; + if (!fs::ReadTextFile(path, &text)) return false; + ApplyIni(text, s); return true; } -bool SaveSettingsIni(const std::filesystem::path& path, const RenderSettings& s) { - std::ofstream f(path, std::ios::binary | std::ios::trunc); - if (!f) return false; - f << SettingsToIni(s); - return f.good(); +bool SaveSettingsIni(base::StringRef path, const RenderSettings& s) { + return fs::WriteTextFile(path, SettingsToIni(s)); } } // namespace rx::render diff --git a/engine/render/core/settings_ini.h b/engine/render/core/settings_ini.h index b2df69a9..7de964ed 100644 --- a/engine/render/core/settings_ini.h +++ b/engine/render/core/settings_ini.h @@ -1,10 +1,8 @@ #ifndef RX_RENDER_SETTINGS_INI_H_ #define RX_RENDER_SETTINGS_INI_H_ -#include -#include -#include - +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "render/core/settings.h" @@ -26,19 +24,19 @@ namespace rx::render { // the excluded fields keep their incoming value. // Serializes the covered fields of `s` to INI text. -RX_RENDER_EXPORT std::string SettingsToIni(const RenderSettings& s); +RX_RENDER_EXPORT base::String SettingsToIni(const RenderSettings& s); // Overlays recognized "key = value" lines from `text` onto `s`. Section headers, // blank lines and ; / # comments are ignored; unknown keys are skipped. Returns // the number of keys applied. -RX_RENDER_EXPORT int ApplyIni(std::string_view text, RenderSettings& s); +RX_RENDER_EXPORT int ApplyIni(base::StringRef text, RenderSettings& s); // Reads a preset file and overlays it onto `s` (see ApplyIni). False if the file // cannot be opened. -RX_RENDER_EXPORT bool LoadSettingsIni(const std::filesystem::path& path, RenderSettings& s); +RX_RENDER_EXPORT bool LoadSettingsIni(base::StringRef path, RenderSettings& s); // Writes SettingsToIni(s) to `path`. False on a write error. -RX_RENDER_EXPORT bool SaveSettingsIni(const std::filesystem::path& path, const RenderSettings& s); +RX_RENDER_EXPORT bool SaveSettingsIni(base::StringRef path, const RenderSettings& s); } // namespace rx::render diff --git a/engine/render/d3d12/d3d12_backend.h b/engine/render/d3d12/d3d12_backend.h index a05bc716..28ca0514 100644 --- a/engine/render/d3d12/d3d12_backend.h +++ b/engine/render/d3d12/d3d12_backend.h @@ -11,11 +11,13 @@ #include "render/d3d12/d3d12_headers.h" -#include #include #include +#include "base/containers/span.h" +#include "base/functional/function.h" +#include "base/memory/unique_pointer.h" #include "render/rhi/device.h" #include "render/rhi/swapchain.h" @@ -220,7 +222,7 @@ class D3D12CommandList final : public CommandList { void BindPipeline(PipelineHandle pipeline) override; void BindSet(u32 set_index, BindingSetHandle set) override; - void BindTransient(u32 set_index, std::span items) override; + void BindTransient(u32 set_index, base::Span items) override; void PushConstants(const void* data, u32 size, u32 offset) override; void Dispatch(u32 x, u32 y, u32 z) override; void DispatchIndirect(const GpuBuffer& args, u64 offset) override; @@ -243,10 +245,10 @@ class D3D12CommandList final : public CommandList { u32 stride) override; void DrawMeshTasksIndirectCount(const GpuBuffer& args, u64 offset, const GpuBuffer& count_buffer, u64 count_offset, u32 max_draws, u32 stride) override; - void TextureBarriers(std::span barriers) override; + void TextureBarriers(base::Span barriers) override; void MemoryBarrier(BarrierScope src, BarrierScope dst) override; void CopyBufferToTexture(const GpuBuffer& src, const GpuImage& dst, - std::span regions) override; + base::Span regions) override; void CopyTextureToBuffer(const GpuImage& src, const GpuBuffer& dst, const BufferTextureCopy& region) override; void CopyBuffer(const GpuBuffer& src, u64 src_offset, const GpuBuffer& dst, u64 dst_offset, @@ -320,7 +322,7 @@ class D3D12CommandList final : public CommandList { // no-op, and the whole frame graph plus RX_UI_SHOT readback runs unchanged. class D3D12Swapchain final : public Swapchain { public: - static std::unique_ptr Create(D3D12Device& device, u32 width, u32 height, + static base::UniquePointer Create(D3D12Device& device, u32 width, u32 height, bool vsync); ~D3D12Swapchain() override; @@ -354,7 +356,7 @@ class D3D12Device final : public Device { public: // `window` may be null: an offscreen device has no HWND and never creates a // swapchain (frames complete through the swapchainless SubmitFrame overload). - static std::unique_ptr Create(const DeviceDesc& desc, Window* window); + static base::UniquePointer Create(const DeviceDesc& desc, Window* window); ~D3D12Device() override; void WaitIdle() override; @@ -362,7 +364,7 @@ class D3D12Device final : public Device { void DestroySurface() override {} // `hdr` is accepted but not yet honored: the offscreen Linux swapchain has // no display pipe, and the DXGI path needs CheckColorSpaceSupport wiring. - std::unique_ptr CreateSwapchain(u32 width, u32 height, bool vsync, + base::UniquePointer CreateSwapchain(u32 width, u32 height, bool vsync, bool hdr) override; MemoryBudget memory_budget() const override; @@ -393,7 +395,7 @@ class D3D12Device final : public Device { void DestroyBindingLayout(BindingLayoutHandle layout) override; BindingSetHandle CreateBindingSet(BindingLayoutHandle layout, u32 variable_count) override; void DestroyBindingSet(BindingSetHandle set) override; - void UpdateBindingSet(BindingSetHandle set, std::span items) override; + void UpdateBindingSet(BindingSetHandle set, base::Span items) override; AccelSizes GetBlasSizes(const BlasBuildDesc& desc) override; AccelSizes GetTlasSizes(u32 instance_count) override; @@ -408,7 +410,7 @@ class D3D12Device final : public Device { void DestroyTimestampPool(TimestampPoolHandle pool) override; bool GetTimestamps(TimestampPoolHandle pool, u32 first, u32 count, u64* out) override; - void ImmediateSubmit(const std::function& record) override; + void ImmediateSubmit(const base::Function& record) override; CommandList* BeginFrame(u32 slot) override; PresentResult SubmitFrame(CommandList* cmd, Swapchain& swapchain, u32 image_index) override; void SubmitFrame(CommandList* cmd) override; @@ -472,7 +474,7 @@ class D3D12Device final : public Device { CpuDescriptorPool& dsv_pool() { return dsv_pool_; } SetLayout* GetOrCreateSetLayout(const BindingLayoutDesc& desc); - ID3D12RootSignature* GetOrCreateRootSignature(std::span sets, u32 push_size, + ID3D12RootSignature* GetOrCreateRootSignature(base::Span sets, u32 push_size, bool push_root_constants, PipelineRecord* out_params); ID3D12CommandSignature* GetDrawIndexedSignature(u32 stride); @@ -545,7 +547,7 @@ class D3D12Device final : public Device { ID3D12Resource* push_ring = nullptr; u8* push_mapped = nullptr; u64 push_cursor = 0; - std::unique_ptr wrapper; + base::UniquePointer wrapper; base::Vector deferred; // Public frame-safe deferred destruction: whole records (resource + views) // retired here are freed once this ring's fence proves the GPU is done, diff --git a/engine/render/d3d12/d3d12_command_list.cc b/engine/render/d3d12/d3d12_command_list.cc index 5c0cdceb..adf86c91 100644 --- a/engine/render/d3d12/d3d12_command_list.cc +++ b/engine/render/d3d12/d3d12_command_list.cc @@ -6,10 +6,13 @@ #include "render/d3d12/d3d12_backend.h" -#include -#include +#include +#include "base/algorithm.h" +#include "base/containers/span.h" +#include "base/memory/mem_ops.h" #include "core/log.h" +#include "core/scalar.h" namespace rx::render::d3d12 { namespace { @@ -37,7 +40,7 @@ constexpr u32 kPlacementAlign = 512; // D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT void D3D12CommandList::OnBeginRecording() { bound_ = nullptr; - std::memset(push_shadow_, 0, sizeof(push_shadow_)); + base::MemSet(push_shadow_, 0, sizeof(push_shadow_)); for (PendingVertexBuffer& vb : pending_vb_) vb = {}; vb_dirty_ = false; buffer_states_.clear(); @@ -142,7 +145,7 @@ void D3D12CommandList::BindSet(u32 set_index, BindingSetHandle set) { } } -void D3D12CommandList::BindTransient(u32 set_index, std::span items) { +void D3D12CommandList::BindTransient(u32 set_index, base::Span items) { if (!bound_ || set_index >= bound_->sets.size()) return; const PipelineRecord::SetParams& params = bound_->sets[set_index]; SetLayout* layout = params.layout; @@ -205,8 +208,8 @@ void D3D12CommandList::BindTransient(u32 set_index, std::span void D3D12CommandList::PushConstants(const void* data, u32 size, u32 offset) { if (!bound_ || bound_->push_param < 0) return; if (offset + size > sizeof(push_shadow_)) return; - std::memcpy(push_shadow_ + offset, data, size); - u32 block = std::min(bound_->push_size, sizeof(push_shadow_)); + base::MemCopy(push_shadow_ + offset, data, size); + u32 block = rx::Min(bound_->push_size, sizeof(push_shadow_)); if (bound_->push_root_constants) { if (bound_->compute) { list_->SetComputeRoot32BitConstants(bound_->push_param, block / 4, push_shadow_, 0); @@ -225,7 +228,7 @@ void D3D12CommandList::PushConstants(const void* data, u32 size, u32 offset) { auto bind_push_address = [&](i32 param, u32 byte_offset) { if (param < 0 || offset > byte_offset || offset + size < byte_offset + 8) return; u64 address = 0; - std::memcpy(&address, push_shadow_ + byte_offset, sizeof(address)); + base::MemCopy(&address, push_shadow_ + byte_offset, sizeof(address)); if (address == 0) return; if (bound_->compute) { list_->SetComputeRootShaderResourceView(param, address); @@ -240,9 +243,9 @@ void D3D12CommandList::PushConstants(const void* data, u32 size, u32 offset) { void D3D12CommandList::SetPushRootCbv() { void* cpu = nullptr; - u32 block = std::min(bound_->push_size, sizeof(push_shadow_)); + u32 block = rx::Min(bound_->push_size, sizeof(push_shadow_)); u64 va = device_.AllocPushSlice(ring_, block, &cpu); - std::memcpy(cpu, push_shadow_, block); + base::MemCopy(cpu, push_shadow_, block); if (bound_->compute) { list_->SetComputeRootConstantBufferView(bound_->push_param, va); } else { @@ -435,7 +438,7 @@ void D3D12CommandList::DrawMeshTasksIndirectCount(const GpuBuffer& args, u64 off // synchronization -void D3D12CommandList::TextureBarriers(std::span barriers) { +void D3D12CommandList::TextureBarriers(base::Span barriers) { for (const TextureBarrier& barrier : barriers) { TextureRecord* texture = Rec(barrier.texture); if (!texture) continue; @@ -468,7 +471,7 @@ void D3D12CommandList::MemoryBarrier(BarrierScope src, BarrierScope dst) { // transfer void D3D12CommandList::CopyBufferToTexture(const GpuBuffer& src, const GpuImage& dst, - std::span regions) { + base::Span regions) { TextureRecord* texture = Rec(dst.handle); BufferRecord* buffer = Rec(src.handle); @@ -478,9 +481,9 @@ void D3D12CommandList::CopyBufferToTexture(const GpuBuffer& src, const GpuImage& bool aligned = true; for (const BufferTextureCopy& region : regions) { u32 width = region.extent.width ? region.extent.width - : std::max(dst.extent.width >> region.mip, 1u); + : rx::Max(dst.extent.width >> region.mip, 1u); u32 height = region.extent.height ? region.extent.height - : std::max(dst.extent.height >> region.mip, 1u); + : rx::Max(dst.extent.height >> region.mip, 1u); RowInfo rows = RowInfoOf(dst.format, width, height); if ((rows.row_bytes % kRowPitchAlign) != 0 || (region.buffer_offset % kPlacementAlign) != 0) { aligned = false; @@ -503,9 +506,9 @@ void D3D12CommandList::CopyBufferToTexture(const GpuBuffer& src, const GpuImage& for (size_t i = 0; i < regions.size(); ++i) { const BufferTextureCopy& region = regions[i]; u32 width = region.extent.width ? region.extent.width - : std::max(dst.extent.width >> region.mip, 1u); + : rx::Max(dst.extent.width >> region.mip, 1u); u32 height = region.extent.height ? region.extent.height - : std::max(dst.extent.height >> region.mip, 1u); + : rx::Max(dst.extent.height >> region.mip, 1u); RowInfo rows = RowInfoOf(dst.format, width, height); u64 pitch = (rows.row_bytes + kRowPitchAlign - 1) & ~static_cast(kRowPitchAlign - 1); total = (total + kPlacementAlign - 1) & ~static_cast(kPlacementAlign - 1); @@ -538,16 +541,16 @@ void D3D12CommandList::CopyBufferToTexture(const GpuBuffer& src, const GpuImage& for (size_t i = 0; i < regions.size(); ++i) { const BufferTextureCopy& region = regions[i]; u32 width = region.extent.width ? region.extent.width - : std::max(dst.extent.width >> region.mip, 1u); + : rx::Max(dst.extent.width >> region.mip, 1u); u32 height = region.extent.height ? region.extent.height - : std::max(dst.extent.height >> region.mip, 1u); + : rx::Max(dst.extent.height >> region.mip, 1u); RowInfo rows = RowInfoOf(dst.format, width, height); u64 pitch = (rows.row_bytes + kRowPitchAlign - 1) & ~static_cast(kRowPitchAlign - 1); for (u32 row = 0; row < rows.row_count; ++row) { const u64 src_offset = region.buffer_offset + static_cast(row) * rows.row_bytes; const u64 dst_offset = staged_offsets[i] + row * pitch; if (mapped) - std::memcpy(out + dst_offset, mapped + src_offset, rows.row_bytes); + base::MemCopy(out + dst_offset, mapped + src_offset, rows.row_bytes); else list_->CopyBufferRegion(staging, dst_offset, buffer->resource, src_offset, rows.row_bytes); @@ -573,9 +576,9 @@ void D3D12CommandList::CopyBufferToTexture(const GpuBuffer& src, const GpuImage& for (size_t i = 0; i < regions.size(); ++i) { const BufferTextureCopy& region = regions[i]; u32 width = region.extent.width ? region.extent.width - : std::max(dst.extent.width >> region.mip, 1u); + : rx::Max(dst.extent.width >> region.mip, 1u); u32 height = region.extent.height ? region.extent.height - : std::max(dst.extent.height >> region.mip, 1u); + : rx::Max(dst.extent.height >> region.mip, 1u); RowInfo rows = RowInfoOf(dst.format, width, height); u64 pitch = aligned ? rows.row_bytes @@ -604,9 +607,9 @@ void D3D12CommandList::CopyTextureToBuffer(const GpuImage& src, const GpuBuffer& TextureRecord* texture = Rec(src.handle); BufferRecord* buffer = Rec(dst.handle); u32 width = region.extent.width ? region.extent.width - : std::max(src.extent.width >> region.mip, 1u); + : rx::Max(src.extent.width >> region.mip, 1u); u32 height = region.extent.height ? region.extent.height - : std::max(src.extent.height >> region.mip, 1u); + : rx::Max(src.extent.height >> region.mip, 1u); RowInfo rows = RowInfoOf(src.format, width, height); bool aligned = @@ -708,11 +711,11 @@ void D3D12CommandList::BlitMip(const GpuImage& image, u32 src_mip, Extent2D src_ color.load = LoadOp::kDontCare; RenderingInfo info; info.extent = dst_extent; - info.colors = std::span(&color, 1); + info.colors = base::Span(&color, 1); BeginRendering(info); BindPipeline(blit); BindingItem blit_src = Bind::Combined(0, src_view, device_.blit_sampler()); - BindTransient(0, std::span(&blit_src, 1)); + BindTransient(0, base::Span(&blit_src, 1)); Draw(3, 1, 0, 0); EndRendering(); @@ -909,7 +912,7 @@ void D3D12CommandList::QueryCompactedSizes(AccelCompactionQueryHandle query, list_->ResourceBarrier(1, &to_copy); list_->CopyBufferRegion(record->readback, 0, record->gpu, 0, static_cast(count) * sizeof(u64)); - std::swap(to_copy.Transition.StateBefore, to_copy.Transition.StateAfter); + base::Swap(to_copy.Transition.StateBefore, to_copy.Transition.StateAfter); list_->ResourceBarrier(1, &to_copy); // Readiness = this list's ring fence reaching the value CloseAndExecute @@ -947,7 +950,7 @@ void D3D12CommandList::WriteTimestamp(TimestampPoolHandle pool, u32 index, bool u64 key = reinterpret_cast(record); u32* max = timestamp_max_.find(key); if (max) { - *max = std::max(*max, index + 1); + *max = rx::Max(*max, index + 1); } else { timestamp_max_.insert(key, index + 1); } diff --git a/engine/render/d3d12/d3d12_device.cc b/engine/render/d3d12/d3d12_device.cc index 759a75f7..c9923210 100644 --- a/engine/render/d3d12/d3d12_device.cc +++ b/engine/render/d3d12/d3d12_device.cc @@ -8,14 +8,20 @@ #define INITGUID #include "render/d3d12/d3d12_backend.h" -#include -#include -#include +#include #include "core/log.h" // Internal blit shaders (BlitMip lowers to a fullscreen draw; D3D12 has no // filtered copy). Embedded by the build like every pass shader. +#include "base/algorithm.h" +#include "base/containers/span.h" +#include "base/functional/function.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" +#include "core/scalar.h" #include "shaders/fullscreen_vs_slang.h" #include "shaders/blit_ps_slang.h" @@ -92,7 +98,7 @@ void SafeRelease(T*& p) { // pipeline desc's attributes (sorted by location) pair 1:1 with the // signature's non-system-value elements (sorted by register). struct SignatureElement { - std::string name; + base::String name; u32 semantic_index = 0; u32 reg = 0; }; @@ -101,34 +107,34 @@ bool ParseInputSignature(const void* dxil, size_t size, base::Vector(dxil); auto rd = [&](size_t offset) -> u32 { u32 v; - std::memcpy(&v, bytes + offset, 4); + base::MemCopy(&v, bytes + offset, 4); return v; }; - if (size < 32 || std::memcmp(bytes, "DXBC", 4) != 0) return false; + if (size < 32 || base::MemCompare(bytes, "DXBC", 4) != 0) return false; u32 part_count = rd(28); for (u32 p = 0; p < part_count; ++p) { u32 part_offset = rd(32 + p * 4); if (part_offset + 8 > size) return false; - if (std::memcmp(bytes + part_offset, "ISG1", 4) != 0) continue; + if (base::MemCompare(bytes + part_offset, "ISG1", 4) != 0) continue; const u8* data = bytes + part_offset + 8; u32 count = 0, header = 0; - std::memcpy(&count, data, 4); - std::memcpy(&header, data + 4, 4); + base::MemCopy(&count, data, 4); + base::MemCopy(&header, data + 4, 4); const u8* e = data + header; // header dwords include count+size for (u32 i = 0; i < count; ++i, e += 32) { // ISG1 element: stream, name_offset, semantic_index, sysval, // component_type, register, mask/rw_mask, min_precision (8 dwords). u32 name_offset, semantic_index, sysval, reg; - std::memcpy(&name_offset, e + 4, 4); - std::memcpy(&semantic_index, e + 8, 4); - std::memcpy(&sysval, e + 12, 4); - std::memcpy(®, e + 20, 4); + base::MemCopy(&name_offset, e + 4, 4); + base::MemCopy(&semantic_index, e + 8, 4); + base::MemCopy(&sysval, e + 12, 4); + base::MemCopy(®, e + 20, 4); if (sysval != 0) continue; // SV_VertexID etc: not vertex-buffer fed SignatureElement element; element.name = reinterpret_cast(data + name_offset); element.semantic_index = semantic_index; element.reg = reg; - out->push_back(std::move(element)); + out->push_back(base::move(element)); } return true; } @@ -242,8 +248,8 @@ void CpuDescriptorPool::Free(u32 index) { free_.push_back(index); } // device creation -std::unique_ptr D3D12Device::Create(const DeviceDesc& desc, Window* window) { - auto device = std::unique_ptr(new D3D12Device()); +base::UniquePointer D3D12Device::Create(const DeviceDesc& desc, Window* window) { + auto device = base::UniquePointer(new D3D12Device()); // Unused on linux (offscreen swapchain). Null for an offscreen device, and // for any window backend that has no OS handle to give. device->platform_window_ = window ? window->native_handles().platform_window : nullptr; @@ -337,11 +343,11 @@ std::unique_ptr D3D12Device::Create(const DeviceDesc& desc, Window* wind return device; } -std::unique_ptr CreateD3D12Device(const DeviceDesc& desc, Window& window) { +base::UniquePointer CreateD3D12Device(const DeviceDesc& desc, Window& window) { return D3D12Device::Create(desc, &window); } -std::unique_ptr CreateD3D12DeviceOffscreen(const DeviceDesc& desc) { +base::UniquePointer CreateD3D12DeviceOffscreen(const DeviceDesc& desc) { return D3D12Device::Create(desc, nullptr); } @@ -428,7 +434,7 @@ bool D3D12Device::InitResources() { void* mapped = nullptr; ring.push_ring->Map(0, nullptr, &mapped); ring.push_mapped = static_cast(mapped); - ring.wrapper = std::make_unique(*this, ring.list, i); + ring.wrapper = base::MakeUnique(*this, ring.list, i); } return true; } @@ -442,7 +448,7 @@ void D3D12Device::ShutdownResources() { SafeRelease(ring.fence); SafeRelease(ring.list); SafeRelease(ring.alloc); - ring.wrapper.reset(); + ring.wrapper.Reset(); } for (auto entry : blit_pipeline_cache_) DestroyPipeline(entry.value); blit_pipeline_cache_.clear(); @@ -490,7 +496,7 @@ void D3D12Device::WaitIdle() { if (queue_) SignalAndWait(); } -std::unique_ptr D3D12Device::CreateSwapchain(u32 width, u32 height, bool vsync, +base::UniquePointer D3D12Device::CreateSwapchain(u32 width, u32 height, bool vsync, bool /*hdr*/) { return D3D12Swapchain::Create(*this, width, height, vsync); } @@ -583,7 +589,7 @@ GpuBuffer D3D12Device::CreateBuffer(u64 size, BufferUsageFlags usage, bool host_ GpuBuffer D3D12Device::CreateBufferWithData(ByteSpan data, BufferUsageFlags usage) { GpuBuffer staging = CreateBuffer(data.size(), kBufferUsageTransferSrc, true); if (!staging.mapped) return {}; - std::memcpy(staging.mapped, data.data(), data.size()); + base::MemCopy(staging.mapped, data.data(), data.size()); GpuBuffer buffer = CreateBuffer(data.size(), usage | kBufferUsageTransferDst, false); ImmediateSubmit( @@ -936,7 +942,7 @@ SamplerHandle D3D12Device::GetSampler(const SamplerDesc& desc) { sampler.AddressV = ToAddressMode(desc.address_v); sampler.AddressW = ToAddressMode(desc.address_w); if (desc.max_anisotropy > 1.0f) { - sampler.MaxAnisotropy = static_cast(std::min(desc.max_anisotropy, 16.0f)); + sampler.MaxAnisotropy = static_cast(rx::Min(desc.max_anisotropy, 16.0f)); } sampler.MinLOD = desc.min_lod; sampler.MaxLOD = desc.max_lod; @@ -1005,7 +1011,7 @@ SetLayout* D3D12Device::GetOrCreateSetLayout(const BindingLayoutDesc& desc) { return layout; } -ID3D12RootSignature* D3D12Device::GetOrCreateRootSignature(std::span sets, +ID3D12RootSignature* D3D12Device::GetOrCreateRootSignature(base::Span sets, u32 push_size, bool push_root_constants, PipelineRecord* out) { @@ -1090,7 +1096,7 @@ ID3D12RootSignature* D3D12Device::GetOrCreateRootSignature(std::span(range_storage.back().size()); @@ -1100,7 +1106,7 @@ ID3D12RootSignature* D3D12Device::GetOrCreateRootSignature(std::span(range_storage.back().size()); @@ -1363,7 +1369,7 @@ void D3D12Device::WriteViewDescriptor(const SetLayout::Slot& slot, const Binding } } -void D3D12Device::UpdateBindingSet(BindingSetHandle set, std::span items) { +void D3D12Device::UpdateBindingSet(BindingSetHandle set, base::Span items) { BindingSetRecord* record = Rec(set); for (const BindingItem& item : items) { const SetLayout::Slot* slot = record->layout->Find(item.slot); @@ -1454,7 +1460,7 @@ PipelineHandle D3D12Device::CreateComputePipeline(const ComputePipelineDesc& des record->compute = true; record->push_size = desc.push_constant_size; record->push_root_constants = desc.push_constant_size <= kMaxRootConstantBytes; - record->root = GetOrCreateRootSignature({set_layouts.data(), set_layouts.size()}, + record->root = GetOrCreateRootSignature(base::Span(set_layouts.data(), set_layouts.size()), desc.push_constant_size, record->push_root_constants, record); if (!record->root) { @@ -1505,7 +1511,7 @@ PipelineHandle D3D12Device::CreateGraphicsPipeline(const GraphicsPipelineDesc& d record->topology = DrawTopology(desc.topology); record->push_size = desc.push_constant_size; record->push_root_constants = desc.push_constant_size <= kMaxRootConstantBytes; - record->root = GetOrCreateRootSignature({set_layouts.data(), set_layouts.size()}, + record->root = GetOrCreateRootSignature(base::Span(set_layouts.data(), set_layouts.size()), desc.push_constant_size, record->push_root_constants, record); if (!record->root) { @@ -1594,12 +1600,15 @@ PipelineHandle D3D12Device::CreateGraphicsPipeline(const GraphicsPipelineDesc& d attributes.push_back({&attribute, binding}); } } - std::sort(attributes.begin(), attributes.end(), - [](const AttributeRef& a, const AttributeRef& b) { - return a.attribute->location < b.attribute->location; - }); - std::sort(signature.begin(), signature.end(), - [](const SignatureElement& a, const SignatureElement& b) { return a.reg < b.reg; }); + // Attribute locations are unique within a pipeline and vertex shader + // inputs are never register-packed, so both keys are unique and any + // correct sort agrees. + base::Sort(attributes.data(), attributes.data() + attributes.size(), + [](const AttributeRef& a, const AttributeRef& b) { + return a.attribute->location < b.attribute->location; + }); + base::Sort(signature.data(), signature.data() + signature.size(), + [](const SignatureElement& a, const SignatureElement& b) { return a.reg < b.reg; }); if (attributes.size() != signature.size()) { RX_ERROR("d3d12: vertex attribute count {} != shader input count {} ({})", attributes.size(), signature.size(), name); @@ -1705,7 +1714,7 @@ PipelineHandle D3D12Device::GetBlitPipeline(Format format) { desc.color_formats.push_back(format); PipelineBindings set; set.slots.push_back({0, BindingType::kCombinedTextureSampler}); - desc.sets.push_back(std::move(set)); + desc.sets.push_back(base::move(set)); desc.debug_name = "blit_mip"; PipelineHandle pipeline = CreateGraphicsPipeline(desc); blit_pipeline_cache_.insert(key, pipeline); @@ -1840,7 +1849,7 @@ void D3D12Device::DestroyTimestampPool(TimestampPoolHandle pool) { bool D3D12Device::GetTimestamps(TimestampPoolHandle pool, u32 first, u32 count, u64* out) { TimestampPoolRecord* record = Rec(pool); if (!record->mapped || first + count > record->count) return false; - std::memcpy(out, static_cast(record->mapped) + first, count * sizeof(u64)); + base::MemCopy(out, static_cast(record->mapped) + first, count * sizeof(u64)); return true; } @@ -1904,7 +1913,7 @@ bool D3D12Device::GetCompactedSizes(AccelCompactionQueryHandle query, u64* out, // Not ready until the fence of the submission that carried the matching // QueryCompactedSizes has signalled (the non-blocking poll contract). if (!record->fence || record->fence->GetCompletedValue() < record->fence_target) return false; - std::memcpy(out, record->mapped, static_cast(count) * sizeof(u64)); + base::MemCopy(out, record->mapped, static_cast(count) * sizeof(u64)); return true; } @@ -1922,7 +1931,7 @@ D3D12CommandList* D3D12Device::BeginRing(u32 ring_index) { ring.view_cursor = 0; ring.push_cursor = 0; ring.wrapper->OnBeginRecording(); - return ring.wrapper.get(); + return ring.wrapper.Get_UseOnlyIfYouKnowWhatYouareDoing(); } void D3D12Device::CloseAndExecute(u32 ring_index) { @@ -1935,7 +1944,7 @@ void D3D12Device::CloseAndExecute(u32 ring_index) { queue_->Signal(ring.fence, ring.fence_value); } -void D3D12Device::ImmediateSubmit(const std::function& record) { +void D3D12Device::ImmediateSubmit(const base::Function& record) { Ring& ring = rings_[kImmediateRing]; D3D12CommandList* cmd = BeginRing(kImmediateRing); record(*cmd); @@ -1970,7 +1979,7 @@ CommandList* D3D12Device::BeginFrame(u32 slot) { PresentResult D3D12Device::SubmitFrame(CommandList* cmd, Swapchain& swapchain, u32 image_index) { (void)image_index; // the swapchain tracked it at Acquire for (u32 i = 0; i < kMaxFramesInFlight; ++i) { - if (rings_[i].wrapper.get() == cmd) { + if (rings_[i].wrapper.Get_UseOnlyIfYouKnowWhatYouareDoing() == cmd) { CloseAndExecute(i); // DXGI Present on windows; no-op against the offscreen ring on linux. return static_cast(swapchain).Present(); @@ -1981,7 +1990,7 @@ PresentResult D3D12Device::SubmitFrame(CommandList* cmd, Swapchain& swapchain, u void D3D12Device::SubmitFrame(CommandList* cmd) { for (u32 i = 0; i < kMaxFramesInFlight; ++i) { - if (rings_[i].wrapper.get() == cmd) { + if (rings_[i].wrapper.Get_UseOnlyIfYouKnowWhatYouareDoing() == cmd) { CloseAndExecute(i); // signals the slot fence; no Acquire, no Present return; } @@ -2011,7 +2020,7 @@ bool D3D12Device::ReadbackImage(const GpuImage& image, ResourceState current, vo // READBACK heap; no extra barrier is needed under the d3d12 memory model. }); - std::memcpy(out, staging.mapped, needed); + base::MemCopy(out, staging.mapped, needed); DestroyBuffer(staging); return true; } diff --git a/engine/render/d3d12/d3d12_headers.h b/engine/render/d3d12/d3d12_headers.h index e00611a4..908f6e0e 100644 --- a/engine/render/d3d12/d3d12_headers.h +++ b/engine/render/d3d12/d3d12_headers.h @@ -5,8 +5,10 @@ // else vkd3d's headers provide the identical API surface natively // (vkd3d_windows.h supplies the COM/Windows base types, vkd3d_d3d12.h the API, // vkd3d_utils.h D3D12CreateDevice/D3D12SerializeRootSignature and the event -// helpers). Include this before any engine header that uses std::min/max: -// vkd3d_windows.h defines min/max macros which are undone below. +// helpers). Engine code uses rx::Min/Max, but a lowercase min/max macro would +// still break any member or function of that name (numeric limits, bounds +// structs), so vkd3d_windows.h's min/max macros are undone below and +// windows.h gets NOMINMAX. #if defined(_WIN32) @@ -14,7 +16,7 @@ #define WIN32_LEAN_AND_MEAN #endif #ifndef NOMINMAX -#define NOMINMAX // engine headers use std::min/max +#define NOMINMAX // min/max macros would break any min/max name that follows #endif #include @@ -41,7 +43,7 @@ inline void DestroyFenceEvent(HANDLE event) { CloseHandle(event); } #include #include -#include +#include #ifdef min #undef min diff --git a/engine/render/d3d12/d3d12_swapchain.cc b/engine/render/d3d12/d3d12_swapchain.cc index 9f8a83d6..ba253eae 100644 --- a/engine/render/d3d12/d3d12_swapchain.cc +++ b/engine/render/d3d12/d3d12_swapchain.cc @@ -12,13 +12,14 @@ #include "render/d3d12/d3d12_backend.h" +#include "base/memory/unique_pointer.h" #include "core/log.h" namespace rx::render::d3d12 { -std::unique_ptr D3D12Swapchain::Create(D3D12Device& device, u32 width, u32 height, +base::UniquePointer D3D12Swapchain::Create(D3D12Device& device, u32 width, u32 height, bool vsync) { - auto swapchain = std::unique_ptr(new D3D12Swapchain(device)); + auto swapchain = base::UniquePointer(new D3D12Swapchain(device)); if (!swapchain->Init(width, height, vsync)) return nullptr; return swapchain; } diff --git a/engine/render/geometry/adaptive_water.cc b/engine/render/geometry/adaptive_water.cc index dc393d1b..2ed9b429 100644 --- a/engine/render/geometry/adaptive_water.cc +++ b/engine/render/geometry/adaptive_water.cc @@ -1,10 +1,9 @@ #include "render/geometry/adaptive_water.h" -#include -#include - #include "asset/mesh.h" +#include "base/containers/vector.h" #include "core/log.h" +#include "core/scalar.h" #include "shaders/adaptive_water_cs_hlsl.h" namespace rx::render { @@ -33,8 +32,8 @@ bool AdaptiveWaterMesh::Initialize(Device& device) { }); if (!pipeline_) return false; - std::vector zero_states(kNodeCount, 0); - std::vector zero_commands(kMaxTriangles, DrawCommand{}); + base::Vector zero_states(kNodeCount, 0); + base::Vector zero_commands(kMaxTriangles, DrawCommand{}); const u32 counters[4] = {2, 0, 0, 0}; states_ = device.CreateBufferWithData( ByteSpan(reinterpret_cast(zero_states.data()), zero_states.size() * sizeof(u32)), @@ -69,14 +68,14 @@ void AdaptiveWaterMesh::Update(CommandList& cmd, const UpdateParams& params) { if (!available()) return; AdaptivePush push{}; push.local_to_clip = params.local_to_clip; - std::copy_n(params.bounds, 4, push.bounds); + for (int k = 0; k < 4; ++k) push.bounds[k] = params.bounds[k]; push.camera_height_time[0] = params.camera_local.x; push.camera_height_time[1] = params.camera_local.z; push.camera_height_time[2] = params.height; push.camera_height_time[3] = params.time; - push.metrics[0] = std::max(params.target_pixels, 1.0f); - push.metrics[1] = static_cast(std::max(params.render_width, 1u)); - push.metrics[2] = static_cast(std::max(params.render_height, 1u)); + push.metrics[0] = rx::Max(params.target_pixels, 1.0f); + push.metrics[1] = static_cast(rx::Max(params.render_width, 1u)); + push.metrics[2] = static_cast(rx::Max(params.render_height, 1u)); push.control[1] = kNodesPerTree; push.control[2] = kMaxDepth; push.control[3] = SanitizeBudget(params.triangle_budget); diff --git a/engine/render/geometry/fluid_sim.cc b/engine/render/geometry/fluid_sim.cc index bb487f7f..67ed6bd1 100644 --- a/engine/render/geometry/fluid_sim.cc +++ b/engine/render/geometry/fluid_sim.cc @@ -1,11 +1,11 @@ #include "render/geometry/fluid_sim.h" -#include -#include -#include -#include +#include +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" #include "core/log.h" +#include "core/scalar.h" #include "shaders/fluid_sim_cs_hlsl.h" namespace rx::render { @@ -142,7 +142,7 @@ void FluidSim::Configure(Device& device, const FluidDomainDesc& desc) { const u32 cells = desc.resolution * desc.resolution; // Initial state: r=water depth, g=lava depth 0, b=T ambient, a=crust 0. - std::vector init(static_cast(cells) * 4, 0.0f); + base::Vector init(static_cast(cells) * 4, 0.0f); for (u32 i = 0; i < cells; ++i) { init[i * 4 + 0] = desc.initial_water ? desc.initial_water[i] : 0.0f; init[i * 4 + 2] = desc.ambient_temperature; @@ -162,22 +162,22 @@ void FluidSim::Configure(Device& device, const FluidDomainDesc& desc) { disabled_ = true; return; } - std::memcpy(bed_stage.mapped, desc.bed, static_cast(cells) * sizeof(f32)); - std::memcpy(state_stage.mapped, init.data(), init.size() * sizeof(f32)); + base::MemCopy(bed_stage.mapped, desc.bed, static_cast(cells) * sizeof(f32)); + base::MemCopy(state_stage.mapped, init.data(), init.size() * sizeof(f32)); device.ImmediateSubmit([&](CommandList& cmd) { const f32 zero[4] = {0, 0, 0, 0}; BufferTextureCopy copy; cmd.Barrier(Transition(bed_, ResourceState::kUndefined, ResourceState::kCopyDst)); - cmd.CopyBufferToTexture(bed_stage, bed_, {©, 1}); + cmd.CopyBufferToTexture(bed_stage, bed_, base::Span(©, 1)); cmd.Barrier(Transition(bed_, ResourceState::kCopyDst, ResourceState::kGeneral)); // Seed both ping-pong slots (each substep writes B then A, but a clean // start keeps the very first sampled reads well defined). for (GpuImage& s : state_) { cmd.Barrier(Transition(s, ResourceState::kUndefined, ResourceState::kCopyDst)); - cmd.CopyBufferToTexture(state_stage, s, {©, 1}); + cmd.CopyBufferToTexture(state_stage, s, base::Span(©, 1)); cmd.Barrier(Transition(s, ResourceState::kCopyDst, ResourceState::kGeneral)); } for (GpuImage* img : {&flux_water_, &flux_lava_, &velocity_}) { @@ -208,7 +208,7 @@ void FluidSim::UploadBed(Device& device, const FluidDomainDesc& desc) { bed_version_ = desc.bed_version; // consume the bump; do not retry-spam return; } - std::memcpy(stage.mapped, desc.bed, static_cast(cells) * sizeof(f32)); + base::MemCopy(stage.mapped, desc.bed, static_cast(cells) * sizeof(f32)); device.ImmediateSubmit([&](CommandList& cmd) { BufferTextureCopy copy; // kUndefined-as-source orders behind ALL prior work (the engine's @@ -217,7 +217,7 @@ void FluidSim::UploadBed(Device& device, const FluidDomainDesc& desc) { // wait on compute. Contents are discarded, but the copy rewrites the whole // image. cmd.Barrier(Transition(bed_, ResourceState::kUndefined, ResourceState::kCopyDst)); - cmd.CopyBufferToTexture(stage, bed_, {©, 1}); + cmd.CopyBufferToTexture(stage, bed_, base::Span(©, 1)); cmd.Barrier(Transition(bed_, ResourceState::kCopyDst, ResourceState::kGeneral)); }); device.DestroyBuffer(stage); @@ -253,12 +253,12 @@ void FluidSim::AddToGraph(RenderGraph& graph, const UpdateParams& params) { gp.extent = desc.extent; gp.texel = l; gp.resolution = static_cast(desc.resolution); - std::memcpy(params_[slot].mapped, &gp, sizeof(gp)); + base::MemCopy(params_[slot].mapped, &gp, sizeof(gp)); // Bounded per-frame sources, packed for the shader. A null pointer means no // sources regardless of the count; the shader must never consume the slot's // stale records from a previous frame. - u32 source_count = params.sources ? std::min(params.source_count, kMaxSources) : 0u; + u32 source_count = params.sources ? rx::Min(params.source_count, kMaxSources) : 0u; if (source_count > 0) { GpuSource* dst = static_cast(sources_[slot].mapped); for (u32 i = 0; i < source_count; ++i) { @@ -276,14 +276,14 @@ void FluidSim::AddToGraph(RenderGraph& graph, const UpdateParams& params) { // Fixed substeps with a per-frame cap; the remainder carries across frames so // the sim is frame-rate independent and deterministic. - accum_ += std::max(params.dt, 0.0f); + accum_ += rx::Max(params.dt, 0.0f); u32 substeps = static_cast(accum_ / kSubstepDt); - substeps = std::min(substeps, kMaxSubsteps); + substeps = rx::Min(substeps, kMaxSubsteps); accum_ -= static_cast(substeps) * kSubstepDt; // A dt larger than the per-frame substep budget must not accumulate as time // debt (it would spiral: every later frame runs the cap and never catches // up). Drop the excess: the sim slows down instead of death-spiralling. - accum_ = std::min(accum_, kSubstepDt); + accum_ = rx::Min(accum_, kSubstepDt); if (substeps == 0) return; // read side unchanged; renderer still has state FluidPush push{}; diff --git a/engine/render/geometry/fluid_surface.cc b/engine/render/geometry/fluid_surface.cc index f4fee39a..b3371feb 100644 --- a/engine/render/geometry/fluid_surface.cc +++ b/engine/render/geometry/fluid_surface.cc @@ -1,8 +1,8 @@ #include "render/geometry/fluid_surface.h" -#include - +#include "base/memory/unique_pointer.h" #include "core/log.h" +#include "core/scalar.h" #include "render/pipeline/mesh_pipeline.h" #include "shaders/fluid_surface_ps_hlsl.h" #include "shaders/fluid_surface_vs_hlsl.h" @@ -36,12 +36,12 @@ constexpr u32 kMaxGridResolution = 512; } // namespace -std::unique_ptr FluidSurfacePass::Create( +base::UniquePointer FluidSurfacePass::Create( Device& device, Format color_format, Format motion_format, Format depth_format, BindingLayoutHandle globals_layout, BindingLayoutHandle environment_layout, BindingLayoutHandle bindless_layout) { (void)bindless_layout; // IBL rides the environment set; no bindless table here. - auto pass = std::unique_ptr(new FluidSurfacePass(device)); + auto pass = base::UniquePointer(new FluidSurfacePass(device)); // Linear + clamp: the state/bed/velocity fields are sampled continuously // across the (coarser) render grid and must not wrap at the domain edge. @@ -90,7 +90,7 @@ FluidSurfacePass::~FluidSurfacePass() { void FluidSurfacePass::Draw(PassContext& ctx, BindingSetHandle globals, BindingSetHandle environment, const FluidSim& sim, u32 frame_slot, f32 time) { - const u32 grid = std::min(sim.domain().resolution, kMaxGridResolution); + const u32 grid = rx::Min(sim.domain().resolution, kMaxGridResolution); if (grid == 0) return; ctx.cmd->BindPipeline(pipeline_); diff --git a/engine/render/geometry/fluid_surface.h b/engine/render/geometry/fluid_surface.h index 4640ec75..148958fc 100644 --- a/engine/render/geometry/fluid_surface.h +++ b/engine/render/geometry/fluid_surface.h @@ -1,8 +1,8 @@ #ifndef RX_RENDER_FLUID_SURFACE_H_ #define RX_RENDER_FLUID_SURFACE_H_ -#include +#include "base/memory/unique_pointer.h" #include "render/core/render_graph.h" #include "render/geometry/fluid_sim.h" #include "render/rhi/device.h" @@ -34,7 +34,7 @@ class FluidSurfacePass { // render pass stays compatible. bindless_layout is accepted for call-site // parity with the other transparent passes but this pass needs no bindless // table (IBL comes through the environment set). - static std::unique_ptr Create(Device& device, Format color_format, + static base::UniquePointer Create(Device& device, Format color_format, Format motion_format, Format depth_format, BindingLayoutHandle globals_layout, BindingLayoutHandle environment_layout, diff --git a/engine/render/geometry/fur.cc b/engine/render/geometry/fur.cc index c2554f46..696514e2 100644 --- a/engine/render/geometry/fur.cc +++ b/engine/render/geometry/fur.cc @@ -1,8 +1,9 @@ #include "render/geometry/fur.h" -#include +#include #include "asset/primitives.h" +#include "base/memory/mem_ops.h" #include "core/log.h" #include "render/rhi/device.h" #include "shaders/fur_ps_hlsl.h" @@ -101,7 +102,7 @@ void FurPass::AddToGraph(RenderGraph& graph, ResourceHandle color, ResourceHandl ctx.cmd->BindPipeline(pipeline_); const FurCamera camera{view_proj}; - std::memcpy(camera_[slot].mapped, &camera, sizeof(camera)); + base::MemCopy(camera_[slot].mapped, &camera, sizeof(camera)); ctx.cmd->BindTransient(0, {Bind::Uniform(0, camera_[slot], 0, sizeof(FurCamera))}); diff --git a/engine/render/geometry/gaussian.cc b/engine/render/geometry/gaussian.cc index 831a3693..9a3cf8c7 100644 --- a/engine/render/geometry/gaussian.cc +++ b/engine/render/geometry/gaussian.cc @@ -1,15 +1,18 @@ #include "render/geometry/gaussian.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" #include "core/log.h" +#include "core/scalar.h" +#include "core/text_reader.h" #include "shaders/gsplat_ps_hlsl.h" #include "shaders/gsplat_vs_hlsl.h" +#include "core/sort.h" namespace rx::render { namespace { @@ -24,7 +27,7 @@ struct GaussianPush { f32 pad[3]; }; -u32 PlyTypeSize(const std::string& t) { +u32 PlyTypeSize(const base::String& t) { if (t == "char" || t == "uchar" || t == "int8" || t == "uint8") return 1; if (t == "short" || t == "ushort" || t == "int16" || t == "uint16") return 2; if (t == "int" || t == "uint" || t == "int32" || t == "uint32" || t == "float" || t == "float32") @@ -33,7 +36,7 @@ u32 PlyTypeSize(const std::string& t) { return 0; } -f32 Sigmoid(f32 x) { return 1.0f / (1.0f + std::exp(-x)); } +f32 Sigmoid(f32 x) { return 1.0f / (1.0f + ::exp(-x)); } } // namespace @@ -68,7 +71,7 @@ void GaussianSplat::AddToGraph(RenderGraph& graph, ResourceHandle color, const base::Vector& gaussians, const Frame& frame, u32 frame_slot) { if (gaussians.empty()) return; - u32 count = std::min(static_cast(gaussians.size()), kMaxGaussians); + u32 count = rx::Min(static_cast(gaussians.size()), kMaxGaussians); // Sort back-to-front by view depth (front = -z, so most-negative first). base::Vector order(count); @@ -78,7 +81,9 @@ void GaussianSplat::AddToGraph(RenderGraph& graph, ResourceHandle color, const GaussianInstance& g = gaussians[i]; return v.m[2] * g.position[0] + v.m[6] * g.position[1] + v.m[10] * g.position[2] + v.m[14]; }; - std::sort(order.begin(), order.end(), [&](u32 a, u32 b) { return view_z(a) < view_z(b); }); + // Stable: equal depths keep splat order. + rx::StableSort(order.data(), order.data() + order.size(), + [&](u32 a, u32 b) { return view_z(a) < view_z(b); }); GaussianInstance* dst = static_cast(buffers_[frame_slot].mapped); for (u32 i = 0; i < count; ++i) dst[i] = gaussians[order[i]]; @@ -115,25 +120,28 @@ void GaussianSplat::Destroy(Device& device) { for (u32 i = 0; i < kFramesInFlight; ++i) device.DestroyBuffer(buffers_[i]); } -bool LoadGaussianPly(const std::string& path, base::Vector* out) { - std::ifstream file(path, std::ios::binary); - if (!file) { +bool LoadGaussianPly(const base::String& path, base::Vector* out) { + base::Vector bytes; + if (!fs::ReadFile(path, &bytes)) { RX_WARN("gaussian ply: cannot open {}", path); return false; } - std::string line; - if (!std::getline(file, line) || line.compare(0, 3, "ply") != 0) { + // The header is text lines, the body binary or more lines, both read from + // this one cursor. + LineReader lines(base::StringRef(reinterpret_cast(bytes.data()), bytes.size())); + base::StringRef line; + if (!lines.Next(&line) || !line.starts_with("ply")) { RX_WARN("gaussian ply: {} is not a ply file", path); return false; } struct Prop { - std::string name; + base::String name; u32 size = 0; u32 offset = 0; }; struct Elem { - std::string name; + base::String name; u64 count = 0; base::Vector props; u32 stride = 0; @@ -141,32 +149,35 @@ bool LoadGaussianPly(const std::string& path, base::Vector* ou }; base::Vector elems; bool binary = false, little = true; - while (std::getline(file, line)) { - std::istringstream ls(line); - std::string tok; - ls >> tok; + while (lines.Next(&line)) { + TokenReader ls(line); + base::String tok; + ls.Next(&tok); if (tok == "end_header") break; if (tok == "format") { - std::string fmt; - ls >> fmt; - binary = fmt.compare(0, 6, "binary") == 0; + base::String fmt; + ls.Next(&fmt); + binary = fmt.starts_with("binary"); little = fmt != "binary_big_endian"; } else if (tok == "element") { Elem e; - ls >> e.name >> e.count; - elems.push_back(std::move(e)); + ls.Next(&e.name); + ls.Next(&e.count); + elems.push_back(base::move(e)); } else if (tok == "property" && !elems.empty()) { - std::string type; - ls >> type; + base::String type; + ls.Next(&type); Elem& e = elems.back(); if (type == "list") { // face index lists; not present on splat vertices - std::string a, b, nm; - ls >> a >> b >> nm; + base::String a, b, nm; + ls.Next(&a); + ls.Next(&b); + ls.Next(&nm); e.props.push_back({nm, 0, 0}); e.has_list = true; } else { - std::string nm; - ls >> nm; + base::String nm; + ls.Next(&nm); u32 sz = PlyTypeSize(type); e.props.push_back({nm, sz, e.stride}); e.stride += sz; @@ -177,6 +188,7 @@ bool LoadGaussianPly(const std::string& path, base::Vector* ou RX_WARN("gaussian ply: big-endian binary is not supported"); return false; } + u64 body = lines.position(); const f32 kC0 = 0.28209479177387814f; // sh band 0 constant const u64 kCap = 1u << 18; // matches the renderer's gaussian budget @@ -188,9 +200,9 @@ bool LoadGaussianPly(const std::string& path, base::Vector* ou RX_WARN("gaussian ply: variable-size element before vertex is unsupported"); return false; } - file.seekg(static_cast(e.count * e.stride), std::ios::cur); + body += e.count * e.stride; } else { - for (u64 i = 0; i < e.count && std::getline(file, line); ++i) { + for (u64 i = 0; i < e.count && lines.Next(&line); ++i) { } } continue; @@ -215,17 +227,17 @@ bool LoadGaussianPly(const std::string& path, base::Vector* ou auto decode = [](const u8* rec, const Prop& p) -> f32 { if (p.size == 4) { float v; - std::memcpy(&v, rec + p.offset, 4); + base::MemCopy(&v, rec + p.offset, 4); return v; } if (p.size == 8) { double v; - std::memcpy(&v, rec + p.offset, 8); + base::MemCopy(&v, rec + p.offset, 8); return static_cast(v); } if (p.size == 2) { u16 v; - std::memcpy(&v, rec + p.offset, 2); + base::MemCopy(&v, rec + p.offset, 2); return static_cast(v); } if (p.size == 1) return static_cast(rec[p.offset]); @@ -237,15 +249,16 @@ bool LoadGaussianPly(const std::string& path, base::Vector* ou bool truncated = false; for (u64 i = 0; i < e.count; ++i) { if (binary) { - file.read(reinterpret_cast(rec.data()), e.stride); - if (!file) break; + if (body > bytes.size() || bytes.size() - body < e.stride) break; + base::MemCopy(rec.data(), bytes.data() + body, e.stride); + body += e.stride; for (size_t k = 0; k < e.props.size(); ++k) vals[k] = decode(rec.data(), e.props[k]); } else { - if (!std::getline(file, line)) break; - std::istringstream vs(line); + if (!lines.Next(&line)) break; + TokenReader vs(line); for (size_t k = 0; k < e.props.size(); ++k) { f32 v = 0.0f; - vs >> v; + vs.Next(&v); vals[k] = v; } } @@ -260,18 +273,18 @@ bool LoadGaussianPly(const std::string& path, base::Vector* ou g.position[2] = at(iz); if (iop >= 0) g.opacity = Sigmoid(at(iop)); if (is0 >= 0 && is1 >= 0 && is2 >= 0) { - g.scale[0] = std::exp(at(is0)); - g.scale[1] = std::exp(at(is1)); - g.scale[2] = std::exp(at(is2)); + g.scale[0] = ::exp(at(is0)); + g.scale[1] = ::exp(at(is1)); + g.scale[2] = ::exp(at(is2)); } if (if0 >= 0 && if1 >= 0 && if2 >= 0) { - g.color[0] = std::clamp(0.5f + kC0 * at(if0), 0.0f, 1.0f); - g.color[1] = std::clamp(0.5f + kC0 * at(if1), 0.0f, 1.0f); - g.color[2] = std::clamp(0.5f + kC0 * at(if2), 0.0f, 1.0f); + g.color[0] = rx::Clamp(0.5f + kC0 * at(if0), 0.0f, 1.0f); + g.color[1] = rx::Clamp(0.5f + kC0 * at(if1), 0.0f, 1.0f); + g.color[2] = rx::Clamp(0.5f + kC0 * at(if2), 0.0f, 1.0f); } if (ir0 >= 0 && ir1 >= 0 && ir2 >= 0 && ir3 >= 0) { f32 w = at(ir0), x = at(ir1), y = at(ir2), z = at(ir3); // inria stores wxyz - f32 len = std::sqrt(w * w + x * x + y * y + z * z); + f32 len = ::sqrt(w * w + x * x + y * y + z * z); if (len < 1e-8f) len = 1.0f; g.rotation[0] = x / len; g.rotation[1] = y / len; diff --git a/engine/render/geometry/gaussian.h b/engine/render/geometry/gaussian.h index 60c74787..3106006d 100644 --- a/engine/render/geometry/gaussian.h +++ b/engine/render/geometry/gaussian.h @@ -1,10 +1,10 @@ #ifndef RX_RENDER_GAUSSIAN_H_ #define RX_RENDER_GAUSSIAN_H_ -#include #include +#include "base/strings/xstring.h" #include "core/export.h" #include "core/math.h" #include "render/core/render_graph.h" @@ -63,7 +63,7 @@ class GaussianSplat { // GaussianInstances, applying the standard activations: sigmoid opacity, exp // scale, sh band-0 dc color, normalized wxyz->xyzw rotation. Returns false on a // read/parse error. Caps at the renderer's gaussian budget (logs if truncated). -RX_RENDER_EXPORT bool LoadGaussianPly(const std::string& path, base::Vector* out); +RX_RENDER_EXPORT bool LoadGaussianPly(const base::String& path, base::Vector* out); } // namespace rx::render diff --git a/engine/render/geometry/hair_groom.cc b/engine/render/geometry/hair_groom.cc index 9600a447..3c6bf557 100644 --- a/engine/render/geometry/hair_groom.cc +++ b/engine/render/geometry/hair_groom.cc @@ -1,13 +1,14 @@ #include "render/geometry/hair_groom.h" -#include -#include -#include +#include +#include #include #include "asset/bc_encode.h" +#include "base/memory/mem_ops.h" #include "core/log.h" +#include "core/scalar.h" namespace rx::render { namespace { @@ -55,8 +56,8 @@ void DecodeBc4Alpha(const u8* block, f32 out[16]) { void DecodeBc1Colors(const u8* block, bool bc3, u8 out[16][3]) { u16 c0, c1; - std::memcpy(&c0, block, 2); - std::memcpy(&c1, block + 2, 2); + base::MemCopy(&c0, block, 2); + base::MemCopy(&c1, block + 2, 2); auto expand = [](u16 c, u8 rgb[3]) { rgb[0] = static_cast(((c >> 11) & 0x1f) * 255 / 31); rgb[1] = static_cast(((c >> 5) & 0x3f) * 255 / 63); @@ -76,7 +77,7 @@ void DecodeBc1Colors(const u8* block, bool bc3, u8 out[16][3]) { } } u32 bits; - std::memcpy(&bits, block + 4, 4); + base::MemCopy(&bits, block + 4, 4); for (int i = 0; i < 16; ++i) { u32 idx = (bits >> (i * 2)) & 0x3; for (int k = 0; k < 3; ++k) out[i][k] = pal[idx][k]; @@ -88,13 +89,13 @@ size_t MipOffset(const asset::Texture& t, u32 mip, u32* w, u32* h) { size_t block = (t.format == asset::TextureFormat::kBc1) ? 8 : 16; size_t offset = 0; for (u32 m = 0; m < mip; ++m) { - u32 mw = std::max(1u, t.width >> m); - u32 mh = std::max(1u, t.height >> m); + u32 mw = rx::Max(1u, t.width >> m); + u32 mh = rx::Max(1u, t.height >> m); offset += compressed ? ((mw + 3) / 4) * ((mh + 3) / 4) * block : static_cast(mw) * mh * 4; } - *w = std::max(1u, t.width >> mip); - *h = std::max(1u, t.height >> mip); + *w = rx::Max(1u, t.width >> mip); + *h = rx::Max(1u, t.height >> mip); return offset; } @@ -108,7 +109,7 @@ bool DecodeDiffuse(const asset::Texture& t, u32 target_max, DecodedTex* out) { } u32 mip = 0; for (u32 m = 0; m + 1 < t.mip_count; ++m) { - if (std::max(t.width >> m, t.height >> m) <= target_max) break; + if (rx::Max(t.width >> m, t.height >> m) <= target_max) break; mip = m + 1; } u32 w, h; @@ -118,7 +119,7 @@ bool DecodeDiffuse(const asset::Texture& t, u32 target_max, DecodedTex* out) { out->rgba.resize(static_cast(w) * h * 4); auto to_lin = [&](u8 c) { f32 x = c / 255.0f; - return t.is_srgb ? std::pow(x, 2.2f) : x; + return t.is_srgb ? ::pow(x, 2.2f) : x; }; if (t.format == asset::TextureFormat::kRgba8) { if (offset + static_cast(w) * h * 4 > t.data.size()) return false; @@ -153,10 +154,10 @@ bool DecodeDiffuse(const asset::Texture& t, u32 target_max, DecodedTex* out) { // DXT1 1-bit alpha: the punch-through palette index (only in the // 3-colour mode) is transparent; opaque cards decode to 1. u16 c0, c1; - std::memcpy(&c0, blk, 2); - std::memcpy(&c1, blk + 2, 2); + base::MemCopy(&c0, blk, 2); + base::MemCopy(&c1, blk + 2, 2); u32 bits; - std::memcpy(&bits, blk + 4, 4); + base::MemCopy(&bits, blk + 4, 4); bool four = c0 > c1; for (int i = 0; i < 16; ++i) { u32 idx = (bits >> (i * 2)) & 0x3; @@ -186,9 +187,9 @@ struct Texel { // Bilinear tap, returns linear rgb + coverage alpha. Texel SampleTex(const DecodedTex& t, f32 u, f32 v) { - auto wrap = [](f32 x) { return x - std::floor(x); }; + auto wrap = [](f32 x) { return x - ::floor(x); }; f32 fx = wrap(u) * t.w - 0.5f, fy = wrap(v) * t.h - 0.5f; - i32 x0 = static_cast(std::floor(fx)), y0 = static_cast(std::floor(fy)); + i32 x0 = static_cast(::floor(fx)), y0 = static_cast(::floor(fy)); f32 tx = fx - x0, ty = fy - y0; auto at = [&](i32 x, i32 y) { x = ((x % (i32)t.w) + t.w) % t.w; @@ -253,7 +254,7 @@ Vec3 ChainStep(const Comp& comp, const base::Vector& verts, f32 target_a, f32 dc = AcrossOf(comp, gv) - c; Vec3 dw = gv.p - prev; f32 e = da * da * inv_ha + dc * dc * inv_hc + Dot(dw, dw) * inv_hw; - f32 w = std::exp(-e); + f32 w = ::exp(-e); acc = acc + gv.p * w; wsum += w; } @@ -265,13 +266,13 @@ Vec3 ChainStep(const Comp& comp, const base::Vector& verts, f32 target_a, // interpolate the scalp edge continuously instead of snapping to those few verts. Vec3 RootAt(const Comp& comp, const base::Vector& verts, f32 c) { f32 n = static_cast(comp.root_verts.size()); - f32 h = std::max((comp.c_max - comp.c_min) / std::max(1.0f, n - 1.0f), 1e-4f) * 1.2f; + f32 h = rx::Max((comp.c_max - comp.c_min) / rx::Max(1.0f, n - 1.0f), 1e-4f) * 1.2f; f32 inv = 1.0f / (h * h); Vec3 acc{0, 0, 0}; f32 wsum = 0; for (u32 vi : comp.root_verts) { f32 dc = AcrossOf(comp, verts[vi]) - c; - f32 w = std::exp(-dc * dc * inv); + f32 w = ::exp(-dc * dc * inv); acc = acc + verts[vi].p * w; wsum += w; } @@ -352,14 +353,14 @@ bool BuildHairGroom(const asset::Mesh& mesh, const GroomParams& params, GroomDat Vec3 dp = verts[vi].p - pbar; suu += du * du; svv += dv * dv; suv += du * dv; sup = sup + dp * du; svp = svp + dp * dv; - bmin = {std::min(bmin.x, verts[vi].p.x), std::min(bmin.y, verts[vi].p.y), - std::min(bmin.z, verts[vi].p.z)}; - bmax = {std::max(bmax.x, verts[vi].p.x), std::max(bmax.y, verts[vi].p.y), - std::max(bmax.z, verts[vi].p.z)}; + bmin = {rx::Min(bmin.x, verts[vi].p.x), rx::Min(bmin.y, verts[vi].p.y), + rx::Min(bmin.z, verts[vi].p.z)}; + bmax = {rx::Max(bmax.x, verts[vi].p.x), rx::Max(bmax.y, verts[vi].p.y), + rx::Max(bmax.z, verts[vi].p.z)}; } f32 det = suu * svv - suv * suv; Vec3 dpu, dpv; // world metres per unit u / v - if (std::fabs(det) > 1e-12f) { + if (::fabs(det) > 1e-12f) { dpu = (sup * svv - svp * suv) * (1.0f / det); dpv = (svp * suu - sup * suv) * (1.0f / det); } else { @@ -369,8 +370,8 @@ bool BuildHairGroom(const asset::Mesh& mesh, const GroomParams& params, GroomDat // Compare world span (metres/uv * uv-extent) on each axis to pick the length. f32 umin = 1e30f, umax = -1e30f, vmin = 1e30f, vmax = -1e30f; for (u32 vi : comp.verts) { - umin = std::min(umin, verts[vi].u); umax = std::max(umax, verts[vi].u); - vmin = std::min(vmin, verts[vi].v); vmax = std::max(vmax, verts[vi].v); + umin = rx::Min(umin, verts[vi].u); umax = rx::Max(umax, verts[vi].u); + vmin = rx::Min(vmin, verts[vi].v); vmax = rx::Max(vmax, verts[vi].v); } f32 span_u = Length(dpu) * (umax - umin); f32 span_v = Length(dpv) * (vmax - vmin); @@ -378,8 +379,8 @@ bool BuildHairGroom(const asset::Mesh& mesh, const GroomParams& params, GroomDat for (u32 vi : comp.verts) { f32 a = AlongOf(comp, verts[vi]); f32 c = AcrossOf(comp, verts[vi]); - amin = std::min(amin, a); amax = std::max(amax, a); - cmin = std::min(cmin, c); cmax = std::max(cmax, c); + amin = rx::Min(amin, a); amax = rx::Max(amax, a); + cmin = rx::Min(cmin, c); cmax = rx::Max(cmax, c); } comp.c_min = cmin; comp.c_max = cmax; @@ -390,7 +391,7 @@ bool BuildHairGroom(const asset::Mesh& mesh, const GroomParams& params, GroomDat comp.across_dir = comp.world_per_c > 1e-9f ? across_grad * (1.0f / comp.world_per_c) : Vec3{1, 0, 0}; comp.across_world = comp.world_per_c * (cmax - cmin); - comp.hw = std::clamp(0.5f * comp.across_world, 0.006f, 0.05f); + comp.hw = rx::Clamp(0.5f * comp.across_world, 0.006f, 0.05f); Vec3 nrm = Cross(dpu, dpv); f32 nl = Length(nrm); comp.normal = nl > 1e-9f ? nrm * (1.0f / nl) : Vec3{0, 0, 1}; @@ -411,9 +412,9 @@ bool BuildHairGroom(const asset::Mesh& mesh, const GroomParams& params, GroomDat else { comp.a_root = amax; comp.a_tip = amin; } // Root-edge verts: the scalp-side band. Strands are seeded only here so they // start at the scalp and trace the full card length, never mid-card. - f32 root_band = 0.14f * std::fabs(comp.a_tip - comp.a_root) + 1e-6f; + f32 root_band = 0.14f * ::fabs(comp.a_tip - comp.a_root) + 1e-6f; for (u32 vi : comp.verts) { - if (std::fabs(AlongOf(comp, verts[vi]) - comp.a_root) <= root_band) { + if (::fabs(AlongOf(comp, verts[vi]) - comp.a_root) <= root_band) { comp.root_verts.push_back(vi); } } @@ -489,26 +490,26 @@ bool BuildHairGroom(const asset::Mesh& mesh, const GroomParams& params, GroomDat } Vec3 color = (have_tex && a_acc > 1e-3f) ? col_acc * (1.0f / a_acc) : Vec3{1, 1, 1}; - f32 ha = 0.8f * std::fabs(comp.a_tip - comp.a_root) / (P - 1) + 1e-4f; - f32 hc = 0.06f * std::fabs(comp.c_max - comp.c_min) + 1e-4f; + f32 ha = 0.8f * ::fabs(comp.a_tip - comp.a_root) / (P - 1) + 1e-4f; + f32 hc = 0.06f * ::fabs(comp.c_max - comp.c_min) + 1e-4f; // Per-strand flyaway: a fixed random bend that grows toward the tip, applied // to the stored point only (not the chain anchor) so neighbouring strands from // the same card fan apart into individual locks instead of a flat card-slab. f32 fang = randf() * 6.2831853f; f32 fh = randf(); - Vec3 fdir{std::cos(fang), -0.15f - 0.3f * fh, std::sin(fang)}; // outward + slight droop + Vec3 fdir{::cos(fang), -0.15f - 0.3f * fh, ::sin(fang)}; // outward + slight droop // A few strands only (fh near 1) get a real flyaway; most stay tidy so the // silhouette reads as groomed hair, not frizz. Curl already separates strands. - f32 famp = std::clamp(comp.hw * 0.3f, 0.002f, 0.008f) * (fh > 0.7f ? fh : 0.15f * fh) * + f32 famp = rx::Clamp(comp.hw * 0.3f, 0.002f, 0.008f) * (fh > 0.7f ? fh : 0.15f * fh) * params.frizz; // Lock cross-section: quantise across into ~10 mm locks and bulge each out // along the card normal (round tube, not flat sheet). const f32 kLockWorld = 0.010f; f32 lock_c = comp.world_per_c > 1e-6f ? kLockWorld / comp.world_per_c : (comp.c_max - comp.c_min); - lock_c = std::min(std::max(lock_c, 1e-6f), comp.c_max - comp.c_min + 1e-6f); - f32 lock_center = comp.c_min + (std::floor((c_seed - comp.c_min) / lock_c) + 0.5f) * lock_c; - f32 an = std::clamp((c_seed - lock_center) / (0.5f * lock_c), -1.0f, 1.0f); - f32 profile = std::sqrt(std::max(0.0f, 1.0f - an * an)); + lock_c = rx::Min(rx::Max(lock_c, 1e-6f), comp.c_max - comp.c_min + 1e-6f); + f32 lock_center = comp.c_min + (::floor((c_seed - comp.c_min) / lock_c) + 0.5f) * lock_c; + f32 an = rx::Clamp((c_seed - lock_center) / (0.5f * lock_c), -1.0f, 1.0f); + f32 profile = ::sqrt(rx::Max(0.0f, 1.0f - an * an)); f32 bulge = 0.5f * lock_c * comp.world_per_c * (profile * 0.9f + (randf() - 0.4f) * 0.4f); // Per-strand curl: a two-frequency wave (in-plane + out-of-plane) unique to // each strand, growing toward the tip, so coplanar strands of one flat card @@ -521,7 +522,7 @@ bool BuildHairGroom(const asset::Mesh& mesh, const GroomParams& params, GroomDat // plus fine detail that breaks flat card facets. Independent of `frizz`: the // wave is what de-slabs coplanar cards into locks, so it must stay strong even // for "groomed" (low-flyaway) facegen styles, or the cards read as metal foil. - f32 camp = std::clamp(comp.hw * 0.85f, 0.010f, 0.020f) * (0.6f + 0.8f * randf()); + f32 camp = rx::Clamp(comp.hw * 0.85f, 0.010f, 0.020f) * (0.6f + 0.8f * randf()); // Trace root->tip, anchored on the scalp vertex and holding across = c_seed so // it follows one card strip (narrow world bandwidth prevents slab merging). Vec3 chain_prev = root_pos; @@ -535,16 +536,16 @@ bool BuildHairGroom(const asset::Mesh& mesh, const GroomParams& params, GroomDat f32 tt = static_cast(k) / (P - 1); f32 a = comp.a_root + (comp.a_tip - comp.a_root) * tt; chain_prev = ChainStep(comp, verts, a, c_seed, chain_prev, ha, hc); - f32 ramp = std::min(1.0f, tt / 0.18f); // keep roots on the scalp + f32 ramp = rx::Min(1.0f, tt / 0.18f); // keep roots on the scalp // Curl carries a small base amount near the root (not zero) so short, // crown-only cards break out of the coplanar slab too, then grows to the // tip; the very first point stays pinned on the scalp (k==0 above). f32 wv = camp * (0.2f + 0.8f * tt); Vec3 curl = - comp.normal * ((std::sin(cfreq * tt + cph1) + 0.4f * std::sin(cfreq * 2.7f * tt + cph3)) * wv) + + comp.normal * ((::sin(cfreq * tt + cph1) + 0.4f * ::sin(cfreq * 2.7f * tt + cph3)) * wv) + comp.across_dir * - ((std::sin(cfreq * 0.7f * tt + cph2) + 0.4f * std::sin(cfreq * 2.3f * tt + cph4)) * wv * 0.6f); - pos = chain_prev + comp.normal * (bulge * ramp) + curl + fdir * (famp * std::pow(tt, 1.4f)); + ((::sin(cfreq * 0.7f * tt + cph2) + 0.4f * ::sin(cfreq * 2.3f * tt + cph4)) * wv * 0.6f); + pos = chain_prev + comp.normal * (bulge * ramp) + curl + fdir * (famp * ::pow(tt, 1.4f)); } pts[(static_cast(emitted) * P + k) * 3 + 0] = pos.x; pts[(static_cast(emitted) * P + k) * 3 + 1] = pos.y; @@ -579,8 +580,8 @@ bool BuildHairGroom(const asset::Mesh& mesh, const GroomParams& params, GroomDat f32 head_r = 0; for (u32 i = 0; i < emitted; ++i) { Vec3 rp{roots[i * 3], roots[i * 3 + 1], roots[i * 3 + 2]}; - f32 horiz = std::sqrt((rp.x - scalp.x) * (rp.x - scalp.x) + (rp.z - scalp.z) * (rp.z - scalp.z)); - head_r = std::max(head_r, horiz); + f32 horiz = ::sqrt((rp.x - scalp.x) * (rp.x - scalp.x) + (rp.z - scalp.z) * (rp.z - scalp.z)); + head_r = rx::Max(head_r, horiz); } if (params.recenter) { for (size_t i = 0; i < pts.size(); i += 3) { @@ -630,9 +631,9 @@ bool BuildTestGroom(TestGroomStyle style, u32 guide_count, u32 seed, GroomData* auto cap_normal = [&](u32 s, u32 count) { f32 t = (static_cast(s) + 0.5f) / count; f32 y = 0.35f + 0.6f * t; - f32 r = std::sqrt(std::max(0.0f, 1.0f - y * y)); + f32 r = ::sqrt(rx::Max(0.0f, 1.0f - y * y)); f32 a = 2.399963f * static_cast(s); - return Vec3{r * std::cos(a), y, r * std::sin(a)}; + return Vec3{r * ::cos(a), y, r * ::sin(a)}; }; out->points.reserve(static_cast(guide_count) * P * 3); @@ -654,7 +655,7 @@ bool BuildTestGroom(TestGroomStyle style, u32 guide_count, u32 seed, GroomData* for (u32 k = 0; k < P; ++k) { if (k > 0) { f32 t = static_cast(k) / (P - 1); - Vec3 dir = Normalize(Lerp(nrm, {0, -1, 0}, std::pow(t, 0.7f))); + Vec3 dir = Normalize(Lerp(nrm, {0, -1, 0}, ::pow(t, 0.7f))); p = p + dir * segment; length_sum += Length(p - prev); prev = p; @@ -672,7 +673,7 @@ bool BuildTestGroom(TestGroomStyle style, u32 guide_count, u32 seed, GroomData* // strand binds to the next strand of its bundle (bundle cohesion), the // bundle leaders bind where their rest paths cross (the plait), and all // three leaders bind at the tip (the elastic). - const u32 per_bundle = std::max(1u, guide_count / 3); + const u32 per_bundle = rx::Max(1u, guide_count / 3); const u32 count = per_bundle * 3; const Vec3 nape{0, -0.15f * head_r, -0.98f * head_r}; const f32 len = 0.34f; @@ -684,8 +685,8 @@ bool BuildTestGroom(TestGroomStyle style, u32 guide_count, u32 seed, GroomData* for (u32 k = 0; k < P; ++k) { f32 t = static_cast(k) / (P - 1); f32 w = 18.849556f * t + phase; // three full crossings down the braid - centers[b * P + k] = nape + Vec3{amp * std::sin(w), -len * t, - -0.02f * t - 0.5f * amp * std::cos(2.0f * w)}; + centers[b * P + k] = nape + Vec3{amp * ::sin(w), -len * t, + -0.02f * t - 0.5f * amp * ::cos(2.0f * w)}; } } for (u32 b = 0; b < 3; ++b) { @@ -693,8 +694,8 @@ bool BuildTestGroom(TestGroomStyle style, u32 guide_count, u32 seed, GroomData* u32 s = b * per_bundle + i; // Per-strand offset within the bundle, tapering to the tip. f32 ang = randf() * 6.2831853f; - f32 rad = 0.004f * std::sqrt(randf()); - Vec3 off{rad * std::cos(ang), 0, rad * std::sin(ang)}; + f32 rad = 0.004f * ::sqrt(randf()); + Vec3 off{rad * ::cos(ang), 0, rad * ::sin(ang)}; Vec3 root = Normalize(centers[b * P] + off * 2.0f) * head_r; push_root(root); Vec3 prev{}; @@ -751,8 +752,8 @@ bool BuildTestGroom(TestGroomStyle style, u32 guide_count, u32 seed, GroomData* Vec3 root = nrm * head_r; // Small spread at the tie so the tail keeps volume. f32 ang = randf() * 6.2831853f; - f32 rad = 0.006f * std::sqrt(randf()); - Vec3 tie_off{rad * std::cos(ang), 0, rad * std::sin(ang)}; + f32 rad = 0.006f * ::sqrt(randf()); + Vec3 tie_off{rad * ::cos(ang), 0, rad * ::sin(ang)}; f32 tail_len = 0.30f + 0.05f * randf(); push_root(root); Vec3 prev{}; diff --git a/engine/render/geometry/hair_strands.cc b/engine/render/geometry/hair_strands.cc index 58141759..7ef2f211 100644 --- a/engine/render/geometry/hair_strands.cc +++ b/engine/render/geometry/hair_strands.cc @@ -1,10 +1,13 @@ #include "render/geometry/hair_strands.h" -#include -#include -#include +#include +#include +#include +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" #include "core/log.h" +#include "core/scalar.h" #include "shaders/hair_dom_ps_hlsl.h" #include "shaders/hair_ps_hlsl.h" #include "shaders/hair_vs_hlsl.h" @@ -179,7 +182,7 @@ void HairStrands::WriteGroomMaterial(Groom& g) { HairTierCaps caps = HairTierApply(g.tier, hair); if (!depth_pipeline_ || !dom_pipeline_) caps.transmittance_volume = false; GroomMaterial m; - std::memcpy(m.sigma_a, hair.sigma_a, sizeof(f32) * 3); + base::MemCopy(m.sigma_a, hair.sigma_a, sizeof(f32) * 3); m.beta_m = hair.beta_m; m.beta_n = hair.beta_n; m.alpha = hair.alpha; @@ -190,7 +193,7 @@ void HairStrands::WriteGroomMaterial(Groom& g) { m.caps = static_cast(PackCaps(caps) | (hair.color_mode == HairColorMode::kAuthored ? kCapAuthoredColor : 0u)); - std::memcpy(g.material.mapped, &m, sizeof(m)); + base::MemCopy(g.material.mapped, &m, sizeof(m)); } void HairStrands::SetGroomHair(u32 id, const HairSurfaceParameters& params) { @@ -214,8 +217,8 @@ bool HairStrands::WorldBounds(Vec3* lo, Vec3* hi) const { for (const Groom& g : grooms_) { if (!g.alive) continue; for (const HairPoint& p : g.host_points) { - mn = {std::min(mn.x, p.pos[0]), std::min(mn.y, p.pos[1]), std::min(mn.z, p.pos[2])}; - mx = {std::max(mx.x, p.pos[0]), std::max(mx.y, p.pos[1]), std::max(mx.z, p.pos[2])}; + mn = {rx::Min(mn.x, p.pos[0]), rx::Min(mn.y, p.pos[1]), rx::Min(mn.z, p.pos[2])}; + mx = {rx::Max(mx.x, p.pos[0]), rx::Max(mx.y, p.pos[1]), rx::Max(mx.z, p.pos[2])}; any = true; } } @@ -291,7 +294,7 @@ u32 HairStrands::Upload(Device& device, const GroomData& data, const GroomParams Groom g; g.host_points.resize(static_cast(n) * kPointsPerStrand); g.local_points.resize(static_cast(n) * kPointsPerStrand * 3); - std::memcpy(g.local_points.data(), data.points.data(), + base::MemCopy(g.local_points.data(), data.points.data(), g.local_points.size() * sizeof(f32)); base::Vector host_colors; host_colors.resize(static_cast(n) * 4); @@ -341,7 +344,7 @@ u32 HairStrands::Upload(Device& device, const GroomData& data, const GroomParams } return 0; } - std::memcpy(g.points[i].mapped, g.host_points.data(), points_bytes); + base::MemCopy(g.points[i].mapped, g.host_points.data(), points_bytes); } g.colors = device.CreateBufferWithData( Span(host_colors.data(), host_colors.size() * sizeof(f32)), kBufferUsageStorage); @@ -362,7 +365,7 @@ u32 HairStrands::Upload(Device& device, const GroomData& data, const GroomParams g.hair = HairPresetParams(HairPreset::kBrown); g.id = next_id_++; g.alive = true; - grooms_.push_back(std::move(g)); + grooms_.push_back(base::move(g)); WriteGroomMaterial(grooms_.back()); RX_INFO("hair: groom {} uploaded, {} guides x{} children, {} ribbon tris", grooms_.back().id, n, children, grooms_.back().index_count / 3); @@ -398,7 +401,7 @@ void HairStrands::SetGroomPoints(u32 id, const f32* positions, u32 count) { Groom* g = Find(id); if (!g || !positions) return; const size_t nodes = - std::min(static_cast(count) / 3, g->host_points.size()); + rx::Min(static_cast(count) / 3, g->host_points.size()); for (size_t i = 0; i < nodes; ++i) { HairPoint& hp = g->host_points[i]; hp.prev[0] = hp.pos[0]; @@ -448,9 +451,9 @@ void HairStrands::SeedCap(Device& device, const Vec3& head_center, f32 head_radi for (u32 s = 0; s < strand_count; ++s) { f32 t = (static_cast(s) + 0.5f) / strand_count; f32 y = 0.45f + 0.55f * t; - f32 r = std::sqrt(std::max(0.0f, 1.0f - y * y)); + f32 r = ::sqrt(rx::Max(0.0f, 1.0f - y * y)); f32 a = golden * static_cast(s); - Vec3 nrm{r * std::cos(a), y, r * std::sin(a)}; + Vec3 nrm{r * ::cos(a), y, r * ::sin(a)}; Vec3 root = nrm * head_radius; // local: scalp at origin for (u32 i = 0; i < kPointsPerStrand; ++i) { Vec3 p = root + nrm * (segment * static_cast(i)); @@ -510,7 +513,7 @@ void HairStrands::AddTransmittanceToGraph(RenderGraph& graph, const Frame& frame idle.ambient[2] = frame.ambient.z; idle.ambient[3] = frame.shadow_density; idle.debug[0] = static_cast(frame.debug_view); - std::memcpy(volume_params_[volume_slot_].mapped, &idle, sizeof(idle)); + base::MemCopy(volume_params_[volume_slot_].mapped, &idle, sizeof(idle)); } if (!active() || !frame.transmittance || !depth_pipeline_ || !dom_pipeline_ || !device_) return; @@ -523,13 +526,13 @@ void HairStrands::AddTransmittanceToGraph(RenderGraph& graph, const Frame& frame const Vec3 centre{(lo.x + hi.x) * 0.5f, (lo.y + hi.y) * 0.5f, (lo.z + hi.z) * 0.5f}; const Vec3 extent_v{hi.x - lo.x, hi.y - lo.y, hi.z - lo.z}; const f32 radius = - 0.5f * std::sqrt(extent_v.x * extent_v.x + extent_v.y * extent_v.y + + 0.5f * ::sqrt(extent_v.x * extent_v.x + extent_v.y * extent_v.y + extent_v.z * extent_v.z) + 0.02f; const Vec3 dir = Normalize(frame.sun_direction); const Vec3 eye{centre.x - dir.x * (radius * 2.0f), centre.y - dir.y * (radius * 2.0f), centre.z - dir.z * (radius * 2.0f)}; - const Vec3 up_ref = std::abs(dir.y) > 0.99f ? Vec3{0, 0, 1} : Vec3{0, 1, 0}; + const Vec3 up_ref = ::abs(dir.y) > 0.99f ? Vec3{0, 0, 1} : Vec3{0, 1, 0}; const f32 depth_range = radius * 4.0f; const Mat4 light_vp = Orthographic(-radius, radius, -radius, radius, 0.0f, depth_range) * LookAt(eye, centre, up_ref); @@ -549,14 +552,14 @@ void HairStrands::AddTransmittanceToGraph(RenderGraph& graph, const Frame& frame // needs it and it is a property of the volume, not of a groom. params.ambient[3] = frame.shadow_density; params.debug[0] = static_cast(frame.debug_view); - std::memcpy(volume_params_[slot].mapped, ¶ms, sizeof(params)); + base::MemCopy(volume_params_[slot].mapped, ¶ms, sizeof(params)); } // Publish this frame's simulated positions before either pass reads them. const u32 slot_bit = 1u << slot; for (Groom& g : grooms_) { if (!g.alive || !(g.stale & slot_bit)) continue; - std::memcpy(g.points[slot].mapped, g.host_points.data(), + base::MemCopy(g.points[slot].mapped, g.host_points.data(), g.host_points.size() * sizeof(HairPoint)); g.stale &= ~slot_bit; } @@ -621,7 +624,7 @@ void HairStrands::AddTransmittanceToGraph(RenderGraph& graph, const Frame& frame }, [this, layers, res, record_grooms](PassContext& ctx) { ColorAttachment att{.view = ctx.graph->image(layers).view, .load = LoadOp::kClear}; - ctx.cmd->BeginRendering({.extent = res, .colors = {&att, 1}}); + ctx.cmd->BeginRendering({.extent = res, .colors = base::Span(&att, 1)}); ctx.cmd->BindPipeline(dom_pipeline_); record_grooms(ctx, true); ctx.cmd->EndRendering(); @@ -640,7 +643,7 @@ void HairStrands::AddToGraph(RenderGraph& graph, ResourceHandle color, ResourceH const u32 slot_bit = 1u << slot; for (Groom& g : grooms_) { if (!g.alive || !(g.stale & slot_bit)) continue; - std::memcpy(g.points[slot].mapped, g.host_points.data(), + base::MemCopy(g.points[slot].mapped, g.host_points.data(), g.host_points.size() * sizeof(HairPoint)); g.stale &= ~slot_bit; } @@ -658,7 +661,7 @@ void HairStrands::AddToGraph(RenderGraph& graph, ResourceHandle color, ResourceH [this, color, depth, extent, frame, slot](PassContext& ctx) { ColorAttachment att{.view = ctx.graph->image(color).view, .load = LoadOp::kLoad}; DepthAttachment depth_att{.view = ctx.graph->image(depth).view, .load = LoadOp::kLoad}; - ctx.cmd->BeginRendering({.extent = extent, .colors = {&att, 1}, .depth = &depth_att}); + ctx.cmd->BeginRendering({.extent = extent, .colors = base::Span(&att, 1), .depth = &depth_att}); ctx.cmd->BindPipeline(draw_pipeline_); Vec3 sun = Normalize(frame.sun_direction); for (Groom& g : grooms_) { diff --git a/engine/render/geometry/imposters.cc b/engine/render/geometry/imposters.cc index 9f069016..40e52efd 100644 --- a/engine/render/geometry/imposters.cc +++ b/engine/render/geometry/imposters.cc @@ -1,13 +1,13 @@ #include "render/geometry/imposters.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include "base/containers/span.h" #include "core/log.h" +#include "core/scalar.h" #include "shaders/imposter_bake_ps_hlsl.h" #include "shaders/imposter_bake_vs_hlsl.h" #include "shaders/imposter_ps_hlsl.h" @@ -50,7 +50,7 @@ Vec3 HemiOctDecode(f32 u, f32 v) { Vec3 d; d.x = (ex - ey) * 0.5f; d.z = (ex + ey) * 0.5f; - d.y = 1.0f - std::abs(d.x) - std::abs(d.z); + d.y = 1.0f - ::abs(d.x) - ::abs(d.z); return Normalize(d); } @@ -115,7 +115,7 @@ bool ImposterPass::Initialize(Device& device, Format color_format, Format depth_ cmd.TextureBarriers(to_dst); BufferTextureCopy copy; copy.extent = {1, 1}; - cmd.CopyBufferToTexture(staging, white_, {©, 1}); + cmd.CopyBufferToTexture(staging, white_, base::Span(©, 1)); TextureBarrier to_read[1] = {Transition(white_, ResourceState::kCopyDst, ResourceState::kShaderReadFragment)}; cmd.TextureBarriers(to_read); @@ -142,7 +142,7 @@ void ImposterPass::Destroy(Device& device) { } u32 ImposterPass::Bake(Device& device, const asset::Mesh& mesh, - std::span materials) { + base::Span materials) { if (!bake_pipeline_ || mesh.lods.empty()) return kNoMesh; if (mesh_count_ >= kMaxMeshes) { RX_WARN("imposter atlas is full at {} meshes; this one is not baked", kMaxMeshes); @@ -156,14 +156,14 @@ u32 ImposterPass::Bake(Device& device, const asset::Mesh& mesh, // Bounds of the mesh (bake frames fit this sphere). Vec3 lo{1e30f, 1e30f, 1e30f}, hi{-1e30f, -1e30f, -1e30f}; for (const asset::Vertex& v : lod.vertices) { - lo = {std::min(lo.x, v.position[0]), std::min(lo.y, v.position[1]), - std::min(lo.z, v.position[2])}; - hi = {std::max(hi.x, v.position[0]), std::max(hi.y, v.position[1]), - std::max(hi.z, v.position[2])}; + lo = {rx::Min(lo.x, v.position[0]), rx::Min(lo.y, v.position[1]), + rx::Min(lo.z, v.position[2])}; + hi = {rx::Max(hi.x, v.position[0]), rx::Max(hi.y, v.position[1]), + rx::Max(hi.z, v.position[2])}; } Vec3 center = (lo + hi) * 0.5f; Vec3 ext = (hi - lo) * 0.5f; - const f32 radius = std::sqrt(ext.x * ext.x + ext.y * ext.y + ext.z * ext.z); + const f32 radius = ::sqrt(ext.x * ext.x + ext.y * ext.y + ext.z * ext.z); if (first) { albedo_atlas_ = device.CreateImage2D( @@ -212,14 +212,14 @@ u32 ImposterPass::Bake(Device& device, const asset::Mesh& mesh, colors[0] = {.view = albedo_atlas_.view, .load = load, .clear = {0, 0, 0, 0}}; colors[1] = {.view = normal_atlas_.view, .load = load, .clear = {0.5f, 1, 0.5f, 0}}; DepthAttachment depth{.view = bake_depth.view, .load = LoadOp::kClear, .clear = 1.0f}; - cmd.BeginRendering({.extent = {kAtlas, kAtlas}, .colors = {colors, 2}, .depth = &depth}); + cmd.BeginRendering({.extent = {kAtlas, kAtlas}, .colors = base::Span(colors, 2), .depth = &depth}); cmd.BindPipeline(bake_pipeline_); cmd.BindVertexBuffer(0, vertices, 0); cmd.BindIndexBuffer(indices, 0, IndexType::kUint32); for (u32 j = 0; j < kGrid; ++j) { for (u32 i = 0; i < kGrid; ++i) { Vec3 dir = HemiOctDecode((i + 0.5f) / kGrid, (j + 0.5f) / kGrid); - Vec3 up = std::abs(dir.y) > 0.98f ? Vec3{0, 0, 1} : Vec3{0, 1, 0}; + Vec3 up = ::abs(dir.y) > 0.98f ? Vec3{0, 0, 1} : Vec3{0, 1, 0}; Mat4 view = LookAt(center + dir * (radius * 2.0f), center, up); Mat4 proj = Orthographic(-radius, radius, -radius, radius, 0.1f, radius * 4.0f); const f32 x = static_cast(tile_x + i * kCell); @@ -229,7 +229,7 @@ u32 ImposterPass::Bake(Device& device, const asset::Mesh& mesh, // One draw per submesh, because the albedo map and the cutoff are the // material's: a tree is bark plus an alpha-masked foliage sheet, and // baking the two through one state loses the leaf shape entirely. - const u32 draws = std::max(1, static_cast(lod.submeshes.size())); + const u32 draws = rx::Max(1, static_cast(lod.submeshes.size())); for (u32 s = 0; s < draws; ++s) { const BakeMaterial material = s < materials.size() ? materials[s] : BakeMaterial{}; const GpuImage& albedo = material.base_color ? *material.base_color : white_; @@ -291,7 +291,7 @@ u32 ImposterPass::Bake(Device& device, const asset::Mesh& mesh, UploadMeshParams(device); // RX_IMPOSTER_DUMP= writes the baked albedo atlas for inspection. - if (const char* dump = std::getenv("RX_IMPOSTER_DUMP")) { + if (const char* dump = ::getenv("RX_IMPOSTER_DUMP")) { GpuBuffer readback = device.CreateBuffer(static_cast(kAtlas) * kAtlas * 4, kBufferUsageTransferDst, true); device.ImmediateSubmit([&](CommandList& cmd) { @@ -312,12 +312,12 @@ u32 ImposterPass::Bake(Device& device, const asset::Mesh& mesh, cmd.TextureBarriers(back); }); if (readback.mapped) { - std::FILE* f = std::fopen(dump, "wb"); + FILE* f = ::fopen(dump, "wb"); if (f) { - std::fprintf(f, "P6\n%u %u\n255\n", kAtlas, kAtlas); + ::fprintf(f, "P6\n%u %u\n255\n", kAtlas, kAtlas); const u8* px = static_cast(readback.mapped); - for (u32 i = 0; i < kAtlas * kAtlas; ++i) std::fwrite(px + i * 4, 1, 3, f); - std::fclose(f); + for (u32 i = 0; i < kAtlas * kAtlas; ++i) ::fwrite(px + i * 4, 1, 3, f); + ::fclose(f); RX_INFO("imposter atlas dumped to {}", dump); } } @@ -334,7 +334,7 @@ void ImposterPass::UploadMeshParams(Device& device) { Span(meshes_, mesh_count_ * sizeof(MeshParams)), kBufferUsageStorage); } -void ImposterPass::SetInstances(Device& device, std::span instances) { +void ImposterPass::SetInstances(Device& device, base::Span instances) { // Deferred: the split is rebuilt as the camera moves, so the buffer this // replaces may still be read by a submitted frame. if (instances_) device.DestroyBufferDeferred(instances_); @@ -374,7 +374,7 @@ void ImposterPass::AddToGraph(RenderGraph& graph, ResourceHandle color, Resource ColorAttachment att{.view = ctx.graph->image(color).view, .load = LoadOp::kLoad}; DepthAttachment depth_att{.view = ctx.graph->image(depth).view, .load = LoadOp::kLoad}; - ctx.cmd->BeginRendering({.extent = extent, .colors = {&att, 1}, .depth = &depth_att}); + ctx.cmd->BeginRendering({.extent = extent, .colors = base::Span(&att, 1), .depth = &depth_att}); ctx.cmd->BindPipeline(draw_pipeline_); ctx.cmd->BindTransient(0, {Bind::StorageBuffer(0, instances_, 0, instances_.size), Bind::Combined(1, albedo_atlas_.view, sampler_), diff --git a/engine/render/geometry/imposters.h b/engine/render/geometry/imposters.h index 3a01675a..4f88fc4d 100644 --- a/engine/render/geometry/imposters.h +++ b/engine/render/geometry/imposters.h @@ -15,9 +15,9 @@ // whose leaf shape lives entirely in a texture's alpha channel - baking their // vertex colours would produce untextured blobs. -#include #include "asset/mesh.h" +#include "base/containers/span.h" #include "core/math.h" #include "render/core/render_graph.h" #include "render/rhi/device.h" @@ -56,8 +56,8 @@ class ImposterPass { // it by, or kNoMesh when the atlas is full or the mesh is empty. `materials` // is parallel to lod 0's submeshes; a short span leaves the rest untextured. u32 Bake(Device& device, const asset::Mesh& mesh, - std::span materials = {}); - void SetInstances(Device& device, std::span instances); + base::Span materials = {}); + void SetInstances(Device& device, base::Span instances); struct Frame { Mat4 view_proj; diff --git a/engine/render/geometry/instance_store.cc b/engine/render/geometry/instance_store.cc index 87eb2b3d..7dd6de2c 100644 --- a/engine/render/geometry/instance_store.cc +++ b/engine/render/geometry/instance_store.cc @@ -1,10 +1,12 @@ #include "render/geometry/instance_store.h" -#include -#include -#include +#include +#include +#include "base/algorithm.h" +#include "base/containers/span.h" #include "core/log.h" +#include "core/scalar.h" namespace rx::render { @@ -12,15 +14,15 @@ namespace { f32 MaxScale(const Mat4 &transform) { const f32 *m = transform.m; - const f32 sx = std::sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]); - const f32 sy = std::sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6]); - const f32 sz = std::sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]); - return std::max(sx, std::max(sy, sz)); + const f32 sx = ::sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]); + const f32 sy = ::sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6]); + const f32 sz = ::sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]); + return rx::Max(sx, rx::Max(sy, sz)); } } // namespace -GpuBuffer InstanceStore::Upload(Device &device, std::span transforms) { +GpuBuffer InstanceStore::Upload(Device &device, base::Span transforms) { if (transforms.empty()) return {}; return device.CreateBufferWithData( ByteSpan(reinterpret_cast(transforms.data()), transforms.size_bytes()), @@ -30,35 +32,35 @@ GpuBuffer InstanceStore::Upload(Device &device, std::span transforms void InstanceStore::ComputeBounds(Group &group, const f32 mesh_center[3], f32 mesh_radius) { group.cullable = mesh_radius > 0; group.lod_scale = 0; - Vec3 lo{std::numeric_limits::max(), std::numeric_limits::max(), - std::numeric_limits::max()}; - Vec3 hi{-std::numeric_limits::max(), -std::numeric_limits::max(), - -std::numeric_limits::max()}; + Vec3 lo{FLT_MAX, FLT_MAX, + FLT_MAX}; + Vec3 hi{-FLT_MAX, -FLT_MAX, + -FLT_MAX}; for (const Mat4 &transform : group.transforms) { const Vec3 center = TransformPoint(transform, {mesh_center[0], mesh_center[1], mesh_center[2]}); const f32 scale = MaxScale(transform); const f32 radius = mesh_radius * scale; - group.lod_scale = std::max(group.lod_scale, scale); - lo.x = std::min(lo.x, center.x - radius); - lo.y = std::min(lo.y, center.y - radius); - lo.z = std::min(lo.z, center.z - radius); - hi.x = std::max(hi.x, center.x + radius); - hi.y = std::max(hi.y, center.y + radius); - hi.z = std::max(hi.z, center.z + radius); + group.lod_scale = rx::Max(group.lod_scale, scale); + lo.x = rx::Min(lo.x, center.x - radius); + lo.y = rx::Min(lo.y, center.y - radius); + lo.z = rx::Min(lo.z, center.z - radius); + hi.x = rx::Max(hi.x, center.x + radius); + hi.y = rx::Max(hi.y, center.y + radius); + hi.z = rx::Max(hi.z, center.z + radius); } - group.lod_scale = std::max(group.lod_scale, 1e-6f); + group.lod_scale = rx::Max(group.lod_scale, 1e-6f); group.bounds_center = (lo + hi) * 0.5f; group.bounds_radius = 0; for (const Mat4 &transform : group.transforms) { const Vec3 center = TransformPoint(transform, {mesh_center[0], mesh_center[1], mesh_center[2]}); const Vec3 delta = center - group.bounds_center; - group.bounds_radius = std::max( - group.bounds_radius, std::sqrt(Dot(delta, delta)) + mesh_radius * MaxScale(transform)); + group.bounds_radius = rx::Max( + group.bounds_radius, ::sqrt(Dot(delta, delta)) + mesh_radius * MaxScale(transform)); } } InstanceGroupHandle InstanceStore::Create(Device &device, u64 mesh, - std::span transforms, + base::Span transforms, const f32 mesh_center[3], f32 mesh_radius) { if (mesh == 0 || transforms.empty()) return {}; GpuBuffer buffer = Upload(device, transforms); @@ -93,7 +95,7 @@ InstanceStore::Group *InstanceStore::Resolve(InstanceGroupHandle handle) { } bool InstanceStore::Replace(Device &device, InstanceGroupHandle handle, - std::span transforms, const f32 mesh_center[3], + base::Span transforms, const f32 mesh_center[3], f32 mesh_radius) { Group *group = Resolve(handle); if (!group || transforms.empty()) return false; @@ -117,7 +119,7 @@ bool InstanceStore::Replace(Device &device, InstanceGroupHandle handle, // submitted prefix and make newly appended instances spawn in place. base::Vector previous; previous.assign(transforms.begin(), transforms.end()); - std::copy(submitted.begin(), submitted.end(), previous.begin()); + base::Copy(submitted.begin(), submitted.end(), previous.begin()); GpuBuffer previous_buffer = Upload(device, previous); if (!previous_buffer) { device.DestroyBuffer(replacement); diff --git a/engine/render/geometry/instance_store.h b/engine/render/geometry/instance_store.h index da9eb3be..43fc23b4 100644 --- a/engine/render/geometry/instance_store.h +++ b/engine/render/geometry/instance_store.h @@ -3,8 +3,8 @@ #include -#include +#include "base/containers/span.h" #include "core/math.h" #include "render/rhi/device.h" @@ -44,9 +44,9 @@ class InstanceStore { bool has_submitted_state = false; }; - InstanceGroupHandle Create(Device &device, u64 mesh, std::span transforms, + InstanceGroupHandle Create(Device &device, u64 mesh, base::Span transforms, const f32 mesh_center[3], f32 mesh_radius); - bool Replace(Device &device, InstanceGroupHandle handle, std::span transforms, + bool Replace(Device &device, InstanceGroupHandle handle, base::Span transforms, const f32 mesh_center[3], f32 mesh_radius); bool Destroy(Device &device, InstanceGroupHandle handle); void RefreshMesh(Device &device, u64 mesh, const f32 mesh_center[3], f32 mesh_radius, @@ -61,7 +61,7 @@ class InstanceStore { private: Group *Resolve(InstanceGroupHandle handle); static void ComputeBounds(Group &group, const f32 mesh_center[3], f32 mesh_radius); - static GpuBuffer Upload(Device &device, std::span transforms); + static GpuBuffer Upload(Device &device, base::Span transforms); base::Vector groups_; base::Vector free_; diff --git a/engine/render/geometry/ocean_fft.cc b/engine/render/geometry/ocean_fft.cc index fbb95f9b..a621f541 100644 --- a/engine/render/geometry/ocean_fft.cc +++ b/engine/render/geometry/ocean_fft.cc @@ -1,11 +1,12 @@ #include "render/geometry/ocean_fft.h" -#include -#include -#include -#include +#include +#include +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" #include "core/log.h" +#include "core/types.h" #include "shaders/ocean_fft_cs_hlsl.h" #include "shaders/ocean_finalize_cs_hlsl.h" #include "shaders/ocean_normals_cs_hlsl.h" @@ -14,6 +15,84 @@ namespace rx::render { namespace { +// The spectrum below was authored, and golden-imaged, against libstdc++'s +// std::mt19937 and std::normal_distribution. These reproduce both bit +// for bit: the engine's algorithm and seeding are fixed by the standard, and +// the distribution follows libstdc++ (Marsaglia's polar method over +// generate_canonical, the second deviate cached), including where +// its arithmetic is float and where it is double. +class Mt19937 { + public: + explicit Mt19937(u32 seed) { + state_[0] = seed; + for (u32 i = 1; i < kN; ++i) { + state_[i] = 1812433253u * (state_[i - 1] ^ (state_[i - 1] >> 30)) + i; + } + } + + u32 operator()() { + if (index_ >= kN) Twist(); + u32 y = state_[index_++]; + y ^= y >> 11; + y ^= (y << 7) & 0x9d2c5680u; + y ^= (y << 15) & 0xefc60000u; + y ^= y >> 18; + return y; + } + + private: + static constexpr u32 kN = 624; + static constexpr u32 kM = 397; + + void Twist() { + for (u32 i = 0; i < kN; ++i) { + const u32 y = (state_[i] & 0x80000000u) | (state_[(i + 1) % kN] & 0x7fffffffu); + state_[i] = state_[(i + kM) % kN] ^ (y >> 1) ^ ((y & 1u) ? 0x9908b0dfu : 0u); + } + index_ = 0; + } + + u32 state_[kN]; + u32 index_ = kN; +}; + +// std::normal_distribution with mean 0 and stddev 1, as libstdc++. +class StandardNormal { + public: + f32 operator()(Mt19937& rng) { + f32 result; + if (saved_available_) { + saved_available_ = false; + result = saved_; + } else { + f32 x, y, r2; + do { + x = static_cast(static_cast(2.0f * Canonical(rng)) - 1.0); + y = static_cast(static_cast(2.0f * Canonical(rng)) - 1.0); + r2 = x * x + y * y; + } while (r2 > 1.0 || r2 == 0.0); + const f32 mult = ::sqrtf(-2 * ::logf(r2) / r2); + saved_ = x * mult; + saved_available_ = true; + result = y * mult; + } + // The distribution's own scale and shift: 1 and 0, but +0 still turns a + // -0 into +0. + return result * 1.0f + 0.0f; + } + + private: + // generate_canonical: one 32-bit draw, clamped below 1. + static f32 Canonical(Mt19937& rng) { + const f32 value = static_cast(rng()) / 4294967296.0f; + return value >= 1.0f ? ::nextafterf(1.0f, 0.0f) : value; + } + + f32 saved_ = 0; + bool saved_available_ = false; +}; + + struct SpectrumPush { u32 size; f32 patch_size; @@ -46,13 +125,13 @@ f32 Phillips(f32 kx, f32 kz, f32 wind_speed, f32 wind_x, f32 wind_z) { if (k2 < 1e-8f) return 0.0f; const f32 g = 9.81f; f32 l = wind_speed * wind_speed / g; // largest wave from this wind - f32 k = std::sqrt(k2); + f32 k = ::sqrt(k2); f32 kdw = (kx * wind_x + kz * wind_z) / k; constexpr f32 kAmplitude = 0.6f; - f32 p = kAmplitude * std::exp(-1.0f / (k2 * l * l)) / (k2 * k2) * (kdw * kdw); + f32 p = kAmplitude * ::exp(-1.0f / (k2 * l * l)) / (k2 * k2) * (kdw * kdw); if (kdw < 0.0f) p *= 0.25f; // damp waves running against the wind const f32 small_cut = 0.35f; // meters - p *= std::exp(-k2 * small_cut * small_cut); + p *= ::exp(-k2 * small_cut * small_cut); return p; } @@ -110,20 +189,20 @@ bool OceanFft::Initialize(Device& device) { // h0(k) on the CPU with a FIXED seed: the whole animation is then a pure // function of time, which the golden-image harness depends on. - std::mt19937 rng(1337); - std::normal_distribution gauss(0.0f, 1.0f); + Mt19937 rng(1337); + StandardNormal gauss; const f32 wind_speed = 7.5f; const f32 inv_sqrt2 = 0.70710678f; f32 wind_x = 0.8f, wind_z = 0.6f; - std::vector h0(static_cast(kSize) * kSize * 4); + base::Vector h0(static_cast(kSize) * kSize * 4); for (u32 y = 0; y < kSize; ++y) { for (u32 x = 0; x < kSize; ++x) { f32 n = static_cast(x) - kSize * 0.5f; f32 m = static_cast(y) - kSize * 0.5f; f32 kx = 2.0f * 3.14159265f * n / kPatchSize; f32 kz = 2.0f * 3.14159265f * m / kPatchSize; - f32 ph = std::sqrt(Phillips(kx, kz, wind_speed, wind_x, wind_z)); - f32 phm = std::sqrt(Phillips(-kx, -kz, wind_speed, wind_x, wind_z)); + f32 ph = ::sqrt(Phillips(kx, kz, wind_speed, wind_x, wind_z)); + f32 phm = ::sqrt(Phillips(-kx, -kz, wind_speed, wind_x, wind_z)); size_t o = (static_cast(y) * kSize + x) * 4; h0[o + 0] = gauss(rng) * inv_sqrt2 * ph; h0[o + 1] = gauss(rng) * inv_sqrt2 * ph; @@ -133,11 +212,11 @@ bool OceanFft::Initialize(Device& device) { } GpuBuffer staging = device.CreateBuffer(h0.size() * sizeof(f32), kBufferUsageTransferSrc, true); if (!staging.mapped) return false; - std::memcpy(staging.mapped, h0.data(), h0.size() * sizeof(f32)); + base::MemCopy(staging.mapped, h0.data(), h0.size() * sizeof(f32)); device.ImmediateSubmit([&](CommandList& cmd) { cmd.Barrier(Transition(h0_, ResourceState::kUndefined, ResourceState::kCopyDst)); BufferTextureCopy copy; - cmd.CopyBufferToTexture(staging, h0_, {©, 1}); + cmd.CopyBufferToTexture(staging, h0_, base::Span(©, 1)); cmd.Barrier(Transition(h0_, ResourceState::kCopyDst, ResourceState::kShaderReadCompute)); TextureBarrier to_general[4] = { Transition(spectrum_[0], ResourceState::kUndefined, ResourceState::kGeneral), diff --git a/engine/render/geometry/particle_emitters.cc b/engine/render/geometry/particle_emitters.cc index 85e2f378..67dd7981 100644 --- a/engine/render/geometry/particle_emitters.cc +++ b/engine/render/geometry/particle_emitters.cc @@ -1,9 +1,11 @@ #include "render/geometry/particle_emitters.h" -#include -#include +#include +#include +#include "base/memory/move.h" #include "core/log.h" +#include "core/scalar.h" namespace rx::render { namespace { @@ -25,25 +27,25 @@ u64 HashCombine(u64 h, u64 v) { f32 LengthSq(const Vec3& v) { return v.x * v.x + v.y * v.y + v.z * v.z; } -f32 SafeLerpT(f32 t, f32 a, f32 b) { return b - a > 1e-5f ? std::clamp((t - a) / (b - a), 0.0f, 1.0f) : 1.0f; } +f32 SafeLerpT(f32 t, f32 a, f32 b) { return b - a > 1e-5f ? rx::Clamp((t - a) / (b - a), 0.0f, 1.0f) : 1.0f; } // Random direction in a cone of half angle `spread` around `axis`. Vec3 ConeDirection(const Vec3& axis, f32 spread, u32& rng) { if (spread <= 1e-3f) return axis; - Vec3 ref = std::abs(axis.y) < 0.95f ? Vec3{0, 1, 0} : Vec3{1, 0, 0}; + Vec3 ref = ::abs(axis.y) < 0.95f ? Vec3{0, 1, 0} : Vec3{1, 0, 0}; Vec3 t = Normalize(Cross(ref, axis)); Vec3 b = Cross(axis, t); f32 angle = spread * RandUnit(rng); f32 azimuth = 6.2831853f * RandUnit(rng); - f32 s = std::sin(angle); - return Normalize(axis * std::cos(angle) + t * (s * std::cos(azimuth)) + - b * (s * std::sin(azimuth))); + f32 s = ::sin(angle); + return Normalize(axis * ::cos(angle) + t * (s * ::cos(azimuth)) + + b * (s * ::sin(azimuth))); } } // namespace void ParticleEmitterSim::BeginFrame(f32 dt, const Vec3& camera, f32 max_distance) { - dt_ = std::min(dt, 0.05f); // clamp hitches + dt_ = rx::Min(dt, 0.05f); // clamp hitches camera_ = camera; max_distance_ = max_distance; ++frame_; @@ -76,11 +78,11 @@ void ParticleEmitterSim::AddInstance(u64 mesh_key, Vec3 ex = TransformDir(transform, {emitter.extent[0], 0, 0}); Vec3 ey = TransformDir(transform, {0, emitter.extent[1], 0}); Vec3 ez = TransformDir(transform, {0, 0, emitter.extent[2]}); - fresh.extent = {std::abs(ex.x) + std::abs(ey.x) + std::abs(ez.x), - std::abs(ex.y) + std::abs(ey.y) + std::abs(ez.y), - std::abs(ex.z) + std::abs(ey.z) + std::abs(ez.z)}; + fresh.extent = {::abs(ex.x) + ::abs(ey.x) + ::abs(ez.x), + ::abs(ex.y) + ::abs(ey.y) + ::abs(ez.y), + ::abs(ex.z) + ::abs(ey.z) + ::abs(ez.z)}; // Uniform scale of the instance transform, for the scalar quantities. - f32 scale = std::sqrt(LengthSq(TransformDir(transform, {1, 0, 0}))); + f32 scale = ::sqrt(LengthSq(TransformDir(transform, {1, 0, 0}))); fresh.spread = emitter.spread; fresh.speed_variation = emitter.speed_variation * scale; fresh.rate = emitter.rate; @@ -88,13 +90,13 @@ void ParticleEmitterSim::AddInstance(u64 mesh_key, fresh.life_variation = emitter.life_variation; fresh.size = emitter.size * scale; for (int k = 0; k < 4; ++k) fresh.color[k] = emitter.color[k]; - fresh.max_particles = std::min(emitter.max_particles, kMaxPerPool); + fresh.max_particles = rx::Min(emitter.max_particles, kMaxPerPool); fresh.additive = emitter.additive; // The renderer rewrote emitter.texture to a bindless index at upload. fresh.texture = static_cast(emitter.texture); - fresh.subtex_frames = std::max(emitter.subtex_frames, 1u); - fresh.subtex_cols = std::clamp(emitter.subtex_cols, 1u, 15u); - fresh.subtex_rows = std::clamp(emitter.subtex_rows, 1u, 15u); + fresh.subtex_frames = rx::Max(emitter.subtex_frames, 1u); + fresh.subtex_cols = rx::Clamp(emitter.subtex_cols, 1u, 15u); + fresh.subtex_rows = rx::Clamp(emitter.subtex_rows, 1u, 15u); fresh.has_ramp = emitter.has_color_ramp; for (int j = 0; j < 6; ++j) fresh.ramp_key[j] = emitter.ramp_key[j]; for (int j = 0; j < 3; ++j) { @@ -103,7 +105,7 @@ void ParticleEmitterSim::AddInstance(u64 mesh_key, fresh.rng = static_cast(key ^ (key >> 32)) | 1u; RX_DEBUG("particles: emitter pool at ({:.1f}, {:.1f}, {:.1f}) rate={:.1f} additive={}", origin.x, origin.y, origin.z, fresh.rate, fresh.additive); - pools_.emplace(key, std::move(fresh)); + pools_.emplace(key, base::move(fresh)); pool = pools_.find(key); } pool->last_frame = frame_; @@ -125,8 +127,8 @@ void ParticleEmitterSim::Step(Pool& pool) { ++i; } - pool.spawn_accumulator = std::min(pool.spawn_accumulator + pool.rate * dt_, 8.0f); - f32 speed = std::sqrt(LengthSq(pool.velocity)); + pool.spawn_accumulator = rx::Min(pool.spawn_accumulator + pool.rate * dt_, 8.0f); + f32 speed = ::sqrt(LengthSq(pool.velocity)); Vec3 axis = speed > 1e-4f ? pool.velocity * (1.0f / speed) : Vec3{0, 1, 0}; while (pool.spawn_accumulator >= 1.0f && pool.particles.size() < pool.max_particles) { pool.spawn_accumulator -= 1.0f; @@ -135,10 +137,10 @@ void ParticleEmitterSim::Step(Pool& pool) { pool.extent.y * RandSigned(pool.rng), pool.extent.z * RandSigned(pool.rng)}; particle.prev = particle.pos; - f32 birth_speed = std::max(speed + pool.speed_variation * RandSigned(pool.rng), 0.0f); + f32 birth_speed = rx::Max(speed + pool.speed_variation * RandSigned(pool.rng), 0.0f); particle.vel = ConeDirection(axis, pool.spread, pool.rng) * birth_speed; particle.life = - std::max(pool.life + pool.life_variation * RandSigned(pool.rng), 0.05f); + rx::Max(pool.life + pool.life_variation * RandSigned(pool.rng), 0.05f); particle.size = pool.size * (0.75f + 0.5f * RandUnit(pool.rng)); pool.particles.push_back(particle); } @@ -159,7 +161,7 @@ void ParticleEmitterSim::Simulate(base::Vector* lit, if (live_ >= kMaxTotal) break; f32 t = particle.age / particle.life; // Quick fade in, longer fade out; flames shrink with age, smoke grows. - f32 fade = std::min(t * 6.0f, 1.0f) * std::min((1.0f - t) * 2.5f, 1.0f); + f32 fade = rx::Min(t * 6.0f, 1.0f) * rx::Min((1.0f - t) * 2.5f, 1.0f); // BSPSysSimpleColorModifier colour-over-life: a 3-stop rgba gradient plus // an alpha fade in/out, overriding the fixed class tint when authored. f32 ramp[4] = {pool.color[0], pool.color[1], pool.color[2], pool.color[3]}; @@ -174,8 +176,8 @@ void ParticleEmitterSim::Simulate(base::Vector* lit, else if (t <= k[4]) seg(1, 1, 0.0f); else if (t < k[5]) seg(1, 2, SafeLerpT(t, k[4], k[5])); else seg(2, 2, 0.0f); - f32 fade_in = k[0] > 1e-4f ? std::clamp(t / k[0], 0.0f, 1.0f) : 1.0f; - f32 fade_out = k[1] < 0.9999f ? std::clamp((1.0f - t) / (1.0f - k[1]), 0.0f, 1.0f) : 1.0f; + f32 fade_in = k[0] > 1e-4f ? rx::Clamp(t / k[0], 0.0f, 1.0f) : 1.0f; + f32 fade_out = k[1] < 0.9999f ? rx::Clamp((1.0f - t) / (1.0f - k[1]), 0.0f, 1.0f) : 1.0f; ramp[3] *= fade_in * fade_out; } ParticleInstance inst; @@ -193,7 +195,7 @@ void ParticleEmitterSim::Simulate(base::Vector* lit, } else { u32 frame_index = 0; if (pool.subtex_frames > 1) { - frame_index = std::min(static_cast(t * static_cast(pool.subtex_frames)), + frame_index = rx::Min(static_cast(t * static_cast(pool.subtex_frames)), pool.subtex_frames - 1); } inst.tex = (pool.texture & 0xffffu) | (frame_index & 0xffu) << 16 | diff --git a/engine/render/geometry/particles.cc b/engine/render/geometry/particles.cc index 582fb1e0..4e39aa59 100644 --- a/engine/render/geometry/particles.cc +++ b/engine/render/geometry/particles.cc @@ -1,9 +1,10 @@ #include "render/geometry/particles.h" -#include -#include +#include +#include "base/memory/mem_ops.h" #include "core/log.h" +#include "core/scalar.h" #include "shaders/particle_ps_hlsl.h" #include "shaders/particle_sim_cs_hlsl.h" #include "shaders/particle_tex_ps_hlsl.h" @@ -145,7 +146,7 @@ bool ParticleSystem::Initialize(Device& device, Format color_format, sim_state_ = device.CreateBuffer(static_cast(kMaxParticles) * 64, kBufferUsageStorage, true); if (!sim_state_.mapped) return false; - std::memset(sim_state_.mapped, 0, static_cast(kMaxParticles) * 64); + base::MemSet(sim_state_.mapped, 0, static_cast(kMaxParticles) * 64); return true; } @@ -156,22 +157,22 @@ void ParticleSystem::AddToGraph(RenderGraph& graph, ResourceHandle color, Resour u32 frame_slot, BindingSetHandle bindless) { // Both sets share the slot's buffer: lit at 0, additive at the next // 256-aligned offset (safe for any minStorageBufferOffsetAlignment). - u32 count = std::min(static_cast(particles.size()), kMaxParticles); + u32 count = rx::Min(static_cast(particles.size()), kMaxParticles); u64 additive_offset = (static_cast(count) * sizeof(ParticleInstance) + 255) & ~255ull; u64 capacity = static_cast(kMaxParticles) * sizeof(ParticleInstance); - u64 additive_room = (capacity - std::min(capacity, additive_offset)) / sizeof(ParticleInstance); + u64 additive_room = (capacity - rx::Min(capacity, additive_offset)) / sizeof(ParticleInstance); u32 additive_count = - std::min(static_cast(additive.size()), static_cast(additive_room)); + rx::Min(static_cast(additive.size()), static_cast(additive_room)); if (count == 0 && additive_count == 0) return; u8* mapped = static_cast(buffers_[frame_slot].mapped); - if (count > 0) std::memcpy(mapped, particles.data(), count * sizeof(ParticleInstance)); + if (count > 0) base::MemCopy(mapped, particles.data(), count * sizeof(ParticleInstance)); if (additive_count > 0) { - std::memcpy(mapped + additive_offset, additive.data(), + base::MemCopy(mapped + additive_offset, additive.data(), additive_count * sizeof(ParticleInstance)); } GpuBuffer buffer = buffers_[frame_slot]; const ParticleCamera cam{frame.view_proj, frame.prev_view_proj}; - std::memcpy(camera_[frame_slot].mapped, &cam, sizeof(cam)); + base::MemCopy(camera_[frame_slot].mapped, &cam, sizeof(cam)); GpuBuffer camera = camera_[frame_slot]; graph.AddPass( @@ -246,7 +247,7 @@ void ParticleSystem::RecordSet(PassContext& ctx, ResourceHandle depth, const Gpu push.sun_color[1] = frame.sun_color.y; push.sun_color[2] = frame.sun_color.z; push.ambient = frame.ambient; - std::memcpy(push.cluster_params, frame.cluster_params, sizeof(push.cluster_params)); + base::MemCopy(push.cluster_params, frame.cluster_params, sizeof(push.cluster_params)); push.froxel_params[0] = frame.froxel_near; push.froxel_params[1] = frame.froxel_far; push.froxel_params[2] = frame.froxel_enabled ? 1.0f : 0.0f; @@ -261,12 +262,12 @@ void ParticleSystem::RecordSet(PassContext& ctx, ResourceHandle depth, const Gpu void ParticleSystem::SimulateAndDraw(RenderGraph& graph, ResourceHandle color, ResourceHandle depth, ResourceHandle motion, const Sim& sim, const Frame& frame, u32 frame_slot, BindingSetHandle bindless) { - u32 count = std::min(sim.count, kMaxParticles); + u32 count = rx::Min(sim.count, kMaxParticles); if (count == 0) return; GpuBuffer instances = buffers_[frame_slot]; GpuBuffer state = sim_state_; const ParticleCamera cam{frame.view_proj, frame.prev_view_proj}; - std::memcpy(camera_[frame_slot].mapped, &cam, sizeof(cam)); + base::MemCopy(camera_[frame_slot].mapped, &cam, sizeof(cam)); GpuBuffer camera = camera_[frame_slot]; graph.AddPass( diff --git a/engine/render/geometry/procedural_grass.cc b/engine/render/geometry/procedural_grass.cc index 32d5bde7..e966406c 100644 --- a/engine/render/geometry/procedural_grass.cc +++ b/engine/render/geometry/procedural_grass.cc @@ -1,10 +1,14 @@ #include "render/geometry/procedural_grass.h" -#include -#include -#include +#include +#include +#include "base/containers/array.h" +#include "base/containers/span.h" +#include "base/memory/mem_ops.h" +#include "base/numeric_limits.h" #include "core/log.h" +#include "core/scalar.h" #include "shaders/procedural_grass_bend_cs_hlsl.h" #include "shaders/procedural_grass_generate_cs_hlsl.h" #include "shaders/procedural_grass_prepass_ps_hlsl.h" @@ -56,40 +60,40 @@ bool BuildCandidateRing(const Vec3& camera, const f64 spacing = static_cast(base_spacing) * stride; const f64 padded_radius = outer_radius + tile_size * 0.5; const u64 requested_side = - static_cast(std::ceil(padded_radius * 2.0 / spacing)) + 1u; + static_cast(::ceil(padded_radius * 2.0 / spacing)) + 1u; const u64 requested = requested_side * requested_side; const bool capped = requested > budget; const u32 cells = capped - ? static_cast(std::floor(std::sqrt(static_cast(budget)))) + ? static_cast(::floor(::sqrt(static_cast(budget)))) : static_cast(requested_side); if (cells == 0) return false; const f64 center_x = capped ? camera.x : snapped_x; const f64 center_z = capped ? camera.z : snapped_z; - const f64 center_cell_x = std::floor(center_x / spacing); - const f64 center_cell_z = std::floor(center_z / spacing); + const f64 center_cell_x = ::floor(center_x / spacing); + const f64 center_cell_z = ::floor(center_z / spacing); const f64 min_fine_x = (center_cell_x - static_cast(cells / 2u)) * stride; const f64 min_fine_z = (center_cell_z - static_cast(cells / 2u)) * stride; const f64 max_fine_x = min_fine_x + static_cast(cells - 1u) * stride; const f64 max_fine_z = min_fine_z + static_cast(cells - 1u) * stride; - if (min_fine_x < std::numeric_limits::min() || - min_fine_x > std::numeric_limits::max() || - min_fine_z < std::numeric_limits::min() || - min_fine_z > std::numeric_limits::max() || - max_fine_x > std::numeric_limits::max() || - max_fine_z > std::numeric_limits::max()) + if (min_fine_x < base::MinMax::min() || + min_fine_x > base::MinMax::max() || + min_fine_z < base::MinMax::min() || + min_fine_z > base::MinMax::max() || + max_fine_x > base::MinMax::max() || + max_fine_z > base::MinMax::max()) return false; f32 actual_outer = outer_radius; if (capped) { const f32 covered_radius = - std::max((static_cast(cells) * 0.5f - 1.0f) * + rx::Max((static_cast(cells) * 0.5f - 1.0f) * static_cast(spacing), 0.0f); - actual_outer = std::min(actual_outer, covered_radius); + actual_outer = rx::Min(actual_outer, covered_radius); } if (actual_outer <= inner_radius) return false; @@ -106,7 +110,7 @@ bool BuildCandidateRing(const Vec3& camera, bool AllFinite(const f32* values, u32 count) { for (u32 i = 0; i < count; ++i) { - if (!std::isfinite(values[i])) + if (!::isfinite(values[i])) return false; } return true; @@ -415,7 +419,7 @@ void ProceduralGrass::Destroy(Device& device) { } bool ProceduralGrass::Prepare(const GrassDomain& domain, - std::span interactions, + base::Span interactions, const Frame& frame, u32 frame_slot) { if (!available() || frame_slot >= kFramesInFlight) @@ -423,19 +427,19 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, Slot& slot = slots_[frame_slot]; slot.active = false; - const u32 type_count = std::min(domain.type_count, kMaxTypes); + const u32 type_count = rx::Min(domain.type_count, kMaxTypes); const u32 surface_count = - domain.surfaces ? std::min(domain.surface_count, kMaxSurfaces) : 0; + domain.surfaces ? rx::Min(domain.surface_count, kMaxSurfaces) : 0; const bool valid_field = domain.samples && domain.sample_width >= 2 && domain.sample_height >= 2 && domain.sample_width <= kMaxFieldDimension && - domain.sample_height <= kMaxFieldDimension && std::isfinite(domain.origin_x) && - std::isfinite(domain.origin_z) && std::isfinite(domain.extent_x) && - std::isfinite(domain.extent_z) && domain.extent_x > 0.0f && domain.extent_z > 0.0f; + domain.sample_height <= kMaxFieldDimension && ::isfinite(domain.origin_x) && + ::isfinite(domain.origin_z) && ::isfinite(domain.extent_x) && + ::isfinite(domain.extent_z) && domain.extent_x > 0.0f && domain.extent_z > 0.0f; if (type_count == 0 || !domain.types || (!valid_field && surface_count == 0)) return false; - if (!std::isfinite(frame.camera_pos.x) || !std::isfinite(frame.camera_pos.y) || - !std::isfinite(frame.camera_pos.z)) + if (!::isfinite(frame.camera_pos.x) || !::isfinite(frame.camera_pos.y) || + !::isfinite(frame.camera_pos.z)) return false; if (!AllFinite(frame.view_proj.m, 16) || !AllFinite(frame.prev_view_proj.m, 16)) return false; @@ -455,7 +459,7 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, for (const f32* values : {type.base_color, type.tip_color, type.dimensions, type.shape, type.material}) { for (u32 value = 0; value < 4; ++value) { - if (!std::isfinite(values[value])) + if (!::isfinite(values[value])) return false; } } @@ -465,7 +469,7 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, GrassGenerationSettings settings = SanitizeGrassSettings(domain.settings); const u32 max_surface_candidates = valid_field ? kMaxCandidates / 4u : kMaxCandidates; - const u32 spacing_bits = std::bit_cast(settings.candidate_spacing); + const u32 spacing_bits = rx::BitCast(settings.candidate_spacing); SurfaceUploadState& surface_upload = slot.surface_upload; const bool cache_surface_source = domain.surface_revision != 0 && domain.surfaces; const bool surface_source_hit = @@ -490,20 +494,20 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, } const f64 snapped_x = - std::floor(static_cast(frame.camera_pos.x) / settings.stream_tile_size + 0.5) * + ::floor(static_cast(frame.camera_pos.x) / settings.stream_tile_size + 0.5) * settings.stream_tile_size; const f64 snapped_z = - std::floor(static_cast(frame.camera_pos.z) / settings.stream_tile_size + 0.5) * + ::floor(static_cast(frame.camera_pos.z) / settings.stream_tile_size + 0.5) * settings.stream_tile_size; - std::array rings{}; + base::Array rings{}; u32 ring_count = 0; u32 terrain_candidates = 0; f32 terrain_radius = 0.0f; f32 far_edge = 0.0f; bool far_active = false; if (valid_field) { - const f32 lod_start = std::min(settings.density_lod_start, settings.stream_radius); - const f32 lod_end = std::min(settings.density_lod_end, settings.stream_radius); + const f32 lod_start = rx::Min(settings.density_lod_start, settings.stream_radius); + const f32 lod_end = rx::Min(settings.density_lod_end, settings.stream_radius); const f32 lod_mid = (lod_start + lod_end) * 0.5f; const f32 desired_inner[3] = {0.0f, lod_start, lod_mid}; const f32 ring_outer[3] = {lod_mid, lod_end, settings.stream_radius}; @@ -511,7 +515,7 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, f32 covered_radius = 0.0f; for (u32 i = 0; i < 3; ++i) { CandidateRing ring; - const f32 inner_radius = std::min(desired_inner[i], covered_radius); + const f32 inner_radius = rx::Min(desired_inner[i], covered_radius); if (!BuildCandidateRing(frame.camera_pos, snapped_x, snapped_z, settings.stream_tile_size, settings.candidate_spacing, inner_radius, @@ -532,7 +536,7 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, const u32 distant_strides[3] = {8u, 16u, 32u}; for (u32 i = 0; i < 3 && far_edge < settings.far_radius; ++i) { CandidateRing ring; - const f32 outer = std::min(settings.far_radius, far_edge * 2.0f); + const f32 outer = rx::Min(settings.far_radius, far_edge * 2.0f); if (!BuildCandidateRing(frame.camera_pos, snapped_x, snapped_z, settings.stream_tile_size, settings.candidate_spacing, far_edge * 0.75f, @@ -562,7 +566,7 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, if (valid_field && !field_hit) { const u64 field_bytes = static_cast(domain.sample_width) * domain.sample_height * sizeof(GrassFieldSample); - std::memcpy(slot.field.mapped, domain.samples, field_bytes); + base::MemCopy(slot.field.mapped, domain.samples, field_bytes); device_->FlushBuffer(slot.field, 0, field_bytes); if (domain.sample_revision != 0) { field_upload = {.source = domain.samples, @@ -584,7 +588,7 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, type_upload.count == type_count; if (!type_hit) { const u64 type_bytes = static_cast(type_count) * sizeof(GrassType); - std::memcpy(slot.types.mapped, domain.types, type_bytes); + base::MemCopy(slot.types.mapped, domain.types, type_bytes); device_->FlushBuffer(slot.types, 0, type_bytes); if (domain.type_revision != 0) { type_upload = {.source = domain.types, @@ -597,11 +601,11 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, } const u32 interaction_count = - std::min(static_cast(interactions.size()), kMaxInteractions); + rx::Min(static_cast(interactions.size()), kMaxInteractions); if (interaction_count > 0) { const u64 interaction_bytes = static_cast(interaction_count) * sizeof(GrassInteraction); - std::memcpy(slot.interactions.mapped, interactions.data(), interaction_bytes); + base::MemCopy(slot.interactions.mapped, interactions.data(), interaction_bytes); device_->FlushBuffer(slot.interactions, 0, interaction_bytes); } @@ -617,25 +621,25 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, auto* gpu_surfaces = static_cast(slot.surfaces.mapped); for (u32 i = 0; i < surface_count; ++i) { const GrassSurfaceTriangle& src = domain.surfaces[i]; - if (!std::isfinite(src.density) || src.density <= 0.0f || - !std::isfinite(src.growth) || src.growth <= 0.0f) + if (!::isfinite(src.density) || src.density <= 0.0f || + !::isfinite(src.growth) || src.growth <= 0.0f) continue; u32 count = GrassSurfaceCandidateCount(src, settings.candidate_spacing); const u32 remaining = max_surface_candidates - surface_candidates; - count = std::min(count, remaining); + count = rx::Min(count, remaining); if (count == 0) continue; GpuSurface& dst = gpu_surfaces[copied_surfaces++]; - std::copy_n(src.p0, 3, dst.p0_density); - dst.p0_density[3] = std::clamp(src.density, 0.0f, 1.0f); - std::copy_n(src.p1, 3, dst.p1_growth); - dst.p1_growth[3] = std::max(src.growth, 0.0f); - std::copy_n(src.p2, 3, dst.p2_pad); + for (int k = 0; k < 3; ++k) dst.p0_density[k] = src.p0[k]; + dst.p0_density[3] = rx::Clamp(src.density, 0.0f, 1.0f); + for (int k = 0; k < 3; ++k) dst.p1_growth[k] = src.p1[k]; + dst.p1_growth[3] = rx::Max(src.growth, 0.0f); + for (int k = 0; k < 3; ++k) dst.p2_pad[k] = src.p2[k]; dst.p2_pad[3] = 0.0f; dst.meta[0] = surface_candidates; dst.meta[1] = count; dst.meta[2] = src.surface_id; - dst.meta[3] = std::min(src.type, type_count - 1); + dst.meta[3] = rx::Min(src.type, type_count - 1); surface_candidates += count; if (surface_candidates >= max_surface_candidates) break; @@ -665,9 +669,9 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, const f32 bend_extent = settings.stream_radius * 2.0f; const f32 bend_texel = bend_extent / static_cast(kBendResolution); const f32 bend_origin_x = - std::floor((frame.camera_pos.x - bend_extent * 0.5f) / bend_texel) * bend_texel; + ::floor((frame.camera_pos.x - bend_extent * 0.5f) / bend_texel) * bend_texel; const f32 bend_origin_z = - std::floor((frame.camera_pos.z - bend_extent * 0.5f) / bend_texel) * bend_texel; + ::floor((frame.camera_pos.z - bend_extent * 0.5f) / bend_texel) * bend_texel; const bool fields_overlap = preserve_bend_history && bend_origin_x < bend_origin_[0] + bend_extent_ && bend_origin_x + bend_extent > bend_origin_[0] && @@ -677,39 +681,39 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, preserve_bend_history && bend_history_active_ ? (frame.time >= bend_last_update_time_ ? frame.time - bend_last_update_time_ - : std::max(frame.delta_time, 0.0f)) + : rx::Max(frame.delta_time, 0.0f)) : 0.0f; f32 bend_max_strength = fields_overlap ? bend_max_strength_ : 0.0f; if (settings.bend_recovery_time > 0.0f) { bend_max_strength *= - std::exp2(-recovery_elapsed / settings.bend_recovery_time); + ::exp2(-recovery_elapsed / settings.bend_recovery_time); } - const f32 bend_height_origin = std::floor(frame.camera_pos.y / 64.0f) * 64.0f; + const f32 bend_height_origin = ::floor(frame.camera_pos.y / 64.0f) * 64.0f; for (u32 i = 0; i < interaction_count; ++i) { const GrassInteraction& interaction = interactions[i]; if (!AllFinite(interaction.position_radius, 4) || !AllFinite(interaction.direction_strength, 4) || interaction.position_radius[3] <= 0.0f || - std::fabs(interaction.direction_strength[3]) <= 1e-4f) + ::fabs(interaction.direction_strength[3]) <= 1e-4f) continue; - const f32 closest_x = std::clamp(interaction.position_radius[0], bend_origin_x, + const f32 closest_x = rx::Clamp(interaction.position_radius[0], bend_origin_x, bend_origin_x + bend_extent); - const f32 closest_z = std::clamp(interaction.position_radius[2], bend_origin_z, + const f32 closest_z = rx::Clamp(interaction.position_radius[2], bend_origin_z, bend_origin_z + bend_extent); const f32 dx = interaction.position_radius[0] - closest_x; const f32 dz = interaction.position_radius[2] - closest_z; - const f32 effective_radius = std::min( - std::max(interaction.position_radius[3], bend_texel * 0.75f), bend_extent); + const f32 effective_radius = rx::Min( + rx::Max(interaction.position_radius[3], bend_texel * 0.75f), bend_extent); if (dx * dx + dz * dz > effective_radius * effective_radius) continue; const f32 strength = - std::min(std::fabs(interaction.direction_strength[3]), 2.0f); + rx::Min(::fabs(interaction.direction_strength[3]), 2.0f); const f32 relative_height = interaction.position_radius[1] - bend_height_origin; const f32 radius = effective_radius; - if (std::fabs(relative_height) * strength > 60000.0f || + if (::fabs(relative_height) * strength > 60000.0f || radius * strength > 60000.0f) continue; - bend_max_strength = std::max(bend_max_strength, strength); + bend_max_strength = rx::Max(bend_max_strength, strength); } const bool bend_active = bend_max_strength > 1e-3f; @@ -722,7 +726,7 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, bend.prev_field[1] = bend_origin_[1]; bend.prev_field[2] = bend_extent_; bend.prev_field[3] = 1.0f / bend_extent_; - bend.params[0] = std::max(frame.delta_time, 0.0f); + bend.params[0] = rx::Max(frame.delta_time, 0.0f); bend.params[1] = recovery_elapsed; bend.params[2] = settings.bend_recovery_time; bend.params[3] = static_cast(interaction_count); @@ -758,7 +762,7 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, generation_domain.bend_field[1] = bend.field[1]; generation_domain.bend_field[2] = bend.height[0]; generation_domain.bend_field[3] = bend_active ? bend.field[3] : 0.0f; - std::memcpy(slot.generation_domain.mapped, &generation_domain, + base::MemCopy(slot.generation_domain.mapped, &generation_domain, sizeof(generation_domain)); GenerationPush common{}; @@ -768,7 +772,7 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, common.camera_stream[3] = settings.stream_radius; common.placement[0] = settings.candidate_spacing; common.counts[1] = copied_surfaces; - common.counts[2] = std::min(settings.max_blades, kMaxBlades); + common.counts[2] = rx::Min(settings.max_blades, kMaxBlades); common.counts[3] = kMaxBlades; common.geometry_fade[0] = settings.geometry_lod_start; common.geometry_fade[1] = settings.geometry_lod_end; @@ -804,12 +808,12 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, // there instead of at the stream boundary so the handoff between the // detailed and distant tiers has no height seam. generation.camera_stream[3] = far_edge; - generation.geometry_fade[2] = std::max( - far_edge - std::max(far_edge * 0.08f, settings.candidate_spacing), 0.0f); + generation.geometry_fade[2] = rx::Max( + far_edge - rx::Max(far_edge * 0.08f, settings.candidate_spacing), 0.0f); generation.geometry_fade[3] = far_edge; } else if (terrain_radius < settings.fade_end) { const f32 fade_length = settings.fade_end - settings.fade_start; - generation.geometry_fade[2] = std::max(terrain_radius - fade_length, 0.0f); + generation.geometry_fade[2] = rx::Max(terrain_radius - fade_length, 0.0f); generation.geometry_fade[3] = terrain_radius; } generation.control[0] = 1; @@ -823,7 +827,7 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, slot.generation[slot.generation_count++] = finalize; const DrawCamera draw_camera{frame.view_proj, frame.prev_view_proj}; - std::memcpy(slot.draw_camera.mapped, &draw_camera, sizeof(draw_camera)); + base::MemCopy(slot.draw_camera.mapped, &draw_camera, sizeof(draw_camera)); DrawPush draw{}; draw.camera_time[0] = frame.camera_pos.x; @@ -841,12 +845,12 @@ bool ProceduralGrass::Prepare(const GrassDomain& domain, draw.wind[0] = frame.wind_speed; draw.wind[1] = frame.wind_yaw; draw.wind[2] = frame.gustiness; - draw.wind[3] = std::clamp(frame.delta_time, 0.0f, 0.1f); + draw.wind[3] = rx::Clamp(frame.delta_time, 0.0f, 0.1f); draw.jitter_lod[0] = frame.jitter[0]; draw.jitter_lod[1] = frame.jitter[1]; draw.jitter_lod[2] = settings.geometry_lod_start; draw.jitter_lod[3] = settings.geometry_lod_end; - draw.control[0] = std::bit_cast(std::max(frame.pixel_scale, 0.0f)); + draw.control[0] = rx::BitCast(rx::Max(frame.pixel_scale, 0.0f)); draw.control[1] = type_count; // control[2] (segment count) and control[3] (arena base) are per-draw; the // three tier draws fill them in Draw(). @@ -867,7 +871,7 @@ void ProceduralGrass::AddGeneration(RenderGraph& graph, u32 frame_slot) { const GrassSurfaceTriangle* bend_surface_source = slot.bend_surface_source; const f32 bend_update_time = slot.bend_update_time; const f32 bend_max_strength = slot.bend_max_strength; - const std::array phases = slot.generation; + const base::Array phases = slot.generation; const u32 phase_count = slot.generation_count; graph.AddPass( "procedural_grass_generate", [](RenderGraph::PassBuilder&) {}, diff --git a/engine/render/geometry/procedural_grass.h b/engine/render/geometry/procedural_grass.h index 26ab1306..00aa1c90 100644 --- a/engine/render/geometry/procedural_grass.h +++ b/engine/render/geometry/procedural_grass.h @@ -1,13 +1,13 @@ #ifndef RX_RENDER_PROCEDURAL_GRASS_H_ #define RX_RENDER_PROCEDURAL_GRASS_H_ -#include -#include -#include -#include -#include +#include +#include "base/containers/array.h" +#include "base/containers/span.h" +#include "base/numeric_limits.h" #include "core/math.h" +#include "core/scalar.h" #include "render/core/render_graph.h" #include "render/rhi/device.h" @@ -84,7 +84,7 @@ struct GrassGenerationSettings { inline GrassGenerationSettings SanitizeGrassSettings(GrassGenerationSettings settings) { const GrassGenerationSettings defaults; auto finite_or = [](f32 value, f32 fallback) { - return std::isfinite(value) ? value : fallback; + return ::isfinite(value) ? value : fallback; }; settings.candidate_spacing = finite_or(settings.candidate_spacing, defaults.candidate_spacing); @@ -107,33 +107,33 @@ inline GrassGenerationSettings SanitizeGrassSettings(GrassGenerationSettings set settings.bend_recovery_time = finite_or(settings.bend_recovery_time, defaults.bend_recovery_time); - settings.candidate_spacing = std::clamp(settings.candidate_spacing, 0.08f, 8.0f); - settings.stream_tile_size = std::clamp(settings.stream_tile_size, 1.0f, 512.0f); + settings.candidate_spacing = rx::Clamp(settings.candidate_spacing, 0.08f, 8.0f); + settings.stream_tile_size = rx::Clamp(settings.stream_tile_size, 1.0f, 512.0f); settings.stream_radius = - std::clamp(settings.stream_radius, settings.candidate_spacing, 512.0f); + rx::Clamp(settings.stream_radius, settings.candidate_spacing, 512.0f); if (settings.far_radius > settings.stream_radius) { settings.far_radius = - std::min({settings.far_radius, settings.stream_radius * 8.0f, 4096.0f}); + rx::Min({settings.far_radius, settings.stream_radius * 8.0f, 4096.0f}); } else { settings.far_radius = 0.0f; } - settings.density_lod_start = std::max(settings.density_lod_start, 0.0f); - settings.density_lod_end = std::max( + settings.density_lod_start = rx::Max(settings.density_lod_start, 0.0f); + settings.density_lod_end = rx::Max( settings.density_lod_end, settings.density_lod_start + settings.candidate_spacing); - settings.far_density = std::clamp(settings.far_density, 0.0f, 1.0f); - settings.geometry_lod_start = std::max(settings.geometry_lod_start, 0.0f); + settings.far_density = rx::Clamp(settings.far_density, 0.0f, 1.0f); + settings.geometry_lod_start = rx::Max(settings.geometry_lod_start, 0.0f); settings.geometry_lod_end = - std::max(settings.geometry_lod_end, + rx::Max(settings.geometry_lod_end, settings.geometry_lod_start + settings.candidate_spacing); const f32 latest_fade_start = - std::max(settings.stream_radius - settings.candidate_spacing, 0.0f); - settings.fade_start = std::clamp(settings.fade_start, 0.0f, latest_fade_start); + rx::Max(settings.stream_radius - settings.candidate_spacing, 0.0f); + settings.fade_start = rx::Clamp(settings.fade_start, 0.0f, latest_fade_start); settings.fade_end = - std::clamp(settings.fade_end, settings.fade_start + settings.candidate_spacing, + rx::Clamp(settings.fade_end, settings.fade_start + settings.candidate_spacing, settings.stream_radius); - settings.max_slope_cos = std::clamp(settings.max_slope_cos, 0.0f, 1.0f); - settings.bend_recovery_time = std::clamp(settings.bend_recovery_time, 0.0f, 3600.0f); - settings.max_blades = std::clamp(settings.max_blades, 1u, 262144u); + settings.max_slope_cos = rx::Clamp(settings.max_slope_cos, 0.0f, 1.0f); + settings.bend_recovery_time = rx::Clamp(settings.bend_recovery_time, 0.0f, 3600.0f); + settings.max_blades = rx::Clamp(settings.max_blades, 1u, 262144u); return settings; } @@ -143,14 +143,14 @@ inline u32 GrassSurfaceCandidateCount(const GrassSurfaceTriangle& triangle, const Vec3 b{triangle.p1[0], triangle.p1[1], triangle.p1[2]}; const Vec3 c{triangle.p2[0], triangle.p2[1], triangle.p2[2]}; const f32 area = 0.5f * Length(Cross(b - a, c - a)); - const f32 spacing = std::clamp( - std::isfinite(candidate_spacing) ? candidate_spacing : 0.42f, 0.08f, 8.0f); - if (!std::isfinite(area) || area <= 1e-6f) + const f32 spacing = rx::Clamp( + ::isfinite(candidate_spacing) ? candidate_spacing : 0.42f, 0.08f, 8.0f); + if (!::isfinite(area) || area <= 1e-6f) return 0; const f64 candidates = - std::ceil(static_cast(area) / (static_cast(spacing) * spacing)); + ::ceil(static_cast(area) / (static_cast(spacing) * spacing)); return static_cast( - std::clamp(candidates, 1.0, static_cast(std::numeric_limits::max()))); + rx::Clamp(candidates, 1.0, static_cast(base::MinMax::max()))); } // Non-owning semantic field submitted for one RenderFrame call. The heightfield @@ -238,7 +238,7 @@ class ProceduralGrass { bool EnsureSampleCount(Device& device, u32 samples); bool Prepare(const GrassDomain& domain, - std::span interactions, + base::Span interactions, const Frame& frame, u32 frame_slot); void AddGeneration(RenderGraph& graph, u32 frame_slot); @@ -348,7 +348,7 @@ class ProceduralGrass { f32 bend_update_time = 0.0f; f32 bend_max_strength = 0.0f; bool bend_active = false; - std::array generation{}; + base::Array generation{}; u32 generation_count = 0; DrawPush draw{}; FieldUploadState field_upload; diff --git a/engine/render/geometry/shore_wetting.cc b/engine/render/geometry/shore_wetting.cc index 5412d737..96474cc0 100644 --- a/engine/render/geometry/shore_wetting.cc +++ b/engine/render/geometry/shore_wetting.cc @@ -1,6 +1,6 @@ #include "render/geometry/shore_wetting.h" -#include +#include #include "core/log.h" #include "shaders/shore_wetting_cs_hlsl.h" @@ -86,8 +86,8 @@ void ShoreWetting::BeginFrame(const Vec3& camera_eye) { // Snap the origin to the texel grid so the field does not shimmer as the // camera slides; the min corner sits half an extent behind the eye. const f32 texel = kExtent / static_cast(kResolution); - origin_[0] = std::floor((camera_eye.x - kExtent * 0.5f) / texel) * texel; - origin_[1] = std::floor((camera_eye.z - kExtent * 0.5f) / texel) * texel; + origin_[0] = ::floor((camera_eye.x - kExtent * 0.5f) / texel) * texel; + origin_[1] = ::floor((camera_eye.z - kExtent * 0.5f) / texel) * texel; if (!have_prev_) { prev_origin_[0] = origin_[0]; prev_origin_[1] = origin_[1]; diff --git a/engine/render/geometry/water.cc b/engine/render/geometry/water.cc index f05bfa34..0b54b01e 100644 --- a/engine/render/geometry/water.cc +++ b/engine/render/geometry/water.cc @@ -1,6 +1,7 @@ #include "render/geometry/water.h" #include "asset/mesh.h" +#include "base/memory/unique_pointer.h" #include "core/log.h" #include "render/pipeline/mesh_pipeline.h" #include "shaders/copy_cs_hlsl.h" @@ -9,13 +10,13 @@ namespace rx::render { -std::unique_ptr WaterPass::Create(Device& device, Format color_format, +base::UniquePointer WaterPass::Create(Device& device, Format color_format, Format motion_format, Format depth_format, BindingLayoutHandle globals_layout, BindingLayoutHandle material_layout, BindingLayoutHandle environment_layout, BindingLayoutHandle bindless_layout) { - auto pass = std::unique_ptr(new WaterPass(device)); + auto pass = base::UniquePointer(new WaterPass(device)); pass->sampler_ = device.GetSampler({.address_u = AddressMode::kClampToEdge, .address_v = AddressMode::kClampToEdge, diff --git a/engine/render/geometry/water.h b/engine/render/geometry/water.h index 5d43b503..45df7734 100644 --- a/engine/render/geometry/water.h +++ b/engine/render/geometry/water.h @@ -1,8 +1,8 @@ #ifndef RX_RENDER_WATER_H_ #define RX_RENDER_WATER_H_ -#include +#include "base/memory/unique_pointer.h" #include "render/core/bindless.h" #include "render/core/render_graph.h" #include "render/geometry/adaptive_water.h" @@ -18,7 +18,7 @@ class WaterPass { public: // Set layouts mirror the water.ps bindings: 0 mesh globals (+tlas), // 1 material, 2 environment, 3 bindless, 4 the opaque snapshot. - static std::unique_ptr Create(Device& device, Format color_format, + static base::UniquePointer Create(Device& device, Format color_format, Format motion_format, Format depth_format, BindingLayoutHandle globals_layout, BindingLayoutHandle material_layout, diff --git a/engine/render/geometry/water_field.cc b/engine/render/geometry/water_field.cc index 9102ab82..4795345d 100644 --- a/engine/render/geometry/water_field.cc +++ b/engine/render/geometry/water_field.cc @@ -1,11 +1,12 @@ #include "render/geometry/water_field.h" -#include -#include -#include -#include +#include +#include +#include "base/containers/span.h" +#include "base/memory/mem_ops.h" #include "core/log.h" +#include "core/scalar.h" #include "shaders/water_field_cs_hlsl.h" namespace rx::render { @@ -137,7 +138,7 @@ bool WaterField::Initialize(Device& device) { Transition(rings_[r][p], ResourceState::kUndefined, ResourceState::kGeneral); for (u32 p = 0; p < 2; ++p) to_general[n++] = Transition(mask_[p], ResourceState::kUndefined, ResourceState::kGeneral); - cmd.TextureBarriers(std::span(to_general, n)); + cmd.TextureBarriers(base::Span(to_general, n)); }); return true; } @@ -174,8 +175,8 @@ void WaterField::AddToGraph(RenderGraph& graph, const UpdateParams& params, prev_origin[r][0] = origin_[r][0]; prev_origin[r][1] = origin_[r][1]; texel_world[r] = 2.0f * kRingHalfExtent[r] / static_cast(kSize); - origin_[r][0] = std::round(params.camera_pos.x / texel_world[r]) * texel_world[r]; - origin_[r][1] = std::round(params.camera_pos.z / texel_world[r]) * texel_world[r]; + origin_[r][0] = ::round(params.camera_pos.x / texel_world[r]) * texel_world[r]; + origin_[r][1] = ::round(params.camera_pos.z / texel_world[r]) * texel_world[r]; if (!centered_) { // first frame: no history, resample lands out of bounds prev_origin[r][0] = origin_[r][0] + 1e6f; prev_origin[r][1] = origin_[r][1] + 1e6f; @@ -194,16 +195,16 @@ void WaterField::AddToGraph(RenderGraph& graph, const UpdateParams& params, gp.ring[r][2] = kRingHalfExtent[r]; gp.ring[r][3] = texel_world[r]; } - std::memcpy(params_[slot].mapped, &gp, sizeof(gp)); + base::MemCopy(params_[slot].mapped, &gp, sizeof(gp)); // Interaction matrices: identical for every ring/phase, so one CB per frame. const WaterFieldCamera camera{params.view_proj, params.inv_view_proj}; - std::memcpy(camera_[slot].mapped, &camera, sizeof(camera)); + base::MemCopy(camera_[slot].mapped, &camera, sizeof(camera)); // Object disturbances for this frame (bounded). - u32 disturbance_count = std::min(params.disturbance_count, kMaxDisturbances); + u32 disturbance_count = rx::Min(params.disturbance_count, kMaxDisturbances); if (disturbance_count > 0 && params.disturbances) { - std::memcpy(disturbances_[slot].mapped, params.disturbances, + base::MemCopy(disturbances_[slot].mapped, params.disturbances, disturbance_count * sizeof(GpuDisturbance)); } @@ -230,7 +231,7 @@ void WaterField::AddToGraph(RenderGraph& graph, const UpdateParams& params, ctx.cmd->MemoryBarrier(BarrierScope::kComputeWrite, BarrierScope::kComputeRead); ctx.cmd->BindPipeline(pipeline_); - f32 inv = kDriftSpeed / std::sqrt(kDriftDirX * kDriftDirX + kDriftDirZ * kDriftDirZ); + f32 inv = kDriftSpeed / ::sqrt(kDriftDirX * kDriftDirX + kDriftDirZ * kDriftDirZ); f32 drift_x = kDriftDirX * inv; f32 drift_z = kDriftDirZ * inv; // Depth for slot 4: the real prepass depth when available, else the ring diff --git a/engine/render/geometry/wboit.cc b/engine/render/geometry/wboit.cc index 1fd08f30..ae2807ba 100644 --- a/engine/render/geometry/wboit.cc +++ b/engine/render/geometry/wboit.cc @@ -1,8 +1,9 @@ #include "render/geometry/wboit.h" -#include +#include #include "asset/primitives.h" +#include "base/memory/mem_ops.h" #include "core/log.h" #include "render/rhi/device.h" #include "shaders/fullscreen_vs_slang.h" @@ -140,7 +141,7 @@ ResourceHandle WboitPass::AddToGraph(RenderGraph& graph, ResourceHandle color, R WboitFrame constants{}; constants.view_proj = view_proj; - std::memcpy(constants.cluster_params, lighting.cluster_params, + base::MemCopy(constants.cluster_params, lighting.cluster_params, sizeof(constants.cluster_params)); constants.froxel_params[0] = lighting.froxel_near; constants.froxel_params[1] = lighting.froxel_far; @@ -170,7 +171,7 @@ ResourceHandle WboitPass::AddToGraph(RenderGraph& graph, ResourceHandle color, R ctx.cmd->BeginRendering( {.extent = {width, height}, .colors = colors, .depth = &depth_att}); ctx.cmd->BindPipeline(geom_pipeline_); - std::memcpy(frames_[slot].mapped, &constants, sizeof(constants)); + base::MemCopy(frames_[slot].mapped, &constants, sizeof(constants)); ctx.cmd->BindTransient( 0, {Bind::StorageBuffer(0, lighting.lights, 0, lighting.lights.size), Bind::StorageBuffer(1, lighting.cluster_counts, 0, lighting.cluster_counts.size), diff --git a/engine/render/gi/ddgi.cc b/engine/render/gi/ddgi.cc index 247c9e27..709241c0 100644 --- a/engine/render/gi/ddgi.cc +++ b/engine/render/gi/ddgi.cc @@ -1,9 +1,12 @@ #include "render/gi/ddgi.h" -#include -#include +#include +#include +#include "base/memory/mem_ops.h" +#include "base/memory/unique_pointer.h" #include "core/log.h" +#include "core/scalar.h" #include "render/gi/raytracing.h" #include "shaders/ddgi_blend_cs_hlsl.h" #include "shaders/ddgi_border_cs_hlsl.h" @@ -57,12 +60,12 @@ void FrameRotation(u32 frame_index, f32 out_rows[12]) { f32 u3 = static_cast(hash(frame_index + 2) & 0xffffff) / 16777215.0f; f32 angle = u1 * 6.2831853f; f32 z = u2 * 2.0f - 1.0f; - f32 r = std::sqrt(std::max(0.0f, 1.0f - z * z)); + f32 r = ::sqrt(rx::Max(0.0f, 1.0f - z * z)); f32 phi = u3 * 6.2831853f; - Vec3 axis{r * std::cos(phi), r * std::sin(phi), z}; + Vec3 axis{r * ::cos(phi), r * ::sin(phi), z}; - f32 c = std::cos(angle); - f32 s = std::sin(angle); + f32 c = ::cos(angle); + f32 s = ::sin(angle); f32 t = 1.0f - c; f32 rows[12] = { t * axis.x * axis.x + c, t * axis.x * axis.y - s * axis.z, @@ -72,15 +75,15 @@ void FrameRotation(u32 frame_index, f32 out_rows[12]) { t * axis.x * axis.z - s * axis.y, t * axis.y * axis.z + s * axis.x, t * axis.z * axis.z + c, 0, }; - std::memcpy(out_rows, rows, sizeof(rows)); + base::MemCopy(out_rows, rows, sizeof(rows)); } } // namespace -std::unique_ptr DdgiSystem::Create(Device& device, TextureView sky_view, +base::UniquePointer DdgiSystem::Create(Device& device, TextureView sky_view, SamplerHandle sky_sampler, BindlessRegistry& bindless) { - auto ddgi = std::unique_ptr(new DdgiSystem(device)); + auto ddgi = base::UniquePointer(new DdgiSystem(device)); ddgi->sky_view_ = sky_view; ddgi->sky_sampler_ = sky_sampler; ddgi->bindless_ = &bindless; @@ -173,9 +176,9 @@ void DdgiSystem::AddToGraph(RenderGraph& graph, RayTracingContext& raytracing, u // every probe represents, so history resets and re-converges. f32 spacing = settings_.probe_spacing; Vec3 extent{(kProbesX - 1) * spacing, (kProbesY - 1) * spacing, (kProbesZ - 1) * spacing}; - Vec3 origin{std::floor((camera.x - extent.x * 0.5f) / spacing) * spacing, - std::floor((camera.y - extent.y * 0.5f) / spacing) * spacing, - std::floor((camera.z - extent.z * 0.5f) / spacing) * spacing}; + Vec3 origin{::floor((camera.x - extent.x * 0.5f) / spacing) * spacing, + ::floor((camera.y - extent.y * 0.5f) / spacing) * spacing, + ::floor((camera.z - extent.z * 0.5f) / spacing) * spacing}; bool snapped = origin.x != origin_.x || origin.y != origin_.y || origin.z != origin_.z; origin_ = origin; bool reset = !history_valid_ || snapped; @@ -195,7 +198,7 @@ void DdgiSystem::AddToGraph(RenderGraph& graph, RayTracingContext& raytracing, u volume.params[2] = spacing * 4.0f; // max ray distance volume.params[3] = settings_.energy_scale; GpuBuffer& volume_buffer = volume_buffers_[frame_index % 2]; - std::memcpy(volume_buffer.mapped, &volume, sizeof(volume)); + base::MemCopy(volume_buffer.mapped, &volume, sizeof(volume)); RaysPush rays_push{}; FrameRotation(frame_index, rays_push.rotation); @@ -244,7 +247,7 @@ void DdgiSystem::AddToGraph(RenderGraph& graph, RayTracingContext& raytracing, u // Blend rays into both atlases. auto blend = [&](TextureView atlas_view, u32 mode, u32 width, u32 height) { BlendPush push{}; - std::memcpy(push.rotation, rays_push.rotation, sizeof(push.rotation)); + base::MemCopy(push.rotation, rays_push.rotation, sizeof(push.rotation)); push.mode = mode; push.ray_count = kRaysPerProbe; push.reset = reset ? 1u : 0u; diff --git a/engine/render/gi/ddgi.h b/engine/render/gi/ddgi.h index 17a287d6..f13740d2 100644 --- a/engine/render/gi/ddgi.h +++ b/engine/render/gi/ddgi.h @@ -1,8 +1,8 @@ #ifndef RX_RENDER_DDGI_H_ #define RX_RENDER_DDGI_H_ -#include +#include "base/memory/unique_pointer.h" #include "core/math.h" #include "render/core/bindless.h" #include "render/atmosphere/environment.h" @@ -38,7 +38,7 @@ class DdgiSystem { f32 energy_scale = 1.0f; }; - static std::unique_ptr Create(Device& device, TextureView sky_view, + static base::UniquePointer Create(Device& device, TextureView sky_view, SamplerHandle sky_sampler, BindlessRegistry& bindless); ~DdgiSystem(); diff --git a/engine/render/gi/denoiser_nrd.cc b/engine/render/gi/denoiser_nrd.cc index 612beaf5..2ecfb001 100644 --- a/engine/render/gi/denoiser_nrd.cc +++ b/engine/render/gi/denoiser_nrd.cc @@ -1,7 +1,8 @@ #include "render/gi/denoiser_nrd.h" -#include +#include +#include "base/memory/mem_ops.h" #include "core/log.h" #include "render/rhi/device.h" #include "render/rhi/vulkan_interop.h" @@ -56,7 +57,7 @@ Format ToFormat(nrd::Format format) { } void CopyMatrix(float (&dst)[16], const Mat4& m) { - std::memcpy(dst, m.m, sizeof(float) * 16); + base::MemCopy(dst, m.m, sizeof(float) * 16); } } // namespace @@ -78,7 +79,7 @@ bool NrdDenoiser::Initialize(Device& device, Extent2D extent) { }; nrd::InstanceCreationDesc creation{}; creation.denoisers = denoiser_descs; - creation.denoisersNum = static_cast(std::size(denoiser_descs)); + creation.denoisersNum = static_cast((sizeof(denoiser_descs) / sizeof(denoiser_descs[0]))); if (nrd::CreateInstance(creation, instance_) != nrd::Result::SUCCESS || !instance_) { RX_ERROR("nrd: instance creation failed"); instance_ = nullptr; @@ -401,7 +402,7 @@ void NrdDenoiser::CreatePools(Device& device, Extent2D extent) { add(out_shadow_); add(out_diffuse_); add(out_specular_); - cmd.TextureBarriers({barriers.data(), barriers.size()}); + cmd.TextureBarriers(base::Span(barriers.data(), barriers.size())); }); } @@ -681,7 +682,7 @@ void NrdDenoiser::RecordDispatches(PassContext& ctx, u32 identifier) { constant_cursor_ = 0; // overflow guard; should not happen } dynamic_offset = static_cast(constant_cursor_ * constant_slot_size_); - std::memcpy(static_cast(constant_ring_.mapped) + dynamic_offset, d.constantBufferData, + base::MemCopy(static_cast(constant_ring_.mapped) + dynamic_offset, d.constantBufferData, d.constantBufferDataSize); constant_cursor_++; } diff --git a/engine/render/gi/light_grid.cc b/engine/render/gi/light_grid.cc index e7087153..a6d1ac6d 100644 --- a/engine/render/gi/light_grid.cc +++ b/engine/render/gi/light_grid.cc @@ -1,8 +1,9 @@ #include "render/gi/light_grid.h" -#include -#include +#include +#include +#include "base/memory/mem_ops.h" #include "core/log.h" #include "shaders/light_grid_cs_hlsl.h" @@ -55,9 +56,9 @@ void LightGrid::AddToGraph(RenderGraph& graph, const GpuBuffer& lights, u32 ligh for (u32 c = 0; c < kCascades; ++c) { f32 extent = kCascade0Extent * static_cast(1u << c); f32 cell_size = extent / static_cast(kCells); - Vec3 origin{std::floor((camera.x - extent * 0.5f) / cell_size) * cell_size, - std::floor((camera.y - extent * 0.5f) / cell_size) * cell_size, - std::floor((camera.z - extent * 0.5f) / cell_size) * cell_size}; + Vec3 origin{::floor((camera.x - extent * 0.5f) / cell_size) * cell_size, + ::floor((camera.y - extent * 0.5f) / cell_size) * cell_size, + ::floor((camera.z - extent * 0.5f) / cell_size) * cell_size}; params.cascade[c][0] = origin.x; params.cascade[c][1] = origin.y; params.cascade[c][2] = origin.z; @@ -68,7 +69,7 @@ void LightGrid::AddToGraph(RenderGraph& graph, const GpuBuffer& lights, u32 ligh params.info[2] = kMaxPerCell; params.info[3] = 0; GpuBuffer& params_buffer = params_buffers_[frame_index % 2]; - std::memcpy(params_buffer.mapped, ¶ms, sizeof(params)); + base::MemCopy(params_buffer.mapped, ¶ms, sizeof(params)); u32 capped = light_count < kMaxLights ? light_count : kMaxLights; graph.AddPass( diff --git a/engine/render/gi/local_shadows.cc b/engine/render/gi/local_shadows.cc index 84c36cfd..c0b66357 100644 --- a/engine/render/gi/local_shadows.cc +++ b/engine/render/gi/local_shadows.cc @@ -1,11 +1,14 @@ #include "render/gi/local_shadows.h" -#include -#include -#include +#include +#include +#include +#include "base/functional/function.h" #include "core/log.h" +#include "core/scalar.h" #include "render/gi/shadow.h" +#include "core/sort.h" namespace rx::render { namespace { @@ -68,12 +71,13 @@ void LocalShadows::Assign(PointLight* lights, u32 count, const Vec3& camera, u32 f32 dist2 = dx * dx + dy * dy + dz * dz; if (dist2 > kMaxShadowDistance * kMaxShadowDistance) continue; candidates[candidate_count++] = { - i, light.color_intensity[3] * radius * radius / std::max(dist2, 1.0f), + i, light.color_intensity[3] * radius * radius / rx::Max(dist2, 1.0f), type == 0 ? 6u : 1u}; if (candidate_count == kMaxCandidates) break; } - std::sort(candidates, candidates + candidate_count, - [](const Candidate& a, const Candidate& b) { return a.score > b.score; }); + // Stable: equal scores keep light order, which decides who gets faces. + rx::StableSort(candidates, candidates + candidate_count, + [](const Candidate& a, const Candidate& b) { return a.score > b.score; }); face_count_ = 0; FaceData* upload = static_cast(face_buffers_[frame_slot].mapped); @@ -84,7 +88,7 @@ void LocalShadows::Assign(PointLight* lights, u32 count, const Vec3& camera, u32 light.params[3] = static_cast(face_count_ + 1); Vec3 pos{light.pos_radius[0], light.pos_radius[1], light.pos_radius[2]}; - f32 far = std::max(light.pos_radius[3], 0.25f); + f32 far = rx::Max(light.pos_radius[3], 0.25f); u32 type = static_cast(light.direction_type[3] + 0.5f); for (u32 f = 0; f < cand.faces; ++f) { Mat4 view; @@ -92,10 +96,10 @@ void LocalShadows::Assign(PointLight* lights, u32 count, const Vec3& camera, u32 if (type == 1) { // spot: one face down the cone, slightly overscanned Vec3 dir = Normalize(Vec3{light.direction_type[0], light.direction_type[1], light.direction_type[2]}); - Vec3 up = std::abs(dir.y) < 0.99f ? Vec3{0, 1, 0} : Vec3{1, 0, 0}; + Vec3 up = ::abs(dir.y) < 0.99f ? Vec3{0, 1, 0} : Vec3{1, 0, 0}; view = LookAt(pos, pos + dir, up); - f32 outer = std::acos(std::clamp(light.params[1], -1.0f, 1.0f)); - fov = std::clamp(2.2f * outer, 0.2f, 2.9f); + f32 outer = ::acos(rx::Clamp(light.params[1], -1.0f, 1.0f)); + fov = rx::Clamp(2.2f * outer, 0.2f, 2.9f); } else { // point: 95-degree cube faces so the sampling inset stays valid view = LookAt(pos, pos + kFaceDirs[f], kFaceUps[f]); fov = 1.658f; @@ -121,13 +125,13 @@ void LocalShadows::Assign(PointLight* lights, u32 count, const Vec3& camera, u32 } void LocalShadows::Render(CommandList& cmd, PipelineHandle pipeline, - const std::function& draw) { + const base::Function& draw) { // Persistent atlas: shader-read between frames, depth target while writing. TextureBarrier to_write = Transition( atlas_, atlas_initialized_ ? ResourceState::kShaderReadFragment : ResourceState::kUndefined, ResourceState::kDepthTarget); atlas_initialized_ = true; - cmd.TextureBarriers({&to_write, 1}); + cmd.TextureBarriers(base::Span(&to_write, 1)); DepthAttachment depth{ .view = atlas_.view, .load = LoadOp::kClear, .store = StoreOp::kStore, .clear = 1.0f}; @@ -147,7 +151,7 @@ void LocalShadows::Render(CommandList& cmd, PipelineHandle pipeline, TextureBarrier to_read = Transition(atlas_, ResourceState::kDepthTarget, ResourceState::kShaderReadFragment); - cmd.TextureBarriers({&to_read, 1}); + cmd.TextureBarriers(base::Span(&to_read, 1)); // The froxel volume samples the atlas from compute; widen visibility (the // layout above already suits any sampled read). cmd.MemoryBarrier(BarrierScope::kAllCommands, BarrierScope::kComputeRead); diff --git a/engine/render/gi/local_shadows.h b/engine/render/gi/local_shadows.h index 4ba77779..b46ee926 100644 --- a/engine/render/gi/local_shadows.h +++ b/engine/render/gi/local_shadows.h @@ -9,8 +9,8 @@ // The claimed light's params.w carries 1 + its first face index (0 = // unshadowed), so the shader side needs no extra per-light buffer. -#include +#include "base/functional/function.h" #include "core/math.h" #include "render/pipeline/mesh_pipeline.h" #include "render/rhi/device.h" @@ -51,7 +51,7 @@ class LocalShadows { // face for the caller's culled submissions. Handles the atlas transitions // (persistent image: shader-read between frames). void Render(CommandList& cmd, PipelineHandle pipeline, - const std::function& draw); + const base::Function& draw); u32 face_count() const { return face_count_; } const GpuImage& atlas() const { return atlas_; } diff --git a/engine/render/gi/path_scene_history.h b/engine/render/gi/path_scene_history.h index 88166c42..4552f6e5 100644 --- a/engine/render/gi/path_scene_history.h +++ b/engine/render/gi/path_scene_history.h @@ -1,9 +1,10 @@ #ifndef RX_RENDER_PATH_SCENE_HISTORY_H_ #define RX_RENDER_PATH_SCENE_HISTORY_H_ -#include -#include +#include +#include "base/containers/span.h" +#include "base/memory/mem_ops.h" #include "render/gi/raytracing.h" namespace rx::render { @@ -11,10 +12,10 @@ namespace rx::render { class PathSceneHistory { public: bool Update(base::Vector& instances, - std::span bones) { + base::Span bones) { bool changed = instances.size() != previous_.size() || bones.size() != bones_.size(); if (!changed && !bones.empty()) - changed = std::memcmp(bones.data(), bones_.data(), bones.size_bytes()) != 0; + changed = base::MemCompare(bones.data(), bones_.data(), bones.size_bytes()) != 0; if (instances.size() > 0xfffffffeu - next_id_) { previous_.clear(); next_id_ = 0; @@ -36,7 +37,7 @@ class PathSceneHistory { private: static bool SameTransform(const Mat4& a, const Mat4& b) { - return std::memcmp(&a, &b, sizeof(Mat4)) == 0; + return base::MemCompare(&a, &b, sizeof(Mat4)) == 0; } static bool SameGeometry(const RayTracingContext::Instance& a, const RayTracingContext::Instance& b) { diff --git a/engine/render/gi/path_tracer.cc b/engine/render/gi/path_tracer.cc index 81ca2128..eaaa0704 100644 --- a/engine/render/gi/path_tracer.cc +++ b/engine/render/gi/path_tracer.cc @@ -1,13 +1,14 @@ #include "render/gi/path_tracer.h" -#include -#include +#include #include "core/log.h" #include "render/gi/raytracing.h" #include "render/rhi/device.h" #include "shaders/pathtrace_cs_hlsl.h" #if defined(RX_HAS_NRD) +#include "base/memory/mem_ops.h" +#include "base/numeric_limits.h" #include "shaders/pathtrace_composite_cs_hlsl.h" #include "shaders/pathtrace_gbuffer_cs_hlsl.h" #endif @@ -170,7 +171,7 @@ void PathTracer::AddToGraph(RenderGraph& graph, RayTracingContext& raytracing, u BindingSetHandle bindless_set, TextureView sky_view, SamplerHandle sky_sampler, ResourceHandle output, const Frame& frame) { if (!available()) return; - if (frame.reset || accumulated_samples_ > std::numeric_limits::max() - spp_) + if (frame.reset || accumulated_samples_ > base::MinMax::max() - spp_) accumulated_samples_ = 0; u32 sample_base = accumulated_samples_; accumulated_samples_ += spp_; @@ -186,7 +187,7 @@ void PathTracer::AddToGraph(RenderGraph& graph, RayTracingContext& raytracing, u frame, sample_base](PassContext& ctx) { const u32 slot = frame.frame_index % 2; const PathCamera camera{frame.inv_view_proj, frame.view_proj, frame.prev_view_proj}; - std::memcpy(camera_[slot].mapped, &camera, sizeof(camera)); + base::MemCopy(camera_[slot].mapped, &camera, sizeof(camera)); PathPush push{}; push.camera_pos[0] = frame.camera_pos.x; @@ -247,7 +248,7 @@ void PathTracer::AddGbufferPass(RenderGraph& graph, RayTracingContext& raytracin const u32 slot = frame.frame_index % 2; const PathCamera camera{frame.inv_view_proj, frame.view_proj, frame.prev_view_proj}; - std::memcpy(camera_[slot].mapped, &camera, sizeof(camera)); + base::MemCopy(camera_[slot].mapped, &camera, sizeof(camera)); items.push_back(Bind::Uniform(8, camera_[slot], 0, sizeof(PathCamera))); items.push_back(Bind::StorageBuffer(9, raytracing.motion_buffer(tlas_slot))); @@ -270,7 +271,7 @@ void PathTracer::AddGbufferPass(RenderGraph& graph, RayTracingContext& raytracin push.bounces = bounces_; ctx.cmd->BindPipeline(gbuffer_pipeline_); - ctx.cmd->BindTransient(0, {items.data(), items.size()}); + ctx.cmd->BindTransient(0, base::Span(items.data(), items.size())); ctx.cmd->BindSet(1, bindless_set); ctx.cmd->Push(push); ctx.cmd->Dispatch2D(extent_); diff --git a/engine/render/gi/raytracing.cc b/engine/render/gi/raytracing.cc index a7926aba..a3b1a434 100644 --- a/engine/render/gi/raytracing.cc +++ b/engine/render/gi/raytracing.cc @@ -1,11 +1,14 @@ #include "render/gi/raytracing.h" -#include -#include -#include +#include #include "asset/mesh.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/numeric_limits.h" #include "core/log.h" +#include "core/scalar.h" #include "render/rhi/device.h" namespace rx::render { @@ -45,9 +48,9 @@ base::Vector BlasGeometries(const GpuMesh& mesh) { } // namespace -std::unique_ptr RayTracingContext::Create(Device& device) { +base::UniquePointer RayTracingContext::Create(Device& device) { if (!device.caps().raytracing) return nullptr; - auto context = std::unique_ptr(new RayTracingContext(device)); + auto context = base::UniquePointer(new RayTracingContext(device)); if (!context->EnsureTlasCapacity(context->fallback_tlas_, 1)) { RX_ERROR("fallback tlas allocation failed"); return nullptr; @@ -99,7 +102,7 @@ bool RayTracingContext::BuildBlasFromGeometries( // stroke boundaries, so avoid a second blocking submit for compaction there. AccelCompactionQueryHandle query = skip_compaction ? AccelCompactionQueryHandle{} : device_.CreateCompactionQuery(1); - BlasBuildDesc desc{.geometries = {geometries.data(), geometries.size()}, + BlasBuildDesc desc{.geometries = base::Span(geometries.data(), geometries.size()), .allow_compaction = static_cast(query)}; AccelSizes sizes = device_.GetBlasSizes(desc); if (sizes.accel_bytes == 0) { @@ -250,7 +253,7 @@ bool RayTracingContext::ReserveSkinnedBlas(u64 key, // No compaction: a compacted structure cannot be refit, which is the whole // point of this path. - BlasBuildDesc desc{.geometries = {geometries.data(), geometries.size()}, + BlasBuildDesc desc{.geometries = base::Span(geometries.data(), geometries.size()), .allow_update = true}; AccelSizes sizes = device_.GetBlasSizes(desc); if (sizes.accel_bytes == 0) return false; @@ -262,7 +265,7 @@ bool RayTracingContext::ReserveSkinnedBlas(u64 key, // One arena covering both the initial full build and every later refit; the // refit is the smaller of the two, so this is dominated by the one-off build. const u32 alignment = device_.caps().accel_scratch_alignment; - const u64 scratch_bytes = std::max(sizes.scratch_bytes, sizes.update_scratch_bytes); + const u64 scratch_bytes = rx::Max(sizes.scratch_bytes, sizes.update_scratch_bytes); entry.scratch = device_.CreateBuffer(scratch_bytes + alignment, kBufferUsageAccelScratch); if (!entry.scratch) { device_.DestroyAccelStruct(entry.blas.handle); @@ -271,14 +274,14 @@ bool RayTracingContext::ReserveSkinnedBlas(u64 key, entry.scratch_offset = AlignUp(entry.scratch.address, alignment) - entry.scratch.address; entry.blas.address = device_.accel_address(entry.blas.handle); entry.geometries = geometries; - skinned_blas_.emplace(key, std::move(entry)); + skinned_blas_.emplace(key, base::move(entry)); return true; } void RayTracingContext::RecordSkinnedBlas(CommandList& cmd, u64 key, u64 src_key) { SkinnedBlas* entry = skinned_blas_.find(key); if (!entry) return; - BlasBuildDesc desc{.geometries = {entry->geometries.data(), entry->geometries.size()}, + BlasBuildDesc desc{.geometries = base::Span(entry->geometries.data(), entry->geometries.size()), .allow_update = true}; // Refit from the partner slot when it holds a completed build; a full build // otherwise, which is the case for an actor's first two frames (neither slot @@ -306,7 +309,7 @@ bool RayTracingContext::EnsureTlasCapacity(Tlas& tlas, u32 instance_count) { u32 capacity = 64; while (capacity < instance_count) { - if (capacity > std::numeric_limits::max() / 2) return false; + if (capacity > base::MinMax::max() / 2) return false; capacity *= 2; } @@ -347,7 +350,7 @@ bool RayTracingContext::ReserveTlas(u32 slot, u32 instance_count) { // Reserve for the upper bound (some instances may lack a BLAS and drop out in // BuildTlas, but never more than this); a stall/realloc here is safe. const AccelStructHandle previous = tlas_[slot].handle; - if (EnsureTlasCapacity(tlas_[slot], std::max(instance_count, 1u))) { + if (EnsureTlasCapacity(tlas_[slot], rx::Max(instance_count, 1u))) { if (tlas_[slot].handle != previous) slot_tracker_.Invalidate(slot); return true; } @@ -392,14 +395,14 @@ void RayTracingContext::BuildTlas(CommandList& cmd, u32 slot, u32 frame_index, u32 count = static_cast(gpu_instances.size()); if (!tlas.handle || !tlas.instances.mapped || !tlas.motion.mapped || !tlas.scratch || - tlas.capacity < std::max(count, 1u)) { + tlas.capacity < rx::Max(count, 1u)) { slot_tracker_.Invalidate(slot); RX_ERROR("tlas slot {} was not reserved; using the empty fallback", slot); return; } if (count > 0) { - std::memcpy(tlas.instances.mapped, gpu_instances.data(), count * sizeof(TlasInstance)); - std::memcpy(tlas.motion.mapped, motion.data(), count * sizeof(MotionRecord)); + base::MemCopy(tlas.instances.mapped, gpu_instances.data(), count * sizeof(TlasInstance)); + base::MemCopy(tlas.motion.mapped, motion.data(), count * sizeof(MotionRecord)); device_.FlushBuffer(tlas.instances, 0, count * sizeof(TlasInstance)); device_.FlushBuffer(tlas.motion, 0, count * sizeof(MotionRecord)); } diff --git a/engine/render/gi/raytracing.h b/engine/render/gi/raytracing.h index b8d97a3c..037e25ad 100644 --- a/engine/render/gi/raytracing.h +++ b/engine/render/gi/raytracing.h @@ -1,11 +1,11 @@ #ifndef RX_RENDER_RAYTRACING_H_ #define RX_RENDER_RAYTRACING_H_ -#include #include #include +#include "base/memory/unique_pointer.h" #include "core/math.h" #include "core/types.h" #include "render/gi/rt_slot_tracker.h" @@ -84,7 +84,7 @@ class RayTracingContext { }; static_assert(sizeof(MotionRecord) == 80); - static std::unique_ptr Create(Device& device); + static base::UniquePointer Create(Device& device); ~RayTracingContext(); RayTracingContext(const RayTracingContext&) = delete; diff --git a/engine/render/gi/rcgi.cc b/engine/render/gi/rcgi.cc index 5bd089ee..caef0d82 100644 --- a/engine/render/gi/rcgi.cc +++ b/engine/render/gi/rcgi.cc @@ -1,10 +1,13 @@ #include "render/gi/rcgi.h" -#include -#include -#include +#include +#include +#include "base/containers/span.h" +#include "base/memory/mem_ops.h" +#include "base/memory/unique_pointer.h" #include "core/log.h" +#include "core/scalar.h" #include "render/gi/raytracing.h" #include "render/gi/sdf_clipmap.h" #include "render/rhi/bindings.h" @@ -100,10 +103,10 @@ void FrameRotation(u32 frame_index, f32 out_rows[12]) { f32 u3 = static_cast(hash(frame_index + 2) & 0xffffff) / 16777215.0f; f32 angle = u1 * 6.2831853f; f32 z = u2 * 2.0f - 1.0f; - f32 r = std::sqrt(std::max(0.0f, 1.0f - z * z)); + f32 r = ::sqrt(rx::Max(0.0f, 1.0f - z * z)); f32 phi = u3 * 6.2831853f; - Vec3 axis{r * std::cos(phi), r * std::sin(phi), z}; - f32 c = std::cos(angle), s = std::sin(angle), t = 1.0f - c; + Vec3 axis{r * ::cos(phi), r * ::sin(phi), z}; + f32 c = ::cos(angle), s = ::sin(angle), t = 1.0f - c; f32 rows[12] = { t * axis.x * axis.x + c, t * axis.x * axis.y - s * axis.z, t * axis.x * axis.z + s * axis.y, 0, @@ -112,15 +115,15 @@ void FrameRotation(u32 frame_index, f32 out_rows[12]) { t * axis.x * axis.z - s * axis.y, t * axis.y * axis.z + s * axis.x, t * axis.z * axis.z + c, 0, }; - std::memcpy(out_rows, rows, sizeof(rows)); + base::MemCopy(out_rows, rows, sizeof(rows)); } } // namespace -std::unique_ptr RcgiSystem::Create(Device& device, TextureView sky_view, +base::UniquePointer RcgiSystem::Create(Device& device, TextureView sky_view, SamplerHandle sky_sampler, BindlessRegistry& bindless, bool rt_available) { - auto rcgi = std::unique_ptr(new RcgiSystem(device)); + auto rcgi = base::UniquePointer(new RcgiSystem(device)); rcgi->sky_view_ = sky_view; rcgi->sky_sampler_ = sky_sampler; rcgi->bindless_ = &bindless; @@ -131,9 +134,9 @@ std::unique_ptr RcgiSystem::Create(Device& device, TextureView sky_v Vec3 RcgiSystem::SnapOrigin(const Vec3& camera, u32 cascade) const { f32 spacing = kBaseSpacing * static_cast(1u << cascade); f32 half = (kProbesPerAxis - 1) * spacing * 0.5f; - return Vec3{std::floor((camera.x - half) / spacing) * spacing, - std::floor((camera.y - half) / spacing) * spacing, - std::floor((camera.z - half) / spacing) * spacing}; + return Vec3{::floor((camera.x - half) / spacing) * spacing, + ::floor((camera.y - half) / spacing) * spacing, + ::floor((camera.z - half) / spacing) * spacing}; } bool RcgiSystem::CreateResources() { @@ -207,7 +210,7 @@ bool RcgiSystem::CreateResources() { volumes = device_.CreateBuffer(static_cast(kInteriorVolFloat4s) * 4 * sizeof(f32), kBufferUsageStorage, true); if (!volumes.mapped) return false; - std::memset(volumes.mapped, 0, + base::MemSet(volumes.mapped, 0, static_cast(kInteriorVolFloat4s) * 4 * sizeof(f32)); } if (!probe_meta_) return false; @@ -498,7 +501,7 @@ void RcgiSystem::AddToGraph(RenderGraph& graph, RayTracingContext* raytracing, u bool async, const SdfClipmap* sdf) { // A camera teleport (bigger than cascade 0's extent) invalidates the whole // world cache; zero it before this frame's inserts. - f32 jump = std::sqrt((camera.x - last_camera_.x) * (camera.x - last_camera_.x) + + f32 jump = ::sqrt((camera.x - last_camera_.x) * (camera.x - last_camera_.x) + (camera.y - last_camera_.y) * (camera.y - last_camera_.y) + (camera.z - last_camera_.z) * (camera.z - last_camera_.z)); if (history_valid_ && jump > kBaseSpacing * kProbesPerAxis) { @@ -608,9 +611,9 @@ void RcgiSystem::AddToGraph(RenderGraph& graph, RayTracingContext* raytracing, u g.interior[3] = config.probe_ao_scale; g.gi_flags[0] = gi_bits; g.gi_flags[1] = interior_volume_count_; - std::memcpy(&g.gi_flags[2], &config.probe_ao_bias, sizeof(f32)); + base::MemCopy(&g.gi_flags[2], &config.probe_ao_bias, sizeof(f32)); g.gi_flags[3] = 0; - std::memcpy(globals_buffers_[frame_index % 2].mapped, &g, sizeof(g)); + base::MemCopy(globals_buffers_[frame_index % 2].mapped, &g, sizeof(g)); RotationPush trace_push{}; FrameRotation(frame_index, trace_push.rotation); @@ -761,7 +764,7 @@ void RcgiSystem::AddToGraph(RenderGraph& graph, RayTracingContext* raytracing, u // Blend rays into the current cascade's irradiance + visibility slabs. auto blend = [&](const GpuImage& atlas, u32 mode) { BlendPush push{}; - std::memcpy(push.rotation, trace_push.rotation, sizeof(push.rotation)); + base::MemCopy(push.rotation, trace_push.rotation, sizeof(push.rotation)); push.mode = mode; push.reset = reset ? 1u : 0u; ctx.cmd->BindPipeline(blend_pipeline_); @@ -799,7 +802,7 @@ void RcgiSystem::AddToGraph(RenderGraph& graph, RayTracingContext* raytracing, u // frame's trace/blend and this frame's downstream irradiance samples. if (relocate) { MetaPush mp{}; - std::memcpy(mp.rotation, trace_push.rotation, sizeof(mp.rotation)); + base::MemCopy(mp.rotation, trace_push.rotation, sizeof(mp.rotation)); mp.reset = reset ? 1u : 0u; ctx.cmd->BindPipeline(probe_meta_pipeline_); ctx.cmd->BindTransient(0, {Bind::Storage(0, rays_), @@ -815,8 +818,8 @@ void RcgiSystem::AddToGraph(RenderGraph& graph, RayTracingContext* raytracing, u }); } -void RcgiSystem::SetInteriorVolumes(std::span volumes, u32 frame_index) { - u32 count = static_cast(std::min(volumes.size(), kMaxInteriorVolumes)); +void RcgiSystem::SetInteriorVolumes(base::Span volumes, u32 frame_index) { + u32 count = static_cast(rx::Min(volumes.size(), kMaxInteriorVolumes)); interior_volume_count_ = count; GpuBuffer& buffer = interior_volumes_[frame_index % 2]; if (!buffer.mapped) return; @@ -855,7 +858,7 @@ ResourceHandle RcgiSystem::AddResolvePass(RenderGraph& graph, ResourceHandle dep const GpuBuffer& globals = globals_buffers_[frame_index % 2]; const GpuImage& out_img = ctx.graph->image(out); ResolvePush push{}; - std::memcpy(push.inv_view_proj, &inv_view_proj, sizeof(push.inv_view_proj)); + base::MemCopy(push.inv_view_proj, &inv_view_proj, sizeof(push.inv_view_proj)); push.inv_size[0] = 1.0f / static_cast(out_img.extent.width); push.inv_size[1] = 1.0f / static_cast(out_img.extent.height); push.camera_pos[0] = camera.x; @@ -940,9 +943,9 @@ ResourceHandle RcgiSystem::AddGatherChain(RenderGraph& graph, RayTracingContext& const GpuBuffer& globals = globals_buffers_[frame_index % 2]; const GpuBuffer& gather_camera = gather_camera_[frame_index % 2]; GatherCamera gc{}; - std::memcpy(gc.inv_view_proj, &inv_view_proj, sizeof(gc.inv_view_proj)); - std::memcpy(gc.prev_view_proj, &prev_view_proj, sizeof(gc.prev_view_proj)); - std::memcpy(gather_camera.mapped, &gc, sizeof(gc)); + base::MemCopy(gc.inv_view_proj, &inv_view_proj, sizeof(gc.inv_view_proj)); + base::MemCopy(gc.prev_view_proj, &prev_view_proj, sizeof(gc.prev_view_proj)); + base::MemCopy(gather_camera.mapped, &gc, sizeof(gc)); GatherPush p{}; p.camera_pos[0] = camera.x; p.camera_pos[1] = camera.y; p.camera_pos[2] = camera.z; @@ -951,7 +954,7 @@ ResourceHandle RcgiSystem::AddGatherChain(RenderGraph& graph, RayTracingContext& p.dims[2] = gather.width; p.dims[3] = gather.height; p.misc[0] = frame_index; p.misc[1] = screen_valid ? 1u : 0u; - std::memcpy(&p.misc[2], &ray_max, sizeof(f32)); + base::MemCopy(&p.misc[2], &ray_max, sizeof(f32)); ctx.cmd->BindPipeline(gather_pipeline_); ctx.cmd->BindTransient( 0, {Bind::Storage(0, ctx.graph->image(a_r)), Bind::Storage(1, ctx.graph->image(a_g)), diff --git a/engine/render/gi/rcgi.h b/engine/render/gi/rcgi.h index 7c8fa37b..1b0903e2 100644 --- a/engine/render/gi/rcgi.h +++ b/engine/render/gi/rcgi.h @@ -1,9 +1,9 @@ #ifndef RX_RENDER_RCGI_H_ #define RX_RENDER_RCGI_H_ -#include -#include +#include "base/containers/span.h" +#include "base/memory/unique_pointer.h" #include "core/math.h" #include "render/core/bindless.h" #include "render/core/render_graph.h" @@ -68,7 +68,7 @@ class RcgiSystem { // hardware). When false, ONLY the software variants + the shared/probes-only // pipelines are created; a SPIR-V module declaring RayQuery can fail pipeline // creation on a non-ray-query device, so the hw pipelines are skipped entirely. - static std::unique_ptr Create(Device& device, TextureView sky_view, + static base::UniquePointer Create(Device& device, TextureView sky_view, SamplerHandle sky_sampler, BindlessRegistry& bindless, bool rt_available); ~RcgiSystem(); @@ -99,7 +99,7 @@ class RcgiSystem { // do not blend with outdoor probes (item 9b). Empty span disables classify. // Cheap: just repacks a small host-visible buffer, so it may be called each // frame from the game's forwarding path. - void SetInteriorVolumes(std::span volumes, u32 frame_index); + void SetInteriorVolumes(base::Span volumes, u32 frame_index); // M1 filler: full-res irradiance from the cascades, sampled per screen pixel. // Reads the prepass depth + oct normals; returns the "rcgi_irradiance" diff --git a/engine/render/gi/rcgi_interior.h b/engine/render/gi/rcgi_interior.h index d9f105cb..140c3a4e 100644 --- a/engine/render/gi/rcgi_interior.h +++ b/engine/render/gi/rcgi_interior.h @@ -1,7 +1,7 @@ #ifndef RX_RENDER_RCGI_INTERIOR_H_ #define RX_RENDER_RCGI_INTERIOR_H_ -#include +#include #include "core/math.h" @@ -54,7 +54,7 @@ inline u32 RcgiPackOffset(const Vec3& frac) { auto lane = [](f32 f) -> u32 { f32 n = f / kRcgiRelocMaxOffset; n = n < -1.0f ? -1.0f : (n > 1.0f ? 1.0f : n); - i32 q = static_cast(std::lround(n * 511.0f)) + 512; // [1, 1023] around 512 + i32 q = static_cast(::lround(n * 511.0f)) + 512; // [1, 1023] around 512 if (q < 0) q = 0; if (q > 1023) q = 1023; return static_cast(q); diff --git a/engine/render/gi/recon_path_tracer.cc b/engine/render/gi/recon_path_tracer.cc index e72032a5..762ba57c 100644 --- a/engine/render/gi/recon_path_tracer.cc +++ b/engine/render/gi/recon_path_tracer.cc @@ -1,9 +1,10 @@ #include "render/gi/recon_path_tracer.h" -#include -#include +#include +#include "base/memory/mem_ops.h" #include "core/log.h" +#include "core/scalar.h" #include "render/gi/raytracing.h" #include "render/rhi/device.h" #include "shaders/recon_atrous_cs_hlsl.h" @@ -432,7 +433,7 @@ bool ReconPathTracer::CreateBuffers(Device& device, Extent2D extent) { ResourceState::kGeneral)); pp->state[i] = ResourceState::kGeneral; } - cmd.TextureBarriers({barriers.data(), barriers.size()}); + cmd.TextureBarriers(base::Span(barriers.data(), barriers.size())); }); buffers_ready_ = true; return true; @@ -515,7 +516,7 @@ void ReconPathTracer::RunTemporal(RenderGraph& graph, ResourceHandle noisy, Reso p.reset = frame.reset ? 1.0f : 0.0f; p.spec_mode = spec ? 1u : 0u; ctx.cmd->BindPipeline(temporal_pipeline_); - ctx.cmd->BindTransient(0, {items.data(), items.size()}); + ctx.cmd->BindTransient(0, base::Span(items.data(), items.size())); ctx.cmd->Push(p); ctx.cmd->Dispatch2D(extent_); }); @@ -587,7 +588,7 @@ void ReconPathTracer::AddToGraph(RenderGraph& graph, RayTracingContext& raytraci // same matrices, and this frame's copy must not disturb the one the previous // in-flight frame is still reading. const ReconCamera camera{frame.inv_view_proj, frame.view_proj, frame.prev_view_proj}; - std::memcpy(camera_[cur].mapped, &camera, sizeof(camera)); + base::MemCopy(camera_[cur].mapped, &camera, sizeof(camera)); auto imp = [&](const char* name, PingPong& pp, u32 i) { return graph.ImportImage(name, pp.image[i], &pp.state[i]); }; @@ -690,7 +691,7 @@ void ReconPathTracer::AddToGraph(RenderGraph& graph, RayTracingContext& raytraci p.bounces = (bounces_ & 0xffu) | (frame.restir ? 0x100u : 0u) | (rr ? 0x200u : 0u) | (di ? 0x400u : 0u); ctx.cmd->BindPipeline(gbuffer_pipeline_); - ctx.cmd->BindTransient(0, {items.data(), items.size()}); + ctx.cmd->BindTransient(0, base::Span(items.data(), items.size())); ctx.cmd->BindSet(1, bindless_set); ctx.cmd->Push(p); ctx.cmd->Dispatch2D(extent_); @@ -785,7 +786,7 @@ void ReconPathTracer::AddToGraph(RenderGraph& graph, RayTracingContext& raytraci p.jitter_delta[0] = jitter_delta.x; p.jitter_delta[1] = jitter_delta.y; ctx.cmd->BindPipeline(restir_di_temporal_pipeline_); - ctx.cmd->BindTransient(0, {items.data(), items.size()}); + ctx.cmd->BindTransient(0, base::Span(items.data(), items.size())); ctx.cmd->Push(p); ctx.cmd->Dispatch2D(extent_); }); @@ -829,7 +830,7 @@ void ReconPathTracer::AddToGraph(RenderGraph& graph, RayTracingContext& raytraci p.sample_count = kRestirDiSpatialSamples; p.radius = kRestirDiSpatialRadius; ctx.cmd->BindPipeline(restir_di_spatial_pipeline_); - ctx.cmd->BindTransient(0, {items.data(), items.size()}); + ctx.cmd->BindTransient(0, base::Span(items.data(), items.size())); ctx.cmd->BindSet(1, bindless_set); ctx.cmd->Push(p); ctx.cmd->Dispatch2D(extent_); @@ -879,7 +880,7 @@ void ReconPathTracer::AddToGraph(RenderGraph& graph, RayTracingContext& raytraci p.jitter_delta[0] = jitter_delta.x; p.jitter_delta[1] = jitter_delta.y; ctx.cmd->BindPipeline(restir_temporal_pipeline_); - ctx.cmd->BindTransient(0, {items.data(), items.size()}); + ctx.cmd->BindTransient(0, base::Span(items.data(), items.size())); ctx.cmd->Push(p); ctx.cmd->Dispatch2D(extent_); }); @@ -913,7 +914,7 @@ void ReconPathTracer::AddToGraph(RenderGraph& graph, RayTracingContext& raytraci // channel (the composite's mode-1 lighting view displays it). p.debug = frame.debug_mode >= 8 ? frame.debug_mode - 7 : 0; ctx.cmd->BindPipeline(restir_spatial_pipeline_); - ctx.cmd->BindTransient(0, {items.data(), items.size()}); + ctx.cmd->BindTransient(0, base::Span(items.data(), items.size())); ctx.cmd->Push(p); ctx.cmd->Dispatch2D(extent_); }); @@ -962,7 +963,7 @@ void ReconPathTracer::AddToGraph(RenderGraph& graph, RayTracingContext& raytraci p.reset = frame.reset ? 1.0f : 0.0f; p.full_size[0] = extent_.width; p.full_size[1] = extent_.height; ctx.cmd->BindPipeline(fog_pipeline_); - ctx.cmd->BindTransient(0, {items.data(), items.size()}); + ctx.cmd->BindTransient(0, base::Span(items.data(), items.size())); ctx.cmd->Push(p); ctx.cmd->Dispatch2D(half); }); @@ -994,7 +995,7 @@ void ReconPathTracer::AddToGraph(RenderGraph& graph, RayTracingContext& raytraci p.max_history = 1.0f; p.fog = fog_on ? 1u : 0u; ctx.cmd->BindPipeline(composite_pipeline_); - ctx.cmd->BindTransient(0, {items.data(), items.size()}); + ctx.cmd->BindTransient(0, base::Span(items.data(), items.size())); ctx.cmd->Push(p); ctx.cmd->Dispatch2D(extent_); }); @@ -1015,7 +1016,7 @@ void ReconPathTracer::AddToGraph(RenderGraph& graph, RayTracingContext& raytraci motion, p_pos, /*spec=*/true, frame); // 3. a-trous (N passes, ping-pong) for each signal - u32 passes = std::clamp(frame.atrous_passes, 1u, 8u); + u32 passes = rx::Clamp(frame.atrous_passes, 1u, 8u); ResourceHandle denoised = RunAtrous(graph, ac_c, ping, pong, nr_c, vz_c, mo_c, passes, false); ResourceHandle spec_denoised = RunAtrous(graph, sac_c, spec_ping, spec_pong, nr_c, vz_c, smo_c, passes, true); @@ -1044,7 +1045,7 @@ void ReconPathTracer::AddToGraph(RenderGraph& graph, RayTracingContext& raytraci p.max_history = static_cast(frame.max_history); p.fog = fog_on ? 1u : 0u; ctx.cmd->BindPipeline(composite_pipeline_); - ctx.cmd->BindTransient(0, {items.data(), items.size()}); + ctx.cmd->BindTransient(0, base::Span(items.data(), items.size())); ctx.cmd->Push(p); ctx.cmd->Dispatch2D(extent_); }); diff --git a/engine/render/gi/rt_instance_cull.cc b/engine/render/gi/rt_instance_cull.cc index 8bd9f247..7f40f0b3 100644 --- a/engine/render/gi/rt_instance_cull.cc +++ b/engine/render/gi/rt_instance_cull.cc @@ -1,7 +1,10 @@ +#include "base/algorithm.h" +#include "base/containers/span.h" +#include "core/scalar.h" #include "render/gi/rt_instance_cull.h" -#include -#include +#include +#include namespace rx::render { namespace { @@ -14,11 +17,11 @@ f32 MaxScale(const Mat4& t) { for (u32 i = 0; i < 3; ++i) { f32 row = 0.0f; for (u32 j = 0; j < 3; ++j) - row += std::abs(m[4 * i] * m[4 * j] + m[4 * i + 1] * m[4 * j + 1] + + row += ::abs(m[4 * i] * m[4 * j] + m[4 * i + 1] * m[4 * j + 1] + m[4 * i + 2] * m[4 * j + 2]); - bound = std::max(bound, row); + bound = rx::Max(bound, row); } - return std::sqrt(bound); + return ::sqrt(bound); } } // namespace @@ -32,7 +35,7 @@ bool RtInstanceCuller::DrawVisible(const Mat4& transform, const Vec3& mesh_cente bool RtInstanceCuller::Cull(const Vec3& c, f32 radius) const { if (radius <= 0.0f) return false; // unknown bounds (radius 0): never cull const f32 dx = c.x - eye_.x, dy = c.y - eye_.y, dz = c.z - eye_.z; - const f32 dist = std::sqrt(dx * dx + dy * dy + dz * dz); + const f32 dist = ::sqrt(dx * dx + dy * dy + dz * dz); if (dist <= start_distance_) return false; // near field is always kept // Angular radius ~= radius / dist; drop when it is below the threshold. return radius < angle_threshold_ * dist; @@ -47,7 +50,7 @@ void RtInstanceCuller::BeginFrame(const Vec3& camera_eye) { teleported_ = true; // Accept-all everywhere; the incremental sweeps re-cull over ~1 s. for (GroupState& gs : groups_) { - std::fill(gs.visible.begin(), gs.visible.end(), u8{1}); + base::Fill(gs.visible.begin(), gs.visible.end(), u8{1}); gs.cursor = 0; } } @@ -57,7 +60,7 @@ void RtInstanceCuller::BeginFrame(const Vec3& camera_eye) { } const base::Vector& RtInstanceCuller::UpdateGroup(u32 group_id, u32 generation, u32 revision, - std::span transforms, + base::Span transforms, const Vec3& mesh_center, f32 mesh_radius) { if (group_id >= groups_.size()) groups_.resize(group_id + 1); GroupState& gs = groups_[group_id]; @@ -89,8 +92,8 @@ const base::Vector& RtInstanceCuller::UpdateGroup(u32 group_id, u32 generati // Re-test a bounded slice, wrapping around; a full sweep lands over // ~kSweepFrames frames regardless of group size. - u32 slice = std::max(kMinSlice, (n + kSweepFrames - 1) / kSweepFrames); - slice = std::min(slice, n); + u32 slice = rx::Max(kMinSlice, (n + kSweepFrames - 1) / kSweepFrames); + slice = rx::Min(slice, n); for (u32 k = 0; k < slice; ++k) { const u32 i = (gs.cursor + k) % n; const Vec3 c = TransformPoint(transforms[i], mesh_center); diff --git a/engine/render/gi/rt_instance_cull.h b/engine/render/gi/rt_instance_cull.h index 7d68d5ec..bbfdd26d 100644 --- a/engine/render/gi/rt_instance_cull.h +++ b/engine/render/gi/rt_instance_cull.h @@ -1,10 +1,10 @@ #ifndef RX_RENDER_RT_INSTANCE_CULL_H_ #define RX_RENDER_RT_INSTANCE_CULL_H_ -#include #include +#include "base/containers/span.h" #include "core/math.h" #include "core/types.h" @@ -63,7 +63,7 @@ class RtInstanceCuller { // its sweep index comes back around). transforms are the per-instance world // transforms; (mesh_center, mesh_radius) is the mesh's model-space sphere. const base::Vector& UpdateGroup(u32 group_id, u32 generation, u32 revision, - std::span transforms, + base::Span transforms, const Vec3& mesh_center, f32 mesh_radius); private: diff --git a/engine/render/gi/sdf_clipmap.cc b/engine/render/gi/sdf_clipmap.cc index 6066b994..6e37e44f 100644 --- a/engine/render/gi/sdf_clipmap.cc +++ b/engine/render/gi/sdf_clipmap.cc @@ -1,16 +1,19 @@ #include "render/gi/sdf_clipmap.h" -#include -#include -#include -#include +#include +#include +#include +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" #include "core/log.h" +#include "core/scalar.h" #include "render/gi/sdf_scene.h" #include "render/rhi/bindings.h" #include "shaders/sdf_clear_cs_hlsl.h" #include "shaders/sdf_compose_cs_hlsl.h" #include "shaders/sdf_debug_cs_hlsl.h" +#include "core/sort.h" namespace rx::render { namespace { @@ -177,9 +180,9 @@ Vec3 SdfClipmap::SnapOrigin(const Vec3& camera, u32 clip) const { // backstop covers that window. const f32 snap = voxel * 8.0f; f32 half = extent * 0.5f; - return Vec3{std::floor((camera.x - half) / snap) * snap, - std::floor((camera.y - half) / snap) * snap, - std::floor((camera.z - half) / snap) * snap}; + return Vec3{::floor((camera.x - half) / snap) * snap, + ::floor((camera.y - half) / snap) * snap, + ::floor((camera.z - half) / snap) * snap}; } void SdfClipmap::WriteGlobals(u32 frame_index, const Vec3& camera) { @@ -197,7 +200,7 @@ void SdfClipmap::WriteGlobals(u32 frame_index, const Vec3& camera) { g.camera_pos[0] = camera.x; g.camera_pos[1] = camera.y; g.camera_pos[2] = camera.z; - std::memcpy(globals_buffers_[frame_index % 2].mapped, &g, sizeof(g)); + base::MemCopy(globals_buffers_[frame_index % 2].mapped, &g, sizeof(g)); } void SdfClipmap::AddComposeToGraph(RenderGraph& graph, const SdfScene& scene, @@ -229,7 +232,7 @@ void SdfClipmap::AddComposeToGraph(RenderGraph& graph, const SdfScene& scene, const SdfScene* scene_ptr = &scene; graph.AddPass( "sdf_compose", [](RenderGraph::PassBuilder&) {}, - [this, scene_ptr, jobs = std::move(jobs), instances = std::move(instances), camera, + [this, scene_ptr, jobs = base::move(jobs), instances = base::move(instances), camera, frame_index](PassContext& ctx) { CommandList* cmd = ctx.cmd; // Order this frame's writes after any prior-frame reads of the volumes. @@ -258,19 +261,19 @@ void SdfClipmap::AddComposeToGraph(RenderGraph& graph, const SdfScene& scene, const Vec3 local_max{mesh->box_min[0] + mesh->voxel * mesh->res[0], mesh->box_min[1] + mesh->voxel * mesh->res[1], mesh->box_min[2] + mesh->voxel * mesh->res[2]}; - Vec3 world_min{std::numeric_limits::max(), std::numeric_limits::max(), - std::numeric_limits::max()}; - Vec3 world_max{-std::numeric_limits::max(), -std::numeric_limits::max(), - -std::numeric_limits::max()}; + Vec3 world_min{FLT_MAX, FLT_MAX, + FLT_MAX}; + Vec3 world_max{-FLT_MAX, -FLT_MAX, + -FLT_MAX}; for (u32 corner = 0; corner < 8; ++corner) { const Vec3 local{corner & 1 ? local_max.x : local_min.x, corner & 2 ? local_max.y : local_min.y, corner & 4 ? local_max.z : local_min.z}; const Vec3 world = TransformPoint(inst.transform, local); - world_min = {std::min(world_min.x, world.x), std::min(world_min.y, world.y), - std::min(world_min.z, world.z)}; - world_max = {std::max(world_max.x, world.x), std::max(world_max.y, world.y), - std::max(world_max.z, world.z)}; + world_min = {rx::Min(world_min.x, world.x), rx::Min(world_min.y, world.y), + rx::Min(world_min.z, world.z)}; + world_max = {rx::Max(world_max.x, world.x), rx::Max(world_max.y, world.y), + rx::Max(world_max.z, world.z)}; } const Vec3 clip_min{j.origin[0], j.origin[1], j.origin[2]}; @@ -283,20 +286,22 @@ void SdfClipmap::AddComposeToGraph(RenderGraph& graph, const SdfScene& scene, world_min.x > clip_max.x || world_min.y > clip_max.y || world_min.z > clip_max.z) continue; - const Vec3 closest{std::clamp(camera.x, world_min.x, world_max.x), - std::clamp(camera.y, world_min.y, world_max.y), - std::clamp(camera.z, world_min.z, world_max.z)}; + const Vec3 closest{rx::Clamp(camera.x, world_min.x, world_max.x), + rx::Clamp(camera.y, world_min.y, world_max.y), + rx::Clamp(camera.z, world_min.z, world_max.z)}; const Vec3 delta = closest - camera; ComposeCandidate candidate{&inst, mesh, Dot(delta, delta)}; (inst.bounded_quality ? bounded_quality : unbounded).push_back(candidate); } - std::sort(bounded_quality.begin(), bounded_quality.end(), - [](const ComposeCandidate& a, const ComposeCandidate& b) { - return a.distance_sq < b.distance_sq; - }); + // Stable: every instance around the camera ties at 0, and the order + // decides which of them fit under the cap. + rx::StableSort(bounded_quality.data(), bounded_quality.data() + bounded_quality.size(), + [](const ComposeCandidate& a, const ComposeCandidate& b) { + return a.distance_sq < b.distance_sq; + }); const size_t bounded_quality_count = - std::min(static_cast(bounded_quality.size()), + rx::Min(static_cast(bounded_quality.size()), kMaxBoundedQualityInstancesPerClip); auto compose = [&](const ComposeCandidate& candidate) { @@ -308,10 +313,10 @@ void SdfClipmap::AddComposeToGraph(RenderGraph& graph, const SdfScene& scene, // trace never overshoots. (The max axis would be correct only for a // non-overshooting bound.) const f32* m = inst.transform.m; - f32 sx = std::sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]); - f32 sy = std::sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6]); - f32 sz = std::sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]); - f32 min_scale = std::min(sx, std::min(sy, sz)); + f32 sx = ::sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]); + f32 sy = ::sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6]); + f32 sz = ::sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]); + f32 min_scale = rx::Min(sx, rx::Min(sy, sz)); ComposePush pp{}; pp.inv_transform = Inverse(inst.transform); @@ -323,8 +328,8 @@ void SdfClipmap::AddComposeToGraph(RenderGraph& graph, const SdfScene& scene, pp.mesh_res[1] = mesh->res[1]; pp.mesh_res[2] = mesh->res[2]; pp.mesh_res[3] = j.clip; - std::memcpy(pp.albedo, mesh->albedo, sizeof(f32) * 3); - std::memcpy(pp.emissive, mesh->emissive, sizeof(f32) * 3); + base::MemCopy(pp.albedo, mesh->albedo, sizeof(f32) * 3); + base::MemCopy(pp.emissive, mesh->emissive, sizeof(f32) * 3); pp.albedo[3] = min_scale; cmd->BindPipeline(compose_pipeline_); diff --git a/engine/render/gi/sdf_scene.cc b/engine/render/gi/sdf_scene.cc index 96b0766c..2ed91e65 100644 --- a/engine/render/gi/sdf_scene.cc +++ b/engine/render/gi/sdf_scene.cc @@ -1,13 +1,15 @@ #include "render/gi/sdf_scene.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include "base/atomic.h" +#include "base/memory/mem_ops.h" +#include "base/time/time.h" #include "core/log.h" #include "core/math.h" +#include "core/scalar.h" namespace rx::render { namespace { @@ -53,7 +55,7 @@ bool AxisRayCross(const Vec3& p, u32 axis, const Tri& t, f32& s) { Vec3 e1 = t.b - t.a, e2 = t.c - t.a; Vec3 pv = Cross(dir, e2); f32 det = Dot(e1, pv); - if (std::fabs(det) < 1e-12f) return false; + if (::fabs(det) < 1e-12f) return false; f32 inv = 1.0f / det; Vec3 tv = p - t.a; f32 u = Dot(tv, pv) * inv; @@ -75,8 +77,8 @@ struct Grid { int Index(int x, int y, int z) const { return (z * n[1] + y) * n[0] + x; } int ClampCell(f32 v, int axis) const { - int c = static_cast(std::floor((v - (&origin.x)[axis]) / cell)); - return std::clamp(c, 0, n[axis] - 1); + int c = static_cast(::floor((v - (&origin.x)[axis]) / cell)); + return rx::Clamp(c, 0, n[axis] - 1); } }; @@ -85,16 +87,16 @@ void BuildGrid(Grid& g, const base::Vector& tris, const Vec3& box_min, cons g.origin = box_min; g.cell = cell; for (int a = 0; a < 3; ++a) - g.n[a] = std::max(1, static_cast(std::ceil((&box_ext.x)[a] / cell))); + g.n[a] = rx::Max(1, static_cast(::ceil((&box_ext.x)[a] / cell))); const int cells = g.n[0] * g.n[1] * g.n[2]; base::Vector counts; counts.resize(static_cast(cells) + 1, 0u); auto tri_cell_range = [&](const Tri& t, int lo[3], int hi[3]) { for (int a = 0; a < 3; ++a) { - f32 mn = std::min({(&t.a.x)[a], (&t.b.x)[a], (&t.c.x)[a]}); - f32 mx = std::max({(&t.a.x)[a], (&t.b.x)[a], (&t.c.x)[a]}); - lo[a] = std::clamp(static_cast(std::floor((mn - (&box_min.x)[a]) / cell)), 0, g.n[a] - 1); - hi[a] = std::clamp(static_cast(std::floor((mx - (&box_min.x)[a]) / cell)), 0, g.n[a] - 1); + f32 mn = rx::Min({(&t.a.x)[a], (&t.b.x)[a], (&t.c.x)[a]}); + f32 mx = rx::Max({(&t.a.x)[a], (&t.b.x)[a], (&t.c.x)[a]}); + lo[a] = rx::Clamp(static_cast(::floor((mn - (&box_min.x)[a]) / cell)), 0, g.n[a] - 1); + hi[a] = rx::Clamp(static_cast(::floor((mx - (&box_min.x)[a]) / cell)), 0, g.n[a] - 1); } }; for (const Tri& t : tris) { @@ -173,7 +175,7 @@ bool SdfScene::RegisterMesh(u64 mesh_key, const MeshInput& input) { } if (input.vertex_count == 0) return false; - const auto t0 = std::chrono::steady_clock::now(); + const auto t0 = base::TimeTicks::Now(); // Gather triangles and the local-space AABB. auto pos = [&](u32 v) -> Vec3 { @@ -186,8 +188,8 @@ bool SdfScene::RegisterMesh(u64 mesh_key, const MeshInput& input) { tris.reserve(tri_count); Vec3 gmin{1e30f, 1e30f, 1e30f}, gmax{-1e30f, -1e30f, -1e30f}; auto grow = [&](const Vec3& v) { - gmin = {std::min(gmin.x, v.x), std::min(gmin.y, v.y), std::min(gmin.z, v.z)}; - gmax = {std::max(gmax.x, v.x), std::max(gmax.y, v.y), std::max(gmax.z, v.z)}; + gmin = {rx::Min(gmin.x, v.x), rx::Min(gmin.y, v.y), rx::Min(gmin.z, v.z)}; + gmax = {rx::Max(gmax.x, v.x), rx::Max(gmax.y, v.y), rx::Max(gmax.z, v.z)}; }; for (u32 t = 0; t < tri_count; ++t) { u32 i0, i1, i2; @@ -210,7 +212,7 @@ bool SdfScene::RegisterMesh(u64 mesh_key, const MeshInput& input) { if (tris.empty()) return false; const Vec3 ext = gmax - gmin; - const f32 maxext = std::max({ext.x, ext.y, ext.z, 1e-4f}); + const f32 maxext = rx::Max({ext.x, ext.y, ext.z, 1e-4f}); const Vec3 center = (gmin + gmax) * 0.5f; constexpr int kPad = 2; // voxels of padding beyond the geometry AABB f32 voxel = maxext / 36.0f; @@ -218,8 +220,8 @@ bool SdfScene::RegisterMesh(u64 mesh_key, const MeshInput& input) { u32 res[3]; auto compute_res = [&]() { for (int a = 0; a < 3; ++a) { - u32 interior = static_cast(std::ceil((&ext.x)[a] / voxel)); - res[a] = std::clamp(interior + 2u * kPad, 16u, 64u); + u32 interior = static_cast(::ceil((&ext.x)[a] / voxel)); + res[a] = rx::Clamp(interior + 2u * kPad, 16u, 64u); } }; compute_res(); @@ -237,8 +239,8 @@ bool SdfScene::RegisterMesh(u64 mesh_key, const MeshInput& input) { out.res[a] = res[a]; out.box_min[a] = (¢er.x)[a] - res[a] * voxel * 0.5f; // centred, symmetric padding } - std::memcpy(out.albedo, input.albedo, sizeof(f32) * 3); - std::memcpy(out.emissive, input.emissive, sizeof(f32) * 3); + base::MemCopy(out.albedo, input.albedo, sizeof(f32) * 3); + base::MemCopy(out.emissive, input.emissive, sizeof(f32) * 3); const Vec3 box_min{out.box_min[0], out.box_min[1], out.box_min[2]}; const Vec3 box_ext{res[0] * voxel, res[1] * voxel, res[2] * voxel}; @@ -274,7 +276,7 @@ bool SdfScene::RegisterMesh(u64 mesh_key, const MeshInput& input) { if (y2 < 0 || y2 >= grid.n[1]) continue; for (int dx = -r; dx <= r; ++dx) { // Only the shell of the r-cube (interior handled at smaller r). - if (r > 0 && std::abs(dx) != r && std::abs(dy) != r && std::abs(dz) != r) continue; + if (r > 0 && ::abs(dx) != r && ::abs(dy) != r && ::abs(dz) != r) continue; int x2 = cx + dx; if (x2 < 0 || x2 >= grid.n[0]) continue; int ci = grid.Index(x2, y2, z2); @@ -286,7 +288,7 @@ bool SdfScene::RegisterMesh(u64 mesh_key, const MeshInput& input) { } } } - f32 dist = std::sqrt(best_sq); + f32 dist = ::sqrt(best_sq); // Sign: 3-axis ray parity. An axis-aligned ray stays in one cell row, // so we only test that row; a hit counts once (cell-ownership dedup). @@ -319,21 +321,21 @@ bool SdfScene::RegisterMesh(u64 mesh_key, const MeshInput& input) { }; // Chunk the z range across the job system; small meshes run inline. - const u32 workers = std::max(1u, jobs_.thread_count()); + const u32 workers = rx::Max(1u, jobs_.thread_count()); if (res[2] <= 2 || workers == 1) { solve_slice(0, res[2]); } else { - std::atomic done{0}; - const u32 chunks = std::min(workers * 2, res[2]); + base::Atomic done{0}; + const u32 chunks = rx::Min(workers * 2, res[2]); const u32 per = (res[2] + chunks - 1) / chunks; u32 submitted = 0; for (u32 c = 0; c < chunks; ++c) { - u32 z0 = c * per, z1 = std::min(z0 + per, res[2]); + u32 z0 = c * per, z1 = rx::Min(z0 + per, res[2]); if (z0 >= z1) break; ++submitted; jobs_.Submit([&solve_slice, z0, z1, &done]() { solve_slice(z0, z1); - done.fetch_add(1, std::memory_order_release); + done.fetch_add(1, base::memory_order_release); }); } jobs_.WaitIdle(); @@ -348,8 +350,8 @@ bool SdfScene::RegisterMesh(u64 mesh_key, const MeshInput& input) { return false; } - const auto t1 = std::chrono::steady_clock::now(); - out.gen_ms = std::chrono::duration(t1 - t0).count(); + const auto t1 = base::TimeTicks::Now(); + out.gen_ms = static_cast((t1 - t0).InSecondsF() * 1000.0); last_gen_ms_ = out.gen_ms; total_gen_ms_ += out.gen_ms; total_bytes_ += out.sdf.size; diff --git a/engine/render/gi/shadow.cc b/engine/render/gi/shadow.cc index ab218f4e..15b74a0b 100644 --- a/engine/render/gi/shadow.cc +++ b/engine/render/gi/shadow.cc @@ -1,13 +1,16 @@ #include "render/gi/shadow.h" -#include -#include -#include -#include +#include +#include +#include +#include #include #include "asset/mesh.h" +#include "base/functional/function.h" +#include "base/memory/mem_ops.h" #include "core/log.h" +#include "core/scalar.h" #include "shaders/shadow_instance_vs_hlsl.h" #include "shaders/shadow_ps_hlsl.h" #include "shaders/shadow_skin_vs_hlsl.h" @@ -119,14 +122,14 @@ bool ShadowPass::Initialize(Device& device, BindingLayoutHandle material_layout, for (u32 i = 0; i < kFramesInFlight; ++i) { cascades_[i] = device.CreateBuffer(sizeof(CascadeData), kBufferUsageUniform, true); if (!cascades_[i].mapped) return false; - std::memset(cascades_[i].mapped, 0, sizeof(CascadeData)); + base::MemSet(cascades_[i].mapped, 0, sizeof(CascadeData)); } return true; } void ShadowPass::Configure(const Settings& settings) { settings_ = settings; - settings_.cascade_count = std::clamp(settings_.cascade_count, 1u, kMaxCascades); + settings_.cascade_count = rx::Clamp(settings_.cascade_count, 1u, kMaxCascades); } void ShadowPass::Update(const Vec3& eye, const Vec3& forward, const Vec3& right, const Vec3& up, @@ -134,7 +137,7 @@ void ShadowPass::Update(const Vec3& eye, const Vec3& forward, const Vec3& right, const u32 count = settings_.cascade_count; const f32 near_plane = 0.1f; const f32 far_plane = settings_.distance; - const f32 tan_half = std::tan(fov_y * 0.5f); + const f32 tan_half = ::tan(fov_y * 0.5f); const f32 lambda = 0.7f; // log/uniform split blend const f32 back_pad = 80.0f; // caster range behind the slice, toward the sun @@ -142,13 +145,13 @@ void ShadowPass::Update(const Vec3& eye, const Vec3& forward, const Vec3& right, splits[0] = near_plane; for (u32 i = 1; i <= count; ++i) { f32 p = static_cast(i) / static_cast(count); - f32 log_split = near_plane * std::pow(far_plane / near_plane, p); + f32 log_split = near_plane * ::pow(far_plane / near_plane, p); f32 uniform_split = near_plane + (far_plane - near_plane) * p; splits[i] = lambda * log_split + (1.0f - lambda) * uniform_split; } Vec3 light_dir = Normalize(sun_direction); // travel direction = look direction - Vec3 up_ref = std::abs(light_dir.y) > 0.99f ? Vec3{0, 0, 1} : Vec3{0, 1, 0}; + Vec3 up_ref = ::abs(light_dir.y) > 0.99f ? Vec3{0, 0, 1} : Vec3{0, 1, 0}; current_ = CascadeData{}; for (u32 i = 0; i < count; ++i) { @@ -169,9 +172,9 @@ void ShadowPass::Update(const Vec3& eye, const Vec3& forward, const Vec3& right, f32 radius = 0.0f; for (const Vec3& p : corners) { Vec3 v = {p.x - center.x, p.y - center.y, p.z - center.z}; - radius = std::max(radius, std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z)); + radius = rx::Max(radius, ::sqrt(v.x * v.x + v.y * v.y + v.z * v.z)); } - radius = std::ceil(radius * 16.0f) / 16.0f; // quantize so it stops pulsing + radius = ::ceil(radius * 16.0f) / 16.0f; // quantize so it stops pulsing Vec3 light_eye = {center.x - light_dir.x * (radius + back_pad), center.y - light_dir.y * (radius + back_pad), @@ -187,8 +190,8 @@ void ShadowPass::Update(const Vec3& eye, const Vec3& forward, const Vec3& right, f32 half_res = settings_.resolution * 0.5f; f32 sx = origin_ndc.x * half_res; f32 sy = origin_ndc.y * half_res; - f32 dx = (std::round(sx) - sx) / half_res; - f32 dy = (std::round(sy) - sy) / half_res; + f32 dx = (::round(sx) - sx) / half_res; + f32 dy = (::round(sy) - sy) / half_res; light_vp.m[12] += dx; light_vp.m[13] += dy; @@ -203,11 +206,11 @@ void ShadowPass::Update(const Vec3& eye, const Vec3& forward, const Vec3& right, current_.p1[1] = 0.0f; current_.p1[2] = settings_.normal_bias; current_.p1[3] = 0.0f; - std::memcpy(cascades_[frame_slot].mapped, ¤t_, sizeof(CascadeData)); + base::MemCopy(cascades_[frame_slot].mapped, ¤t_, sizeof(CascadeData)); } void ShadowPass::Render(CommandList& cmd, TextureView atlas_view, - const std::function& draw) { + const base::Function& draw) { const u32 res = settings_.resolution; // The graph already put the atlas in the depth-target state for this write. diff --git a/engine/render/gi/shadow.h b/engine/render/gi/shadow.h index 09a00734..504e2b2c 100644 --- a/engine/render/gi/shadow.h +++ b/engine/render/gi/shadow.h @@ -1,8 +1,8 @@ #ifndef RX_RENDER_SHADOW_H_ #define RX_RENDER_SHADOW_H_ -#include +#include "base/functional/function.h" #include "core/math.h" #include "render/rhi/device.h" #include "render/pipeline/mesh_pipeline.h" @@ -81,7 +81,7 @@ class ShadowPass { // opaque draws inside, binding pipeline() for static and skinned_pipeline() // for animated casters (both share one binding/push interface). void Render(CommandList& cmd, TextureView atlas_view, - const std::function& draw); + const base::Function& draw); const GpuBuffer& cascade_buffer(u32 frame_slot) const { return cascades_[frame_slot]; } u64 cascade_buffer_size() const { return sizeof(CascadeData); } diff --git a/engine/render/gi/shadow_trace.cc b/engine/render/gi/shadow_trace.cc index 2ff601ed..38515af7 100644 --- a/engine/render/gi/shadow_trace.cc +++ b/engine/render/gi/shadow_trace.cc @@ -1,6 +1,6 @@ #include "render/gi/shadow_trace.h" -#include +#include #include "core/log.h" #include "render/gi/denoiser_nrd.h" @@ -77,7 +77,7 @@ ResourceHandle ShadowTracePass::AddToGraph(RenderGraph& graph, RayTracingContext push.near_plane = near_plane; push.inv_size[0] = 1.0f / static_cast(extent_.width); push.inv_size[1] = 1.0f / static_cast(extent_.height); - push.tan_angular_radius = std::tan(angular_radius > 0.0f ? angular_radius : 0.0045f); + push.tan_angular_radius = ::tan(angular_radius > 0.0f ? angular_radius : 0.0045f); push.max_distance = 1000.0f; push.jitter[0] = jitter_x; push.jitter[1] = jitter_y; diff --git a/engine/render/null/null_device.cc b/engine/render/null/null_device.cc index 7d35768f..7d566e9f 100644 --- a/engine/render/null/null_device.cc +++ b/engine/render/null/null_device.cc @@ -2,6 +2,9 @@ // engine's control flow alive (headless servers, machines without a GPU or // loader) and proves the rhi interface implementable without any graphics API. +#include "base/containers/span.h" +#include "base/functional/function.h" +#include "base/memory/unique_pointer.h" #include "render/rhi/device.h" #include "render/rhi/swapchain.h" @@ -12,7 +15,7 @@ class NullCommandList final : public CommandList { public: void BindPipeline(PipelineHandle) override {} void BindSet(u32, BindingSetHandle) override {} - void BindTransient(u32, std::span) override {} + void BindTransient(u32, base::Span) override {} void PushConstants(const void*, u32, u32) override {} void Dispatch(u32, u32, u32) override {} void BeginRendering(const RenderingInfo&) override {} @@ -25,10 +28,10 @@ class NullCommandList final : public CommandList { void DrawIndexed(u32, u32, u32, i32, u32) override {} void DrawIndexedIndirect(const GpuBuffer&, u64, u32, u32) override {} void DrawMeshTasks(u32, u32, u32) override {} - void TextureBarriers(std::span) override {} + void TextureBarriers(base::Span) override {} void MemoryBarrier(BarrierScope, BarrierScope) override {} void CopyBufferToTexture(const GpuBuffer&, const GpuImage&, - std::span) override {} + base::Span) override {} void CopyTextureToBuffer(const GpuImage&, const GpuBuffer&, const BufferTextureCopy&) override {} void CopyBuffer(const GpuBuffer&, u64, const GpuBuffer&, u64, u64) override {} @@ -54,7 +57,7 @@ class NullDevice final : public Device { void WaitIdle() override {} bool RecreateSurface(Window&) override { return false; } void DestroySurface() override {} - std::unique_ptr CreateSwapchain(u32, u32, bool, bool) override { return nullptr; } + base::UniquePointer CreateSwapchain(u32, u32, bool, bool) override { return nullptr; } MemoryBudget memory_budget() const override { return {}; } GpuBuffer CreateBuffer(u64, BufferUsageFlags, bool) override { return {}; } @@ -75,7 +78,7 @@ class NullDevice final : public Device { void DestroyBindingLayout(BindingLayoutHandle) override {} BindingSetHandle CreateBindingSet(BindingLayoutHandle, u32) override { return {}; } void DestroyBindingSet(BindingSetHandle) override {} - void UpdateBindingSet(BindingSetHandle, std::span) override {} + void UpdateBindingSet(BindingSetHandle, base::Span) override {} AccelSizes GetBlasSizes(const BlasBuildDesc&) override { return {}; } AccelSizes GetTlasSizes(u32) override { return {}; } @@ -87,7 +90,7 @@ class NullDevice final : public Device { void DestroyTimestampPool(TimestampPoolHandle) override {} bool GetTimestamps(TimestampPoolHandle, u32, u32, u64*) override { return false; } - void ImmediateSubmit(const std::function& record) override { + void ImmediateSubmit(const base::Function& record) override { NullCommandList cmd; record(cmd); } @@ -102,6 +105,6 @@ class NullDevice final : public Device { } // namespace -std::unique_ptr CreateNullDevice() { return std::make_unique(); } +base::UniquePointer CreateNullDevice() { return base::MakeUnique(); } } // namespace rx::render::null diff --git a/engine/render/pipeline/gpu_cull.cc b/engine/render/pipeline/gpu_cull.cc index 3a6e3308..3c9b3723 100644 --- a/engine/render/pipeline/gpu_cull.cc +++ b/engine/render/pipeline/gpu_cull.cc @@ -1,8 +1,9 @@ #include "render/pipeline/gpu_cull.h" -#include -#include +#include +#include +#include "base/memory/mem_ops.h" #include "core/log.h" #include "render/rhi/device.h" #include "shaders/bounds_ps_hlsl.h" @@ -47,7 +48,7 @@ void ExtractPlanes(const Mat4& vp, f32 out[5][4]) { {row(2, 0), row(2, 1), row(2, 2), row(2, 3)}, // near: clip.z >= 0 }; for (int i = 0; i < 5; ++i) { - f32 len = std::sqrt(p[i][0] * p[i][0] + p[i][1] * p[i][1] + p[i][2] * p[i][2]); + f32 len = ::sqrt(p[i][0] * p[i][0] + p[i][1] * p[i][1] + p[i][2] * p[i][2]); if (len < 1e-8f) len = 1.0f; for (int c = 0; c < 4; ++c) out[i][c] = p[i][c] / len; } @@ -186,7 +187,7 @@ void GpuCull::AddBoundsPass(RenderGraph& graph, ResourceHandle color, const Mat4 [this, color, instances, view_proj, instance_count](PassContext& ctx) { const GpuImage& target = ctx.graph->image(color); ColorAttachment attachment{.view = target.view, .load = LoadOp::kLoad}; - ctx.cmd->BeginRendering({.extent = target.extent, .colors = {&attachment, 1}}); + ctx.cmd->BeginRendering({.extent = target.extent, .colors = base::Span(&attachment, 1)}); ctx.cmd->BindPipeline(bounds_pipeline_); ctx.cmd->BindTransient(0, {Bind::StorageBuffer(0, instances)}); ctx.cmd->Push(view_proj); @@ -215,7 +216,7 @@ void GpuCull::AddToGraph(RenderGraph& graph, const Mat4& view_proj, const Mat4& bool occ = occlusion && hiz != kInvalidResource; const CullReproject reproject{prev_view_proj}; - std::memcpy(reproject_[slot].mapped, &reproject, sizeof(reproject)); + base::MemCopy(reproject_[slot].mapped, &reproject, sizeof(reproject)); CullPush push{}; ExtractPlanes(view_proj, push.planes); @@ -249,7 +250,7 @@ void GpuCull::AddToGraph(RenderGraph& graph, const Mat4& view_proj, const Mat4& items.push_back(Bind::StorageBuffer(2, counts)); if (has_hiz) items.push_back(Bind::Sampled(3, ctx.graph->image(hiz))); items.push_back(Bind::Uniform(4, reproject_buffer, 0, sizeof(CullReproject))); - ctx.cmd->BindTransient(0, {items.data(), items.size()}); + ctx.cmd->BindTransient(0, base::Span(items.data(), items.size())); ctx.cmd->Push(push); ctx.cmd->Dispatch((instance_count + 63) / 64, 1, 1); diff --git a/engine/render/pipeline/hair_material.cc b/engine/render/pipeline/hair_material.cc index f39e5c45..860e1799 100644 --- a/engine/render/pipeline/hair_material.cc +++ b/engine/render/pipeline/hair_material.cc @@ -1,10 +1,12 @@ #include "render/pipeline/hair_material.h" +#include "base/memory/mem_ops.h" #include "core/log.h" +#include "core/scalar.h" -#include -#include -#include +#include +#include +#include namespace rx::render { namespace { @@ -14,8 +16,8 @@ constexpr f32 kSqrtPiOver8 = 0.626657069f; constexpr int kPMax = 3; f32 Sqr(f32 x) { return x * x; } -f32 SafeSqrt(f32 x) { return std::sqrt(std::max(x, 0.0f)); } -f32 SafeAsin(f32 x) { return std::asin(std::clamp(x, -1.0f, 1.0f)); } +f32 SafeSqrt(f32 x) { return ::sqrt(rx::Max(x, 0.0f)); } +f32 SafeAsin(f32 x) { return ::asin(rx::Clamp(x, -1.0f, 1.0f)); } f32 I0(f32 x) { f32 val = 0.0f; @@ -33,45 +35,45 @@ f32 I0(f32 x) { f32 LogI0(f32 x) { if (x > 12.0f) { - return x + 0.5f * (-std::log(2.0f * kPi) + std::log(1.0f / x) + 1.0f / (8.0f * x)); + return x + 0.5f * (-::log(2.0f * kPi) + ::log(1.0f / x) + 1.0f / (8.0f * x)); } - return std::log(I0(x)); + return ::log(I0(x)); } f32 Mp(f32 cos_theta_i, f32 cos_theta_o, f32 sin_theta_i, f32 sin_theta_o, f32 v) { f32 a = cos_theta_i * cos_theta_o / v; f32 b = sin_theta_i * sin_theta_o / v; if (v <= 0.1f) { - return std::exp(LogI0(a) - b - 1.0f / v + 0.6931472f + std::log(1.0f / (2.0f * v))); + return ::exp(LogI0(a) - b - 1.0f / v + 0.6931472f + ::log(1.0f / (2.0f * v))); } - return (std::exp(-b) * I0(a)) / (std::sinh(1.0f / v) * 2.0f * v); + return (::exp(-b) * I0(a)) / (::sinh(1.0f / v) * 2.0f * v); } f32 FresnelDielectric(f32 cos_theta_i, f32 eta) { - cos_theta_i = std::clamp(cos_theta_i, -1.0f, 1.0f); + cos_theta_i = rx::Clamp(cos_theta_i, -1.0f, 1.0f); if (cos_theta_i < 0.0f) { eta = 1.0f / eta; cos_theta_i = -cos_theta_i; } f32 sin2_t = (1.0f - cos_theta_i * cos_theta_i) / (eta * eta); if (sin2_t >= 1.0f) return 1.0f; - f32 cos_t = std::sqrt(1.0f - sin2_t); + f32 cos_t = ::sqrt(1.0f - sin2_t); f32 rp = (eta * cos_theta_i - cos_t) / (eta * cos_theta_i + cos_t); f32 rs = (cos_theta_i - eta * cos_t) / (cos_theta_i + eta * cos_t); return 0.5f * (rp * rp + rs * rs); } f32 Logistic(f32 x, f32 s) { - x = std::abs(x); - f32 e = std::exp(-x / s); + x = ::abs(x); + f32 e = ::exp(-x / s); return e / (s * Sqr(1.0f + e)); } -f32 LogisticCdf(f32 x, f32 s) { return 1.0f / (1.0f + std::exp(-x / s)); } +f32 LogisticCdf(f32 x, f32 s) { return 1.0f / (1.0f + ::exp(-x / s)); } f32 TrimmedLogistic(f32 x, f32 s) { f32 norm = LogisticCdf(kPi, s) - LogisticCdf(-kPi, s); - return Logistic(x, s) / std::max(norm, 1e-6f); + return Logistic(x, s) / rx::Max(norm, 1e-6f); } f32 PhiLobe(int p, f32 gamma_o, f32 gamma_t) { @@ -80,13 +82,13 @@ f32 PhiLobe(int p, f32 gamma_o, f32 gamma_t) { f32 Np(f32 phi, int p, f32 s, f32 gamma_o, f32 gamma_t) { f32 dphi = phi - PhiLobe(p, gamma_o, gamma_t); - dphi = dphi - 2.0f * kPi * std::floor((dphi + kPi) / (2.0f * kPi)); + dphi = dphi - 2.0f * kPi * ::floor((dphi + kPi) / (2.0f * kPi)); return TrimmedLogistic(dphi, s); } void LobeVariance(f32 beta_m, f32 v[kPMax + 1]) { - f32 b = std::clamp(beta_m, 0.02f, 1.0f); - f32 v0 = Sqr(0.726f * b + 0.812f * b * b + 3.7f * std::pow(b, 20.0f)); + f32 b = rx::Clamp(beta_m, 0.02f, 1.0f); + f32 v0 = Sqr(0.726f * b + 0.812f * b * b + 3.7f * ::pow(b, 20.0f)); v[0] = v0; v[1] = 0.25f * v0; v[2] = 4.0f * v0; @@ -94,20 +96,20 @@ void LobeVariance(f32 beta_m, f32 v[kPMax + 1]) { } f32 AzimuthalScale(f32 beta_n) { - f32 b = std::clamp(beta_n, 0.02f, 1.0f); - return kSqrtPiOver8 * (0.265f * b + 1.194f * b * b + 5.372f * std::pow(b, 22.0f)); + f32 b = rx::Clamp(beta_n, 0.02f, 1.0f); + return kSqrtPiOver8 * (0.265f * b + 1.194f * b * b + 5.372f * ::pow(b, 22.0f)); } void AverageAttenuation(const HairSurfaceParameters& p, f32 cos_theta_d, f32 a_f[3], f32 a_b[3]) { - f32 f = FresnelDielectric(std::max(cos_theta_d, 1e-3f), p.eta); - f32 spread = std::clamp(p.beta_n, 0.0f, 1.0f); + f32 f = FresnelDielectric(rx::Max(cos_theta_d, 1e-3f), p.eta); + f32 spread = rx::Clamp(p.beta_n, 0.0f, 1.0f); for (int c = 0; c < 3; ++c) { - f32 T = std::exp(-p.sigma_a[c] * 2.0f); + f32 T = ::exp(-p.sigma_a[c] * 2.0f); f32 tt = Sqr(1.0f - f) * T; f32 trt = tt * T * f; - a_f[c] = std::clamp(tt * (1.0f - 0.5f * spread) + trt * 0.15f, 0.0f, 1.0f); - a_b[c] = std::clamp(f * (0.3f + 0.4f * spread) + trt * (0.5f + 0.5f * spread), 0.0f, 1.0f); + a_f[c] = rx::Clamp(tt * (1.0f - 0.5f * spread) + trt * 0.15f, 0.0f, 1.0f); + a_b[c] = rx::Clamp(f * (0.3f + 0.4f * spread) + trt * (0.5f + 0.5f * spread), 0.0f, 1.0f); } } @@ -117,7 +119,7 @@ void HairSigmaFromMelanin(f32 eumelanin, f32 pheomelanin, f32 out_sigma[3]) { const f32 eu[3] = {0.419f, 0.697f, 1.37f}; const f32 pheo[3] = {0.187f, 0.4f, 1.05f}; for (int c = 0; c < 3; ++c) { - out_sigma[c] = std::max(eumelanin, 0.0f) * eu[c] + std::max(pheomelanin, 0.0f) * pheo[c]; + out_sigma[c] = rx::Max(eumelanin, 0.0f) * eu[c] + rx::Max(pheomelanin, 0.0f) * pheo[c]; } } @@ -126,15 +128,15 @@ void HairSigmaFromColorPathTraced(const f32 color[3], f32 beta_n, f32 out_sigma[ const f32 denom = 5.969f - 0.215f * b + 2.532f * b * b - 10.73f * b * b * b + 5.574f * b * b * b * b + 0.245f * b * b * b * b * b; for (int c = 0; c < 3; ++c) { - const f32 t = std::log(std::clamp(color[c], 1e-4f, 1.0f)) / std::max(denom, 1e-4f); + const f32 t = ::log(rx::Clamp(color[c], 1e-4f, 1.0f)) / rx::Max(denom, 1e-4f); out_sigma[c] = t * t; } } void HairSigmaFromColor(const f32 color[3], f32 reference_depth, f32 out_sigma[3]) { - const f32 denom = 2.17f + 2.02f * std::max(reference_depth, 0.0f); + const f32 denom = 2.17f + 2.02f * rx::Max(reference_depth, 0.0f); for (int c = 0; c < 3; ++c) { - out_sigma[c] = -std::log(std::clamp(color[c], 1e-4f, 1.0f)) / std::max(denom, 1e-3f); + out_sigma[c] = -::log(rx::Clamp(color[c], 1e-4f, 1.0f)) / rx::Max(denom, 1e-3f); } } @@ -190,7 +192,7 @@ HairSurfaceParameters HairPresetParams(HairPreset preset) { void HairResolveSigma(const HairSurfaceParameters& params, const f32 color[3], f32 out_sigma[3]) { if (params.color_mode == HairColorMode::kPigment) { - std::memcpy(out_sigma, params.sigma_a, sizeof(f32) * 3); + base::MemCopy(out_sigma, params.sigma_a, sizeof(f32) * 3); return; } HairSigmaFromColor(color, params.color_reference_depth, out_sigma); @@ -210,8 +212,8 @@ HairTierCaps HairTierApply(HairTier tier, HairSurfaceParameters& params) { params.dual_scattering = false; // At this size a groom is a silhouette. Keeping the lobes alive costs // aliasing, not detail, so the fibre is flattened to one broad response. - params.beta_m = std::max(params.beta_m, 0.5f); - params.beta_n = std::max(params.beta_n, 0.5f); + params.beta_m = rx::Max(params.beta_m, 0.5f); + params.beta_n = rx::Max(params.beta_n, 0.5f); params.alpha = 0.0f; params.scatter_scale = 0.0f; break; @@ -220,7 +222,7 @@ HairTierCaps HairTierApply(HairTier tier, HairSurfaceParameters& params) { } HairRange HairSafeRange(const char* field) { - auto is = [&](const char* n) { return std::strcmp(field, n) == 0; }; + auto is = [&](const char* n) { return ::strcmp(field, n) == 0; }; // Outside these the model stops being physical: roughness below ~0.02 makes // the longitudinal lobe narrower than a pixel (pure aliasing), a tilt beyond // ~10 degrees separates the highlights further than any real cuticle, and an @@ -246,26 +248,26 @@ void HairEvaluateCpu(const HairSurfaceParameters& p, const f32 wo[3], const f32 f32 out_rgb[3]) { out_rgb[0] = out_rgb[1] = out_rgb[2] = 0.0f; - const f32 sin_theta_o = std::clamp(wo[0], -1.0f, 1.0f); + const f32 sin_theta_o = rx::Clamp(wo[0], -1.0f, 1.0f); const f32 cos_theta_o = SafeSqrt(1.0f - sin_theta_o * sin_theta_o); - const f32 phi_o = std::atan2(wo[2], wo[1]); + const f32 phi_o = ::atan2(wo[2], wo[1]); - const f32 sin_theta_i = std::clamp(wi[0], -1.0f, 1.0f); + const f32 sin_theta_i = rx::Clamp(wi[0], -1.0f, 1.0f); const f32 cos_theta_i = SafeSqrt(1.0f - sin_theta_i * sin_theta_i); - const f32 phi_i = std::atan2(wi[2], wi[1]); + const f32 phi_i = ::atan2(wi[2], wi[1]); const f32 sin_theta_t = sin_theta_o / p.eta; const f32 cos_theta_t = SafeSqrt(1.0f - sin_theta_t * sin_theta_t); const f32 eta_p = - SafeSqrt(p.eta * p.eta - sin_theta_o * sin_theta_o) / std::max(cos_theta_o, 1e-5f); - const f32 sin_gamma_t = std::clamp(h / std::max(eta_p, 1e-5f), -1.0f, 1.0f); + SafeSqrt(p.eta * p.eta - sin_theta_o * sin_theta_o) / rx::Max(cos_theta_o, 1e-5f); + const f32 sin_gamma_t = rx::Clamp(h / rx::Max(eta_p, 1e-5f), -1.0f, 1.0f); const f32 cos_gamma_t = SafeSqrt(1.0f - sin_gamma_t * sin_gamma_t); const f32 gamma_t = SafeAsin(sin_gamma_t); - const f32 gamma_o = SafeAsin(std::clamp(h, -1.0f, 1.0f)); + const f32 gamma_o = SafeAsin(rx::Clamp(h, -1.0f, 1.0f)); f32 T[3]; for (int c = 0; c < 3; ++c) { - T[c] = std::exp(-p.sigma_a[c] * (2.0f * cos_gamma_t / std::max(cos_theta_t, 1e-5f))); + T[c] = ::exp(-p.sigma_a[c] * (2.0f * cos_gamma_t / rx::Max(cos_theta_t, 1e-5f))); } const f32 cos_gamma_o = SafeSqrt(1.0f - h * h); @@ -275,14 +277,14 @@ void HairEvaluateCpu(const HairSurfaceParameters& p, const f32 wo[3], const f32 ap[0][c] = f; ap[1][c] = Sqr(1.0f - f) * T[c]; ap[2][c] = ap[1][c] * T[c] * f; - ap[3][c] = ap[2][c] * f * T[c] / std::max(1.0f - T[c] * f, 1e-5f); + ap[3][c] = ap[2][c] * f * T[c] / rx::Max(1.0f - T[c] * f, 1e-5f); } f32 v[kPMax + 1]; LobeVariance(p.beta_m, v); const f32 s = AzimuthalScale(p.beta_n); - const f32 sin_a = std::sin(p.alpha); + const f32 sin_a = ::sin(p.alpha); const f32 cos_a = SafeSqrt(1.0f - sin_a * sin_a); const f32 sin2a = 2.0f * sin_a * cos_a; const f32 cos2a = cos_a * cos_a - sin_a * sin_a; @@ -302,7 +304,7 @@ void HairEvaluateCpu(const HairSurfaceParameters& p, const f32 wo[3], const f32 sto = sin_theta_o * cos4a + cos_theta_o * sin4a; cto = cos_theta_o * cos4a - sin_theta_o * sin4a; } - cto = std::abs(cto); + cto = ::abs(cto); const f32 m = Mp(cos_theta_i, cto, sin_theta_i, sto, v[lobe]); const f32 n = Np(phi, lobe, s, gamma_o, gamma_t); for (int c = 0; c < 3; ++c) out_rgb[c] += m * ap[lobe][c] * n; @@ -313,7 +315,7 @@ void HairEvaluateCpu(const HairSurfaceParameters& p, const f32 wo[3], const f32 // The lobe products are f * cos(theta_i); divide the cosine back out so // this returns a BSDF (see the shader's HairEvaluate). if (cos_theta_i > 1e-4f) out_rgb[c] /= cos_theta_i; - out_rgb[c] = std::max(out_rgb[c], 0.0f); + out_rgb[c] = rx::Max(out_rgb[c], 0.0f); } } @@ -321,7 +323,7 @@ void HairShadeCpu(const HairSurfaceParameters& p, const f32 wo[3], const f32 wi[ f32 strand_count, f32 out_rgb[3]) { const f32 cos_theta_i = SafeSqrt(1.0f - wi[0] * wi[0]); const f32 cos_theta_d = - std::cos(0.5f * (SafeAsin(wi[0]) - SafeAsin(wo[0]))); + ::cos(0.5f * (SafeAsin(wi[0]) - SafeAsin(wo[0]))); if (!p.dual_scattering) { HairEvaluateCpu(p, wo, wi, h, out_rgb); @@ -330,23 +332,23 @@ void HairShadeCpu(const HairSurfaceParameters& p, const f32 wo[3], const f32 wi[ } f32 a_f[3], a_b[3]; - AverageAttenuation(p, std::abs(cos_theta_d), a_f, a_b); - const f32 n = std::max(strand_count, 0.0f) * std::max(p.density, 0.0f); - const f32 spread = std::sqrt(n) * 0.4f * (0.2f + std::clamp(p.beta_m, 0.0f, 1.0f)); + AverageAttenuation(p, ::abs(cos_theta_d), a_f, a_b); + const f32 n = rx::Max(strand_count, 0.0f) * rx::Max(p.density, 0.0f); + const f32 spread = ::sqrt(n) * 0.4f * (0.2f + rx::Clamp(p.beta_m, 0.0f, 1.0f)); f32 direct[3]; HairEvaluateCpu(p, wo, wi, h, direct); HairSurfaceParameters blunt = p; - blunt.beta_m = std::clamp(p.beta_m + spread, 0.0f, 1.0f); - blunt.beta_n = std::clamp(p.beta_n + spread * 0.5f, 0.0f, 1.0f); + blunt.beta_m = rx::Clamp(p.beta_m + spread, 0.0f, 1.0f); + blunt.beta_n = rx::Clamp(p.beta_n + spread * 0.5f, 0.0f, 1.0f); f32 scattered[3]; HairEvaluateCpu(blunt, wo, wi, 0.0f, scattered); - const f32 density_term = 1.0f - std::exp(-0.35f * n); + const f32 density_term = 1.0f - ::exp(-0.35f * n); for (int c = 0; c < 3; ++c) { - const f32 forward = std::exp(std::log(std::max(a_f[c], 1e-6f)) * n); - const f32 back = a_b[c] * density_term * std::max(p.scatter_scale, 0.0f); + const f32 forward = ::exp(::log(rx::Max(a_f[c], 1e-6f)) * n); + const f32 back = a_b[c] * density_term * rx::Max(p.scatter_scale, 0.0f); out_rgb[c] = (direct[c] * forward + scattered[c] * forward * back) * cos_theta_i; } } @@ -361,13 +363,13 @@ f32 HairAlbedoCpu(const HairSurfaceParameters& p, const f32 wo[3], u32 theta_ste const double dphi = 2.0 * kPi / static_cast(phi_steps); for (u32 ti = 0; ti < theta_steps; ++ti) { const double theta = -0.5 * kPi + (static_cast(ti) + 0.5) * dtheta; - const double sin_theta = std::sin(theta); - const double cos_theta = std::cos(theta); + const double sin_theta = ::sin(theta); + const double cos_theta = ::cos(theta); for (u32 pi_i = 0; pi_i < phi_steps; ++pi_i) { const double phi = (static_cast(pi_i) + 0.5) * dphi; const f32 wi[3] = {static_cast(sin_theta), - static_cast(cos_theta * std::cos(phi)), - static_cast(cos_theta * std::sin(phi))}; + static_cast(cos_theta * ::cos(phi)), + static_cast(cos_theta * ::sin(phi))}; f32 rgb[3]; HairEvaluateCpu(p, wo, wi, 0.0f, rgb); total += static_cast(rgb[channel]) * cos_theta * cos_theta * dtheta * dphi; diff --git a/engine/render/pipeline/human_material.cc b/engine/render/pipeline/human_material.cc index 423f3856..94b5e7c9 100644 --- a/engine/render/pipeline/human_material.cc +++ b/engine/render/pipeline/human_material.cc @@ -1,10 +1,11 @@ #include "render/pipeline/human_material.h" -#include -#include -#include +#include +#include +#include "base/memory/mem_ops.h" #include "core/log.h" +#include "core/scalar.h" namespace rx::render { namespace { @@ -18,7 +19,7 @@ V3 Load(const f32 v[3]) { return {v[0], v[1], v[2]}; } f32 Dot(V3 a, V3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; } V3 Add(V3 a, V3 b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; } V3 Norm(V3 a) { - f32 len = std::sqrt(std::max(Dot(a, a), 1e-20f)); + f32 len = ::sqrt(rx::Max(Dot(a, a), 1e-20f)); return {a.x / len, a.y / len, a.z / len}; } f32 Sat(f32 x) { return x < 0.0f ? 0.0f : (x > 1.0f ? 1.0f : x); } @@ -26,13 +27,13 @@ f32 Sat(f32 x) { return x < 0.0f ? 0.0f : (x > 1.0f ? 1.0f : x); } f32 D_GGX(f32 ndh, f32 a) { f32 a2 = a * a; f32 d = ndh * ndh * (a2 - 1.0f) + 1.0f; - return a2 / std::max(kPi * d * d, 1e-7f); + return a2 / rx::Max(kPi * d * d, 1e-7f); } f32 V_Smith(f32 ndv, f32 ndl, f32 a) { f32 a2 = a * a; - f32 gv = ndl * std::sqrt(ndv * ndv * (1.0f - a2) + a2); - f32 gl = ndv * std::sqrt(ndl * ndl * (1.0f - a2) + a2); - return 0.5f / std::max(gv + gl, 1e-5f); + f32 gv = ndl * ::sqrt(ndv * ndv * (1.0f - a2) + a2); + f32 gl = ndv * ::sqrt(ndl * ndl * (1.0f - a2) + a2); + return 0.5f / rx::Max(gv + gl, 1e-5f); } } // namespace @@ -229,7 +230,7 @@ HumanTier HumanTierForScreenHeight(f32 pixels) { } HumanRange HumanSafeRange(const char* field) { - auto is = [&](const char* n) { return std::strcmp(field, n) == 0; }; + auto is = [&](const char* n) { return ::strcmp(field, n) == 0; }; // Outside these the model stops being energy-sane: a diffuse Fresnel peak // above 1 can more than double the grazing diffuse, a terminator length above // ~0.5 wraps light onto the far side of a sphere, a secondary roughness scale @@ -287,7 +288,7 @@ HumanSurfaceParameters HumanResolve(const asset::Material::HumanParams& a) { p.mean_free_path = a.mean_free_path; p.subsurface_scale = a.subsurface_scale; p.transmission = a.transmission; - std::memcpy(p.transmission_tint, a.transmission_tint, sizeof(f32) * 3); + base::MemCopy(p.transmission_tint, a.transmission_tint, sizeof(f32) * 3); p.extinction_scale = a.extinction_scale; p.thickness_scale = a.thickness_scale; p.corneal_wetness = a.corneal_wetness; @@ -321,7 +322,7 @@ void HumanStore(const HumanSurfaceParameters& p, asset::Material::HumanParams& a a.mean_free_path = p.mean_free_path; a.subsurface_scale = p.subsurface_scale; a.transmission = p.transmission; - std::memcpy(a.transmission_tint, p.transmission_tint, sizeof(f32) * 3); + base::MemCopy(a.transmission_tint, p.transmission_tint, sizeof(f32) * 3); a.extinction_scale = p.extinction_scale; a.thickness_scale = p.thickness_scale; a.corneal_wetness = p.corneal_wetness; @@ -348,13 +349,13 @@ HumanBrdfSample HumanEvaluateCpu(const HumanSurfaceParameters& p, const f32 base V3 v = Load(v_in), l = Load(l_in); f32 ndl_d = Dot(nd, l); - f32 ndv_d = std::max(Dot(nd, v), 1e-4f); + f32 ndv_d = rx::Max(Dot(nd, v), 1e-4f); f32 ndl_g = Dot(ng, l); // diffuse cosine with the energy-normalized wrap f32 cos_d = Sat(ndl_d); if (p.smooth_terminator_amount > 0.0f) { - f32 w = std::max(p.smooth_terminator_length, 0.0f); + f32 w = rx::Max(p.smooth_terminator_length, 0.0f); f32 soft = Sat((ndl_d + w) / ((1.0f + w) * (1.0f + w))); f32 gate = Sat(ndl_g * 4.0f + 1.0f); f32 amount = Sat(p.smooth_terminator_amount) * gate; @@ -364,23 +365,23 @@ HumanBrdfSample HumanEvaluateCpu(const HumanSurfaceParameters& p, const f32 base if (cos_d > 0.0f) { V3 hd = Norm(Add(l, v)); f32 ldh = Sat(Dot(l, hd)); - f32 gv = std::pow(Sat(1.0f - ndv_d), std::max(p.diffuse_fresnel_falloff, 1e-2f)); - f32 gl = std::pow(Sat(1.0f - Sat(ndl_d)), - std::max(p.diffuse_fresnel_tangent_falloff, 1e-2f)); + f32 gv = ::pow(Sat(1.0f - ndv_d), rx::Max(p.diffuse_fresnel_falloff, 1e-2f)); + f32 gl = ::pow(Sat(1.0f - Sat(ndl_d)), + rx::Max(p.diffuse_fresnel_tangent_falloff, 1e-2f)); f32 fresnel = (1.0f + p.diffuse_fresnel_peak * gv) * (1.0f + p.diffuse_fresnel_peak * gl); f32 fd90 = p.retroreflection_peak * ldh * ldh; - f32 rv = 1.0f + fd90 * std::pow(Sat(1.0f - ndv_d), - std::max(p.retroreflection_tangent_falloff, 1e-2f)); - f32 rl = 1.0f + fd90 * std::pow(Sat(1.0f - Sat(ndl_d)), - std::max(p.retroreflection_falloff, 1e-2f)); - f32 shaping = std::max(fresnel * rv * rl, 0.0f); + f32 rv = 1.0f + fd90 * ::pow(Sat(1.0f - ndv_d), + rx::Max(p.retroreflection_tangent_falloff, 1e-2f)); + f32 rl = 1.0f + fd90 * ::pow(Sat(1.0f - Sat(ndl_d)), + rx::Max(p.retroreflection_falloff, 1e-2f)); + f32 shaping = rx::Max(fresnel * rv * rl, 0.0f); for (int c = 0; c < 3; ++c) out.diffuse[c] = base_color[c] * (1.0f / kPi) * shaping * cos_d; } f32 ndl_s = Dot(ns, l); if (ndl_s > 0.0f) { f32 ndl = Sat(ndl_s); - f32 ndv = std::max(Dot(ns, v), 1e-4f); + f32 ndv = rx::Max(Dot(ns, v), 1e-4f); V3 h = Norm(Add(l, v)); f32 ndh = Sat(Dot(ns, h)); f32 vdh = Sat(Dot(v, h)); @@ -388,27 +389,27 @@ HumanBrdfSample HumanEvaluateCpu(const HumanSurfaceParameters& p, const f32 base f32 rough = roughness; solid_angle *= Sat(p.light_shape_response); if (solid_angle > 0.0f) { - f32 widen = Sat(std::sqrt(solid_angle / kPi) * 0.5f); - rough = Sat(std::sqrt(roughness * roughness + widen * widen)); + f32 widen = Sat(::sqrt(solid_angle / kPi) * 0.5f); + rough = Sat(::sqrt(roughness * roughness + widen * widen)); } - f32 a1 = std::max(rough * rough, 1e-5f); + f32 a1 = rx::Max(rough * rough, 1e-5f); f32 core = D_GGX(ndh, a1) * V_Smith(ndv, ndl, a1); f32 lobe = core; if (p.secondary_specular_weight > 0.0f) { - f32 rough2 = Sat(rough * std::max(p.secondary_roughness_scale, 1e-2f)); - f32 a2 = std::max(rough2 * rough2, 1e-5f); + f32 rough2 = Sat(rough * rx::Max(p.secondary_roughness_scale, 1e-2f)); + f32 a2 = rx::Max(rough2 * rough2, 1e-5f); f32 tail = D_GGX(ndh, a2) * V_Smith(ndv, ndl, a2); f32 w = Sat(p.secondary_specular_weight); lobe = core + (tail - core) * w; } - f32 fpow = std::pow(Sat(1.0f - vdh), std::max(p.specular_fresnel_falloff, 1e-2f)); + f32 fpow = ::pow(Sat(1.0f - vdh), rx::Max(p.specular_fresnel_falloff, 1e-2f)); for (int c = 0; c < 3; ++c) { f32 f = f0[c] + (1.0f - f0[c]) * fpow; out.specular[c] = lobe * f * ndl; } if (p.corneal_wetness > 0.0f) { - f32 wa = std::max(0.02f * 0.02f, 1e-5f); - f32 wf = (0.02f + 0.98f * std::pow(Sat(1.0f - vdh), 5.0f)) * Sat(p.corneal_wetness); + f32 wa = rx::Max(0.02f * 0.02f, 1e-5f); + f32 wf = (0.02f + 0.98f * ::pow(Sat(1.0f - vdh), 5.0f)) * Sat(p.corneal_wetness); f32 wet = D_GGX(ndh, wa) * V_Smith(ndv, ndl, wa) * ndl; for (int c = 0; c < 3; ++c) { out.specular[c] = out.specular[c] * (1.0f - wf) + wet * wf; @@ -419,8 +420,8 @@ HumanBrdfSample HumanEvaluateCpu(const HumanSurfaceParameters& p, const f32 base if (p.transmission > 0.0f && ndl_d < 0.35f) { f32 optical = - std::max(p.extinction_scale * thickness / std::max(p.mean_free_path * p.subsurface_scale, 1e-4f), 0.0f); - f32 attenuation = std::exp(-optical); + rx::Max(p.extinction_scale * thickness / rx::Max(p.mean_free_path * p.subsurface_scale, 1e-4f), 0.0f); + f32 attenuation = ::exp(-optical); f32 back = Sat(Dot(v, {-l.x, -l.y, -l.z})); f32 lobe = back * back * Sat(0.35f - ndl_d) / 0.35f; for (int c = 0; c < 3; ++c) { @@ -439,16 +440,16 @@ f32 HumanTerminatorMultiplierCpu(const HumanSurfaceParameters& p, const f32 geom f32 cos_soft = ndl; if (p.smooth_terminator_amount > 0.0f) { - const f32 w = std::max(p.smooth_terminator_length, 0.0f); + const f32 w = rx::Max(p.smooth_terminator_length, 0.0f); const f32 soft = Sat((ndl_shading + w) / ((1.0f + w) * (1.0f + w))); const f32 gate = Sat(Dot(ng, rep) * 4.0f + 1.0f); cos_soft = ndl + (soft - ndl) * Sat(p.smooth_terminator_amount) * gate; } - const f32 wrap = std::max(p.smooth_terminator_length, 0.0f); + const f32 wrap = rx::Max(p.smooth_terminator_length, 0.0f); const f32 amount = Sat(p.smooth_terminator_amount); const f32 max_lift = (1.0f - amount) + amount * 2.0f / ((1.0f + wrap) * (1.0f + wrap)); - return std::min(cos_soft / std::max(ndl, 1e-4f), max_lift); + return rx::Min(cos_soft / rx::Max(ndl, 1e-4f), max_lift); } HumanBrdfSample StockBrdfCpu(const f32 base_color[3], f32 roughness, const f32 f0[3], @@ -456,13 +457,13 @@ HumanBrdfSample StockBrdfCpu(const f32 base_color[3], f32 roughness, const f32 f HumanBrdfSample out; V3 n = Load(n_in), v = Load(v_in), l = Load(l_in); f32 ndl = Sat(Dot(n, l)); - f32 ndv = std::max(Dot(n, v), 1e-4f); + f32 ndv = rx::Max(Dot(n, v), 1e-4f); V3 h = Norm(Add(l, v)); f32 ndh = Sat(Dot(n, h)); f32 vdh = Sat(Dot(v, h)); - f32 a = std::max(roughness * roughness, 1e-5f); + f32 a = rx::Max(roughness * roughness, 1e-5f); f32 lobe = D_GGX(ndh, a) * V_Smith(ndv, ndl, a); - f32 fpow = std::pow(Sat(1.0f - vdh), 5.0f); + f32 fpow = ::pow(Sat(1.0f - vdh), 5.0f); for (int c = 0; c < 3; ++c) { out.diffuse[c] = base_color[c] * (1.0f / kPi) * ndl; f32 f = f0[c] + (1.0f - f0[c]) * fpow; diff --git a/engine/render/pipeline/material_system.cc b/engine/render/pipeline/material_system.cc index a514d146..a3e3e9e9 100644 --- a/engine/render/pipeline/material_system.cc +++ b/engine/render/pipeline/material_system.cc @@ -2,14 +2,17 @@ #include "render/pipeline/human_material.h" -#include -#include -#include -#include +#include +#include #include "asset/bc_encode.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/numeric_limits.h" #include "core/log.h" #include "core/memory/small_vector.h" +#include "core/scalar.h" namespace rx::render { namespace { @@ -30,20 +33,20 @@ SkinCoeffs ComputeSkinCoeffs(const asset::Material::SkinParams& p) { SkinCoeffs c; c.g = p.anisotropy_g; c.ior = p.ior; - c.perfusion = std::clamp(p.perfusion, 0.0f, 1.0f); - const f32 scale = std::max(p.scatter_scale, 1e-4f); + c.perfusion = rx::Clamp(p.perfusion, 0.0f, 1.0f); + const f32 scale = rx::Max(p.scatter_scale, 1e-4f); for (int i = 0; i < 3; ++i) { - const f32 col = std::clamp(p.scatter_color[i], 0.0f, 1.0f); + const f32 col = rx::Clamp(p.scatter_color[i], 0.0f, 1.0f); c.scatter_color[i] = col; // Kulla-Conty 2017: invert the multiple-scattering albedo so the authored // colour is what's seen. Per channel; s is the surface-albedo scale factor. const f32 s = 4.09712f + 4.20863f * col - - std::sqrt(std::max(0.0f, 9.59217f + 41.6808f * col + + ::sqrt(rx::Max(0.0f, 9.59217f + 41.6808f * col + 17.7126f * col * col)); const f32 s2 = s * s; - const f32 alpha = std::clamp((1.0f - s2) / (1.0f - c.g * s2), 0.0f, 0.999f); + const f32 alpha = rx::Clamp((1.0f - s2) / (1.0f - c.g * s2), 0.0f, 0.999f); // mfp authored in mm; the renderer's world unit is metres (1 mm = 0.001 m). - const f32 mfp_world = std::max(p.mfp[i] * scale * 0.001f, 1e-6f); + const f32 mfp_world = rx::Max(p.mfp[i] * scale * 0.001f, 1e-6f); c.sigma_t[i] = 1.0f / mfp_world; c.sigma_s[i] = alpha * c.sigma_t[i]; } @@ -87,8 +90,8 @@ u64 MipSizeBytes(const FormatInfo& info, u32 width, u32 height) { u32 FullMipChainLength(u32 width, u32 height) { u32 levels = 1; while (width > 1 || height > 1) { - width = std::max(1u, width / 2); - height = std::max(1u, height / 2); + width = rx::Max(1u, width / 2); + height = rx::Max(1u, height / 2); ++levels; } return levels; @@ -128,13 +131,13 @@ bool DecodeAlphaGrid(const asset::Texture& tex, MaterialSystem::AlphaCoverage& o const u32 w = tex.width, h = tex.height; if (w == 0 || h == 0 || tex.data.empty()) return false; const u8* src = tex.data.data(); - const u32 gw = std::min(w, kAlphaGridDim); - const u32 gh = std::min(h, kAlphaGridDim); + const u32 gw = rx::Min(w, kAlphaGridDim); + const u32 gh = rx::Min(h, kAlphaGridDim); base::Vector sum(static_cast(gw) * gh); // value-initialized to 0 base::Vector cnt(static_cast(gw) * gh); auto add = [&](u32 x, u32 y, u32 a) { - u32 cx = std::min(gw - 1, x * gw / w); - u32 cy = std::min(gh - 1, y * gh / h); + u32 cx = rx::Min(gw - 1, x * gw / w); + u32 cy = rx::Min(gh - 1, y * gh / h); size_t c = static_cast(cy) * gw + cx; sum[c] += a; ++cnt[c]; @@ -241,16 +244,16 @@ bool DecodeAlphaGrid(const asset::Texture& tex, MaterialSystem::AlphaCoverage& o } // namespace -std::unique_ptr MaterialSystem::Create(Device& device, +base::UniquePointer MaterialSystem::Create(Device& device, BindlessRegistry* registry) { - auto system = std::unique_ptr(new MaterialSystem(device)); + auto system = base::UniquePointer(new MaterialSystem(device)); system->registry_ = registry; // Trilinear repeat sampler, anisotropic when the device supports it. Cached // by the device, never destroyed here. SamplerDesc sampler_desc{}; if (device.caps().max_anisotropy > 1.0f) { - sampler_desc.max_anisotropy = std::min(16.0f, device.caps().max_anisotropy); + sampler_desc.max_anisotropy = rx::Min(16.0f, device.caps().max_anisotropy); } system->sampler_ = device.GetSampler(sampler_desc); @@ -310,7 +313,7 @@ bool MaterialSystem::CreateDefaults() { default_material.roughness_factor = 0.8f; if (registry_) { BindlessRegistry::MaterialRecord record; - std::memcpy(record.base_color_factor, default_material.base_color_factor, sizeof(f32) * 4); + base::MemCopy(record.base_color_factor, default_material.base_color_factor, sizeof(f32) * 4); registry_->RegisterMaterial(record); // index 0, the fallback } default_set_ = AllocateSet(); @@ -336,8 +339,8 @@ GpuImage MaterialSystem::UploadTextureImage(const asset::Texture& texture, u32 f // mips in the asset and cannot be blitted. Partial (streamed) uploads only // happen on baked chains, never through the generate path. bool generate_mips = texture.mip_count == 1 && info.block_dim == 1; - u32 top_width = std::max(1u, texture.width >> first_mip); - u32 top_height = std::max(1u, texture.height >> first_mip); + u32 top_width = rx::Max(1u, texture.width >> first_mip); + u32 top_height = rx::Max(1u, texture.height >> first_mip); u32 mip_count = generate_mips ? FullMipChainLength(texture.width, texture.height) : texture.mip_count - first_mip; u32 upload_mips = generate_mips ? 1 : mip_count; @@ -349,8 +352,8 @@ GpuImage MaterialSystem::UploadTextureImage(const asset::Texture& texture, u32 f u32 height = texture.height; for (u32 mip = 0; mip < first_mip; ++mip) { skip += MipSizeBytes(info, width, height); - width = std::max(1u, width / 2); - height = std::max(1u, height / 2); + width = rx::Max(1u, width / 2); + height = rx::Max(1u, height / 2); } } u64 upload_bytes = 0; @@ -359,8 +362,8 @@ GpuImage MaterialSystem::UploadTextureImage(const asset::Texture& texture, u32 f u32 height = top_height; for (u32 mip = 0; mip < upload_mips; ++mip) { upload_bytes += MipSizeBytes(info, width, height); - width = std::max(1u, width / 2); - height = std::max(1u, height / 2); + width = rx::Max(1u, width / 2); + height = rx::Max(1u, height / 2); } } // upload_bytes is always positive here: every mip is at least one block of a @@ -399,7 +402,7 @@ GpuImage MaterialSystem::UploadTextureImage(const asset::Texture& texture, u32 f return {}; } } - std::memcpy(staging->mapped, texture.data.data() + skip, upload_bytes); + base::MemCopy(staging->mapped, texture.data.data() + skip, upload_bytes); device_.FlushBuffer(*staging, 0, upload_bytes); device_.RecordUpload([&](CommandList& cmd) { @@ -412,10 +415,10 @@ GpuImage MaterialSystem::UploadTextureImage(const asset::Texture& texture, u32 f for (u32 mip = 0; mip < upload_mips; ++mip) { regions.push_back({.buffer_offset = offset, .mip = mip, .extent = {width, height}}); offset += MipSizeBytes(info, width, height); - width = std::max(1u, width / 2); - height = std::max(1u, height / 2); + width = rx::Max(1u, width / 2); + height = rx::Max(1u, height / 2); } - cmd.CopyBufferToTexture(*staging, image, {regions.data(), regions.size()}); + cmd.CopyBufferToTexture(*staging, image, base::Span(regions.data(), regions.size())); if (generate_mips && mip_count > 1) { u32 src_width = texture.width; @@ -426,8 +429,8 @@ GpuImage MaterialSystem::UploadTextureImage(const asset::Texture& texture, u32 f .after = ResourceState::kCopySrc, .base_mip = mip - 1, .mip_count = 1}); - u32 dst_width = std::max(1u, src_width / 2); - u32 dst_height = std::max(1u, src_height / 2); + u32 dst_width = rx::Max(1u, src_width / 2); + u32 dst_height = rx::Max(1u, src_height / 2); cmd.BlitMip(image, mip - 1, {src_width, src_height}, mip, {dst_width, dst_height}); src_width = dst_width; src_height = dst_height; @@ -445,7 +448,7 @@ GpuImage MaterialSystem::UploadTextureImage(const asset::Texture& texture, u32 f .after = ResourceState::kShaderReadAll, .base_mip = mip_count - 1, .mip_count = 1}}; - cmd.TextureBarriers({finals, 2}); + cmd.TextureBarriers(base::Span(finals, 2)); } else { cmd.Barrier({.texture = image.handle, .before = ResourceState::kCopyDst, @@ -464,7 +467,7 @@ GpuBuffer* MaterialSystem::AcquireStaging(u64 bytes) { // Round up so a stream of slightly-growing textures re-creates the buffer // a handful of times instead of once per texture. constexpr u64 kGranule = 4u << 20; - if (bytes > std::numeric_limits::max() - (kGranule - 1)) return nullptr; + if (bytes > base::MinMax::max() - (kGranule - 1)) return nullptr; const u64 grown_bytes = (bytes + kGranule - 1) / kGranule * kGranule; GpuBuffer grown = device_.CreateBuffer(grown_bytes, kBufferUsageTransferSrc, true); if (!grown.mapped) { @@ -485,8 +488,8 @@ u64 MaterialSystem::BytesForMips(const asset::Texture& texture, u32 first_mip) c u32 height = texture.height; for (u32 mip = 0; mip < texture.mip_count; ++mip) { if (mip >= first_mip) bytes += MipSizeBytes(info, width, height); - width = std::max(1u, width / 2); - height = std::max(1u, height / 2); + width = rx::Max(1u, width / 2); + height = rx::Max(1u, height / 2); } return bytes; } @@ -497,7 +500,7 @@ bool MaterialSystem::UploadTexture(const asset::Texture& texture, u64 id_salt) { GpuImage image = UploadTextureImage(texture); if (!image) return false; - auto record = std::make_unique(); + auto record = base::MakeUnique(); record->key = key; record->image = image; record->format = texture.format; @@ -508,7 +511,7 @@ bool MaterialSystem::UploadTexture(const asset::Texture& texture, u64 id_salt) { record->full_bytes = baked ? texture.data.size() : texture.data.size() * 4 / 3; record->resident_bytes = record->full_bytes; if (baked) { - u32 dim = std::max(texture.width, texture.height); + u32 dim = rx::Max(texture.width, texture.height); u32 tail = 0; while (tail + 1 < texture.mip_count && (dim >> tail) > kTailMaxDim) ++tail; record->tail_first_mip = tail; @@ -527,12 +530,12 @@ bool MaterialSystem::UploadTexture(const asset::Texture& texture, u64 id_salt) { // to a full-size (opacity 1) stand-in, i.e. today's behavior. AlphaCoverage cov; if (DecodeAlphaGrid(texture, cov) && cov.mean < kOpaqueMeanThreshold) { - texture_alpha_.insert(key, std::move(cov)); + texture_alpha_.insert(key, base::move(cov)); } } resident_bytes_ += record->resident_bytes; u32 index = static_cast(texture_records_.size()); - texture_records_.push_back(std::move(record)); + texture_records_.push_back(base::move(record)); textures_.insert(key, index); return true; } @@ -559,7 +562,7 @@ BindingSetHandle MaterialSystem::AllocateSet() { MaterialSystem::TextureRecord* MaterialSystem::record_for(u64 hash) { if (hash == 0) return nullptr; const u32* index = textures_.find(hash); - return index ? texture_records_[*index].get() : nullptr; + return index ? texture_records_[*index].Get_UseOnlyIfYouKnowWhatYouareDoing() : nullptr; } const GpuImage* MaterialSystem::texture_or(u64 hash, const GpuImage& fallback) const { @@ -617,8 +620,8 @@ void MaterialSystem::WriteSetBindings(BindingSetHandle set, const MaterialRuntim // nobody could act on. void MaterialSystem::BuildParams(const asset::Material& material, u64 id_salt, Params& params, u64 out_map_keys[12]) { - std::memcpy(params.base_color_factor, material.base_color_factor, sizeof(f32) * 4); - std::memcpy(params.emissive_factor, material.emissive_factor, sizeof(f32) * 3); + base::MemCopy(params.base_color_factor, material.base_color_factor, sizeof(f32) * 4); + base::MemCopy(params.emissive_factor, material.emissive_factor, sizeof(f32) * 3); params.metallic_factor = material.metallic_factor; params.roughness_factor = material.roughness_factor; params.ao_strength = material.ao_strength; @@ -627,16 +630,16 @@ void MaterialSystem::BuildParams(const asset::Material& material, u64 id_salt, P params.clearcoat_roughness = material.clearcoat_roughness; params.anisotropy = material.anisotropy; params.ior = material.ior; - std::memcpy(params.sheen_color, material.sheen_color, sizeof(f32) * 3); + base::MemCopy(params.sheen_color, material.sheen_color, sizeof(f32) * 3); params.sheen_roughness = material.sheen_roughness; - std::memcpy(params.subsurface_color, material.subsurface_color, sizeof(f32) * 3); + base::MemCopy(params.subsurface_color, material.subsurface_color, sizeof(f32) * 3); params.subsurface = material.subsurface; params.iridescence = material.iridescence; params.iridescence_thickness = material.iridescence_thickness; params.transmission = material.transmission; - std::memcpy(params.openpbr_specular_color, material.openpbr_specular_color, sizeof(f32) * 3); + base::MemCopy(params.openpbr_specular_color, material.openpbr_specular_color, sizeof(f32) * 3); params.specular_weight = material.specular_weight; - std::memcpy(params.coat_color, material.coat_color, sizeof(f32) * 3); + base::MemCopy(params.coat_color, material.coat_color, sizeof(f32) * 3); params.coat_ior = material.coat_ior; params.base_diffuse_roughness = material.base_diffuse_roughness; params.coat_darkening = material.coat_darkening; @@ -645,7 +648,7 @@ void MaterialSystem::BuildParams(const asset::Material& material, u64 id_salt, P params.uv_scroll[1] = material.uv_scroll_v; params.emissive_pulse[0] = material.emissive_pulse[0]; params.emissive_pulse[1] = material.emissive_pulse[1]; - std::memcpy(params.specular_color, material.specular_color, sizeof(f32) * 3); + base::MemCopy(params.specular_color, material.specular_color, sizeof(f32) * 3); params.specular_strength = material.specular_strength; params.env_reflect = material.env_reflect; params.soft_lighting = material.soft_lighting; @@ -669,9 +672,9 @@ void MaterialSystem::BuildParams(const asset::Material& material, u64 id_salt, P if (material.skin) { params.flags |= kFlagSkin; const SkinCoeffs c = ComputeSkinCoeffs(material.skin_params); - std::memcpy(params.sss_sigma_t, c.sigma_t, sizeof(f32) * 3); - std::memcpy(params.sss_sigma_s, c.sigma_s, sizeof(f32) * 3); - std::memcpy(params.sss_scatter_color, c.scatter_color, sizeof(f32) * 3); + base::MemCopy(params.sss_sigma_t, c.sigma_t, sizeof(f32) * 3); + base::MemCopy(params.sss_sigma_s, c.sigma_s, sizeof(f32) * 3); + base::MemCopy(params.sss_scatter_color, c.scatter_color, sizeof(f32) * 3); params.sss_anisotropy_g = c.g; params.sss_perfusion = c.perfusion; params.sss_ior = c.ior; @@ -698,7 +701,7 @@ void MaterialSystem::BuildParams(const asset::Material& material, u64 id_salt, P params.human_transport[1] = h.transmission; params.human_transport[2] = h.extinction_scale; params.human_transport[3] = h.corneal_wetness; - std::memcpy(params.human_tint, h.transmission_tint, sizeof(f32) * 3); + base::MemCopy(params.human_tint, h.transmission_tint, sizeof(f32) * 3); params.human_tint[3] = h.residual_weight; params.human_layer[0] = h.cavity_occlusion; params.human_layer[1] = h.specular_normal_strength; @@ -739,7 +742,7 @@ void MaterialSystem::BuildParams(const asset::Material& material, u64 id_salt, P if (material.hair) { params.flags |= kFlagHair; const asset::Material::HairParams& h = material.hair_params; - std::memcpy(params.hair0, h.sigma_a, sizeof(f32) * 3); + base::MemCopy(params.hair0, h.sigma_a, sizeof(f32) * 3); params.hair0[3] = h.beta_m; params.hair1[0] = h.beta_n; params.hair1[1] = h.alpha; @@ -806,7 +809,7 @@ void MaterialSystem::BuildParams(const asset::Material& material, u64 id_salt, P // would leave the params stale. Any unresolvable layer keeps the whole // material on the legacy 3-layer path (flag unset). if (material.is_terrain && material.terrain_layer_count > 0 && registry_) { - u32 count = std::min(material.terrain_layer_count, 8u); + u32 count = rx::Min(material.terrain_layer_count, 8u); bool complete = true; for (u32 s = 0; s < count && complete; ++s) { u64 albedo_key = material.terrain_layers[s].hash ^ id_salt; @@ -853,13 +856,13 @@ bool MaterialSystem::WriteSet(BindingSetHandle set, u32 pool, u32 param_index, GpuBuffer& buffer = param_buffers_[pool]; u64 offset = static_cast(param_index) * kParamStride; - std::memcpy(static_cast(buffer.mapped) + offset, ¶ms, sizeof(params)); + base::MemCopy(static_cast(buffer.mapped) + offset, ¶ms, sizeof(params)); MaterialRuntime runtime; runtime.set = set; runtime.pool = pool; runtime.param_index = param_index; - std::memcpy(runtime.map_keys, out_map_keys, sizeof(runtime.map_keys)); + base::MemCopy(runtime.map_keys, out_map_keys, sizeof(runtime.map_keys)); WriteSetBindings(set, runtime); return true; } @@ -872,8 +875,8 @@ const GpuImage* MaterialSystem::find_texture(u64 hash) const { BindlessRegistry::MaterialRecord MaterialSystem::BuildBindlessRecord( const asset::Material& material, u64 id_salt, asset::AlphaMode mode) { BindlessRegistry::MaterialRecord record; - std::memcpy(record.base_color_factor, material.base_color_factor, sizeof(f32) * 4); - std::memcpy(record.emissive, material.emissive_factor, sizeof(f32) * 3); + base::MemCopy(record.base_color_factor, material.base_color_factor, sizeof(f32) * 4); + base::MemCopy(record.emissive, material.emissive_factor, sizeof(f32) * 3); record.roughness = material.roughness_factor; record.metallic = material.metallic_factor; if (TextureRecord* base = record_for(material.base_color.hash ^ id_salt)) { @@ -890,9 +893,9 @@ BindlessRegistry::MaterialRecord MaterialSystem::BuildBindlessRecord( if (material.skin) { record.flags |= BindlessRegistry::kMaterialSkin; const SkinCoeffs c = ComputeSkinCoeffs(material.skin_params); - std::memcpy(record.sss_sigma_t, c.sigma_t, sizeof(f32) * 3); - std::memcpy(record.sss_sigma_s, c.sigma_s, sizeof(f32) * 3); - std::memcpy(record.sss_scatter_color, c.scatter_color, sizeof(f32) * 3); + base::MemCopy(record.sss_sigma_t, c.sigma_t, sizeof(f32) * 3); + base::MemCopy(record.sss_sigma_s, c.sigma_s, sizeof(f32) * 3); + base::MemCopy(record.sss_scatter_color, c.scatter_color, sizeof(f32) * 3); record.sss_anisotropy_g = c.g; record.sss_perfusion = c.perfusion; record.sss_ior = c.ior; @@ -916,7 +919,7 @@ BindlessRegistry::MaterialRecord MaterialSystem::BuildBindlessRecord( record.human_spec[2] = h.secondary_specular_weight; record.human_spec[3] = h.mean_free_path; record.human_transmission[0] = h.transmission; - std::memcpy(record.human_transmission + 1, h.transmission_tint, sizeof(f32) * 3); + base::MemCopy(record.human_transmission + 1, h.transmission_tint, sizeof(f32) * 3); record.human_extra[0] = h.light_shape_response; record.human_extra[1] = h.thickness_scale; record.human_extra[2] = h.subsurface_scale; @@ -964,7 +967,7 @@ bool MaterialSystem::UpdateMaterialParams(const asset::Material& material, u64 i BuildParams(material, id_salt, params, keys); GpuBuffer& buffer = param_buffers_[runtime.pool]; const u64 offset = static_cast(runtime.param_index) * kParamStride; - std::memcpy(static_cast(buffer.mapped) + offset, ¶ms, sizeof(params)); + base::MemCopy(static_cast(buffer.mapped) + offset, ¶ms, sizeof(params)); return true; } @@ -987,8 +990,8 @@ bool MaterialSystem::UploadMaterial(const asset::Material& material, u64 id_salt blend_modes_.insert(key, static_cast(mode)); if (mode == asset::AlphaMode::kMask) runtime.alpha_cutoff = material.alpha_cutoff; MaterialColor color; - std::memcpy(color.albedo, material.base_color_factor, sizeof(f32) * 3); - std::memcpy(color.emissive, material.emissive_factor, sizeof(f32) * 3); + base::MemCopy(color.albedo, material.base_color_factor, sizeof(f32) * 3); + base::MemCopy(color.emissive, material.emissive_factor, sizeof(f32) * 3); colors_.insert(key, color); if (material.is_water) water_.insert(key, 1); if (material.effect) effects_.insert(key, material.effect_additive ? 2 : 1); @@ -1220,11 +1223,11 @@ MaterialSystem::MaterialColor MaterialSystem::material_color(u64 material_hash) f32 MaterialSystem::AlphaCoverage::Sample(f32 u, f32 v) const { if (alpha.empty() || width == 0 || height == 0) return mean; - u -= std::floor(u); // wrap into [0,1) - v -= std::floor(v); + u -= ::floor(u); // wrap into [0,1) + v -= ::floor(v); f32 fx = u * static_cast(width) - 0.5f; f32 fy = v * static_cast(height) - 0.5f; - i32 x0 = static_cast(std::floor(fx)), y0 = static_cast(std::floor(fy)); + i32 x0 = static_cast(::floor(fx)), y0 = static_cast(::floor(fy)); f32 tx = fx - static_cast(x0), ty = fy - static_cast(y0); auto at = [&](i32 x, i32 y) -> f32 { x = ((x % static_cast(width)) + static_cast(width)) % static_cast(width); diff --git a/engine/render/pipeline/material_system.h b/engine/render/pipeline/material_system.h index 19b4df1f..f990e093 100644 --- a/engine/render/pipeline/material_system.h +++ b/engine/render/pipeline/material_system.h @@ -1,13 +1,13 @@ #ifndef RX_RENDER_MATERIAL_SYSTEM_H_ #define RX_RENDER_MATERIAL_SYSTEM_H_ -#include #include #include #include "asset/material.h" #include "asset/texture.h" +#include "base/memory/unique_pointer.h" #include "render/core/bindless.h" #include "render/rhi/device.h" @@ -167,7 +167,7 @@ class MaterialSystem { const GpuImage* find_texture(u64 hash) const; // registry may be null (no raytracing); hit-shading tables are skipped. - static std::unique_ptr Create(Device& device, BindlessRegistry* registry); + static base::UniquePointer Create(Device& device, BindlessRegistry* registry); ~MaterialSystem(); MaterialSystem(const MaterialSystem&) = delete; @@ -397,7 +397,7 @@ class MaterialSystem { u64 staging_bytes_ = 0; base::Vector param_buffers_; // one per pool, host visible - base::Vector> texture_records_; + base::Vector> texture_records_; base::Vector material_records_; base::Vector retired_; base::UnorderedMap textures_; // texture hash -> texture_records_ diff --git a/engine/render/pipeline/mesh_pipeline.cc b/engine/render/pipeline/mesh_pipeline.cc index 16005ee7..b4355dd9 100644 --- a/engine/render/pipeline/mesh_pipeline.cc +++ b/engine/render/pipeline/mesh_pipeline.cc @@ -1,6 +1,7 @@ #include "render/pipeline/mesh_pipeline.h" #include "asset/mesh.h" +#include "base/memory/unique_pointer.h" #include "core/log.h" #include "shaders/mesh_instance_vs_hlsl.h" #include "shaders/mesh_ps_hlsl.h" @@ -16,14 +17,14 @@ namespace rx::render { -std::unique_ptr MeshPipeline::Create(Device& device, Format color_format, +base::UniquePointer MeshPipeline::Create(Device& device, Format color_format, Format motion_format, Format normal_format, Format depth_format, BindingLayoutHandle material_layout, BindingLayoutHandle environment_layout, BindingLayoutHandle bindless_layout, u32 samples) { - auto pipeline = std::unique_ptr(new MeshPipeline(device)); + auto pipeline = base::UniquePointer(new MeshPipeline(device)); bool rt = device.caps().ray_query; bool mesh_caps = device.caps().mesh_shaders; pipeline->has_bindless_ = static_cast(bindless_layout); diff --git a/engine/render/pipeline/mesh_pipeline.h b/engine/render/pipeline/mesh_pipeline.h index 9569446a..106fbb2e 100644 --- a/engine/render/pipeline/mesh_pipeline.h +++ b/engine/render/pipeline/mesh_pipeline.h @@ -1,9 +1,9 @@ #ifndef RX_RENDER_MESH_PIPELINE_H_ #define RX_RENDER_MESH_PIPELINE_H_ -#include -#include +#include +#include "base/memory/unique_pointer.h" #include "core/math.h" #include "render/rhi/device.h" @@ -249,7 +249,7 @@ class MeshPipeline { // samples > 1 builds the opaque/prepass raster pipelines multisampled // (kMsaa mode). Blend pipelines stay single-sampled: the transparent pass // always runs after the resolve. - static std::unique_ptr Create(Device& device, Format color_format, + static base::UniquePointer Create(Device& device, Format color_format, Format motion_format, Format normal_format, Format depth_format, BindingLayoutHandle material_layout, diff --git a/engine/render/pipeline/meshlet.cc b/engine/render/pipeline/meshlet.cc index 49f35a7e..80c528a4 100644 --- a/engine/render/pipeline/meshlet.cc +++ b/engine/render/pipeline/meshlet.cc @@ -1,12 +1,14 @@ #include "render/pipeline/meshlet.h" -#include -#include -#include -#include -#include +#include +#include +#include "base/algorithm.h" +#include "base/containers/pair.h" +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" #include "core/log.h" +#include "core/scalar.h" #include "render/rhi/device.h" #include "shaders/meshlet_ms_hlsl.h" #include "shaders/meshlet_ps_hlsl.h" @@ -68,7 +70,7 @@ MeshletGeometry BuildImpl(const asset::Vertex* verts, u32 vertex_count, const u3 f32 radius = 0.0f; for (u32 i = 0; i < local_count; ++i) { Vec3 d = P(local_global[i]) - center; - radius = std::max(radius, std::sqrt(d.x * d.x + d.y * d.y + d.z * d.z)); + radius = rx::Max(radius, ::sqrt(d.x * d.x + d.y * d.y + d.z * d.z)); } m.center_radius[0] = center.x; m.center_radius[1] = center.y; @@ -82,10 +84,10 @@ MeshletGeometry BuildImpl(const asset::Vertex* verts, u32 vertex_count, const u3 u32 packed = out.triangles[t]; Vec3 n = Cross(P(local_global[(packed >> 8) & 0xff]) - P(local_global[packed & 0xff]), P(local_global[(packed >> 16) & 0xff]) - P(local_global[packed & 0xff])); - f32 len = std::sqrt(n.x * n.x + n.y * n.y + n.z * n.z); + f32 len = ::sqrt(n.x * n.x + n.y * n.y + n.z * n.z); if (len > 1e-8f) axis = axis + n * (1.0f / len); } - f32 alen = std::sqrt(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z); + f32 alen = ::sqrt(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z); if (alen > 1e-6f) { axis = axis * (1.0f / alen); f32 min_c = 1.0f; @@ -93,13 +95,13 @@ MeshletGeometry BuildImpl(const asset::Vertex* verts, u32 vertex_count, const u3 u32 packed = out.triangles[t]; Vec3 n = Cross(P(local_global[(packed >> 8) & 0xff]) - P(local_global[packed & 0xff]), P(local_global[(packed >> 16) & 0xff]) - P(local_global[packed & 0xff])); - f32 len = std::sqrt(n.x * n.x + n.y * n.y + n.z * n.z); - if (len > 1e-8f) min_c = std::min(min_c, (n.x * axis.x + n.y * axis.y + n.z * axis.z) / len); + f32 len = ::sqrt(n.x * n.x + n.y * n.y + n.z * n.z); + if (len > 1e-8f) min_c = rx::Min(min_c, (n.x * axis.x + n.y * axis.y + n.z * axis.z) / len); } m.cone[0] = axis.x; m.cone[1] = axis.y; m.cone[2] = axis.z; - m.cone[3] = min_c > 0.0f ? std::sqrt(1.0f - min_c * min_c) : 2.0f; // 2 = never cull + m.cone[3] = min_c > 0.0f ? ::sqrt(1.0f - min_c * min_c) : 2.0f; // 2 = never cull } else { m.cone[3] = 2.0f; // degenerate, never cone-cull } @@ -118,19 +120,21 @@ MeshletGeometry BuildImpl(const asset::Vertex* verts, u32 vertex_count, const u3 Vec3 lo{1e30f, 1e30f, 1e30f}, hi{-1e30f, -1e30f, -1e30f}; for (u32 i = 0; i < vertex_count; ++i) { Vec3 p = P(i); - lo = {std::min(lo.x, p.x), std::min(lo.y, p.y), std::min(lo.z, p.z)}; - hi = {std::max(hi.x, p.x), std::max(hi.y, p.y), std::max(hi.z, p.z)}; + lo = {rx::Min(lo.x, p.x), rx::Min(lo.y, p.y), rx::Min(lo.z, p.z)}; + hi = {rx::Max(hi.x, p.x), rx::Max(hi.y, p.y), rx::Max(hi.z, p.z)}; } - Vec3 ext{std::max(hi.x - lo.x, 1e-6f), std::max(hi.y - lo.y, 1e-6f), std::max(hi.z - lo.z, 1e-6f)}; - std::vector> order(tri_total); // (morton, triangle index) + Vec3 ext{rx::Max(hi.x - lo.x, 1e-6f), rx::Max(hi.y - lo.y, 1e-6f), rx::Max(hi.z - lo.z, 1e-6f)}; + base::Vector> order(tri_total); // (morton, triangle index) for (size_t t = 0; t < tri_total; ++t) { Vec3 c = (P(indices[t * 3]) + P(indices[t * 3 + 1]) + P(indices[t * 3 + 2])) * (1.0f / 3.0f); - u32 qx = static_cast(std::clamp((c.x - lo.x) / ext.x, 0.0f, 1.0f) * 1023.0f); - u32 qy = static_cast(std::clamp((c.y - lo.y) / ext.y, 0.0f, 1.0f) * 1023.0f); - u32 qz = static_cast(std::clamp((c.z - lo.z) / ext.z, 0.0f, 1.0f) * 1023.0f); + u32 qx = static_cast(rx::Clamp((c.x - lo.x) / ext.x, 0.0f, 1.0f) * 1023.0f); + u32 qy = static_cast(rx::Clamp((c.y - lo.y) / ext.y, 0.0f, 1.0f) * 1023.0f); + u32 qz = static_cast(rx::Clamp((c.z - lo.z) / ext.z, 0.0f, 1.0f) * 1023.0f); order[t] = {Morton3(qx, qy, qz), static_cast(t)}; } - std::sort(order.begin(), order.end()); + // Keys are unique (the triangle index breaks every tie), so any correct + // sort yields this order. + base::Sort(order.data(), order.data() + order.size()); for (const auto& ord : order) { size_t i = static_cast(ord.second) * 3; @@ -151,13 +155,13 @@ MeshletGeometry BuildImpl(const asset::Vertex* verts, u32 vertex_count, const u3 // This triangle's unit normal, to keep the meshlet's normal cone tight. Vec3 pa = P(g[0]); Vec3 n = Cross(P(g[1]) - pa, P(g[2]) - pa); - f32 nlen = std::sqrt(n.x * n.x + n.y * n.y + n.z * n.z); + f32 nlen = ::sqrt(n.x * n.x + n.y * n.y + n.z * n.z); if (nlen > 1e-8f) n = n * (1.0f / nlen); // Bound the cone half-angle (~45deg from the running mean) so backface cone // culling stays effective; finalize early when a triangle would widen it. bool cone_break = false; if (cone_split && local_count > 0) { - f32 slen = std::sqrt(cone_sum.x * cone_sum.x + cone_sum.y * cone_sum.y + cone_sum.z * cone_sum.z); + f32 slen = ::sqrt(cone_sum.x * cone_sum.x + cone_sum.y * cone_sum.y + cone_sum.z * cone_sum.z); if (slen > 1e-6f && (n.x * cone_sum.x + n.y * cone_sum.y + n.z * cone_sum.z) / slen < 0.85f) { cone_break = true; } @@ -257,7 +261,7 @@ void MeshletPass::Upload(Device& device, const asset::Mesh& mesh) { if (meshlet_count_ == 0) return; // The demo shader pulls a compact position+normal vertex (24 bytes). - std::vector verts; + base::Vector verts; verts.reserve(lod.vertices.size()); for (const asset::Vertex& v : lod.vertices) { verts.push_back({v.position[0], v.position[1], v.position[2], v.normal[0], v.normal[1], @@ -290,10 +294,10 @@ void MeshletPass::AddToGraph(RenderGraph& graph, ResourceHandle color, ResourceH if (counters_[slot].mapped) *static_cast(counters_[slot].mapped) = 0; const MeshletCamera cam{view_proj}; - std::memcpy(camera_[slot].mapped, &cam, sizeof(cam)); + base::MemCopy(camera_[slot].mapped, &cam, sizeof(cam)); MeshletPush push{}; - std::memcpy(push.planes, planes, sizeof(push.planes)); + base::MemCopy(push.planes, planes, sizeof(push.planes)); push.camera[0] = camera.x; push.camera[1] = camera.y; push.camera[2] = camera.z; @@ -311,7 +315,7 @@ void MeshletPass::AddToGraph(RenderGraph& graph, ResourceHandle color, ResourceH ColorAttachment color_att{.view = target.view, .load = LoadOp::kLoad}; DepthAttachment depth_att{.view = ctx.graph->image(depth).view, .load = LoadOp::kLoad}; ctx.cmd->BeginRendering( - {.extent = target.extent, .colors = {&color_att, 1}, .depth = &depth_att}); + {.extent = target.extent, .colors = base::Span(&color_att, 1), .depth = &depth_att}); ctx.cmd->BindPipeline(pipeline_); ctx.cmd->BindTransient(0, {Bind::StorageBuffer(0, meshlets_), Bind::StorageBuffer(1, meshlet_vertices_), diff --git a/engine/render/pipeline/virtual_geometry.cc b/engine/render/pipeline/virtual_geometry.cc index 62a9a659..7b9b2e01 100644 --- a/engine/render/pipeline/virtual_geometry.cc +++ b/engine/render/pipeline/virtual_geometry.cc @@ -1,14 +1,18 @@ #include "render/pipeline/virtual_geometry.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include "asset/simplify.h" +#include "base/containers/span.h" +#include "base/containers/unordered_map.h" +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" #include "core/log.h" +#include "core/scalar.h" #include "render/pipeline/meshlet.h" #include "shaders/fullscreen_vs_slang.h" #include "shaders/meshlet_ps_hlsl.h" @@ -21,6 +25,7 @@ #include "shaders/vgeo_sw_cs_hlsl.h" #include "shaders/vgeo_vis_ms_hlsl.h" #include "shaders/vgeo_vis_ps_hlsl.h" +#include "core/sort.h" namespace rx::render { namespace { @@ -227,7 +232,7 @@ bool VirtualGeometryPass::Initialize(Device& device, Format color_format, Format SamplerDesc albedo_sampler_desc{}; if (device.caps().max_anisotropy > 1.0f) { - albedo_sampler_desc.max_anisotropy = std::min(16.0f, device.caps().max_anisotropy); + albedo_sampler_desc.max_anisotropy = rx::Min(16.0f, device.caps().max_anisotropy); } albedo_sampler_ = device.GetSampler(albedo_sampler_desc); @@ -276,7 +281,7 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { const asset::MeshLod& lod = mesh.lods[0]; const u32 vertex_count = static_cast(lod.vertices.size()); - std::vector positions(vertex_count); + base::Vector positions(vertex_count); for (u32 i = 0; i < vertex_count; ++i) { positions[i] = {lod.vertices[i].position[0], lod.vertices[i].position[1], lod.vertices[i].position[2]}; @@ -287,13 +292,13 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { // created it. Level 0 clusters are exact (error 0, own bounds). struct WorkCluster { Meshlet m; // bounds/cone from the meshlet build - std::vector indices; // global triangle list + base::Vector indices; // global triangle list f32 self_error = 0.0f; f32 self_sphere[4] = {0, 0, 0, 0}; u32 dag_index = 0; // where it landed in the output array }; - std::vector dag; + base::Vector dag; base::Vector all_vertex_indices; base::Vector all_triangles; @@ -301,10 +306,10 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { // 8-bit indexing; inputs come from BuildMeshletGeometry so <=64 uniques). auto emit = [&](WorkCluster& c, u32 level) { DagMeshlet d{}; - std::memcpy(d.center_radius, c.m.center_radius, sizeof(d.center_radius)); - std::memcpy(d.cone, c.m.cone, sizeof(d.cone)); - std::memcpy(d.self_sphere, c.self_sphere, sizeof(d.self_sphere)); - std::memcpy(d.parent_sphere, c.self_sphere, sizeof(d.parent_sphere)); + base::MemCopy(d.center_radius, c.m.center_radius, sizeof(d.center_radius)); + base::MemCopy(d.cone, c.m.cone, sizeof(d.cone)); + base::MemCopy(d.self_sphere, c.self_sphere, sizeof(d.self_sphere)); + base::MemCopy(d.parent_sphere, c.self_sphere, sizeof(d.parent_sphere)); d.self_error = c.self_error; d.parent_error = FLT_MAX; d.lod = level; @@ -341,8 +346,8 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { // Turns an index list into work clusters via the meshlet builder (cone // splitting off: coarse levels would fragment into tiny clusters whose // locked borders stall further simplification). - auto make_clusters = [&](const std::vector& indices, f32 self_error, - const f32 self_sphere[4], std::vector* out) { + auto make_clusters = [&](const base::Vector& indices, f32 self_error, + const f32 self_sphere[4], base::Vector* out) { MeshletGeometry geo = BuildMeshletGeometry(lod.vertices.data(), vertex_count, indices.data(), static_cast(indices.size()), /*cone_split=*/false); @@ -351,9 +356,9 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { c.m = m; c.self_error = self_error; if (self_sphere) { - std::memcpy(c.self_sphere, self_sphere, sizeof(c.self_sphere)); + base::MemCopy(c.self_sphere, self_sphere, sizeof(c.self_sphere)); } else { - std::memcpy(c.self_sphere, m.center_radius, sizeof(c.self_sphere)); + base::MemCopy(c.self_sphere, m.center_radius, sizeof(c.self_sphere)); } c.indices.reserve(static_cast(m.triangle_count) * 3); for (u32 t = 0; t < m.triangle_count; ++t) { @@ -362,13 +367,13 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { c.indices.push_back(geo.vertex_indices[m.vertex_offset + ((packed >> (8 * k)) & 0xffu)]); } } - out->push_back(std::move(c)); + out->push_back(base::move(c)); } }; - std::vector current; + base::Vector current; { - std::vector root_indices(lod.indices.begin(), lod.indices.end()); + base::Vector root_indices(lod.indices.begin(), lod.indices.end()); make_clusters(root_indices, 0.0f, nullptr, ¤t); } @@ -382,18 +387,19 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { // Morton-order the clusters so group seeds walk spatial patches. const u32 count = static_cast(current.size()); - std::vector order(count); + base::Vector order(count); for (u32 i = 0; i < count; ++i) order[i] = i; Vec3 lo{1e30f, 1e30f, 1e30f}, hi{-1e30f, -1e30f, -1e30f}; for (const WorkCluster& c : current) { - lo = {std::min(lo.x, c.m.center_radius[0]), std::min(lo.y, c.m.center_radius[1]), - std::min(lo.z, c.m.center_radius[2])}; - hi = {std::max(hi.x, c.m.center_radius[0]), std::max(hi.y, c.m.center_radius[1]), - std::max(hi.z, c.m.center_radius[2])}; + lo = {rx::Min(lo.x, c.m.center_radius[0]), rx::Min(lo.y, c.m.center_radius[1]), + rx::Min(lo.z, c.m.center_radius[2])}; + hi = {rx::Max(hi.x, c.m.center_radius[0]), rx::Max(hi.y, c.m.center_radius[1]), + rx::Max(hi.z, c.m.center_radius[2])}; } - Vec3 ext{std::max(hi.x - lo.x, 1e-6f), std::max(hi.y - lo.y, 1e-6f), - std::max(hi.z - lo.z, 1e-6f)}; - std::sort(order.begin(), order.end(), [&](u32 a, u32 b) { + Vec3 ext{rx::Max(hi.x - lo.x, 1e-6f), rx::Max(hi.y - lo.y, 1e-6f), + rx::Max(hi.z - lo.z, 1e-6f)}; + // Stable: quantized codes tie often, and the order seeds grouping. + rx::StableSort(order.data(), order.data() + order.size(), [&](u32 a, u32 b) { auto code = [&](u32 i) { const f32* c = current[i].m.center_radius; return Morton3(static_cast((c[0] - lo.x) / ext.x * 1023.0f), @@ -405,7 +411,7 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { // Vertex use counts across the level: a vertex referenced outside the // group is a border and must be locked. - std::vector use_count(vertex_count, 0); + base::Vector use_count(vertex_count, 0); for (const WorkCluster& c : current) { for (u32 v : c.indices) ++use_count[v]; } @@ -414,7 +420,7 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { // connectivity instead of raw morton runs, so fewer vertices sit on group // borders and the locked-edge simplification keeps making progress on the // coarse levels (morton runs used to strand groups that refuse to shrink). - std::unordered_map> vertex_clusters; + base::UnorderedMap> vertex_clusters; for (u32 i = 0; i < count; ++i) { for (u32 v : current[i].indices) { auto& list = vertex_clusters[v]; @@ -422,16 +428,16 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { } } - std::vector grouped(count, 0); - std::vector score(count, 0); - std::vector touched; + base::Vector grouped(count, 0); + base::Vector score(count, 0); + base::Vector touched; // Group-local scratch, level-sized once and reset via the touched list: // allocating these per group is quadratic in vertex count (fatal on // multi-million-vertex source meshes). - std::vector group_use(vertex_count, 0); - std::vector lock(vertex_count, 0); - std::vector group_verts; - std::vector next; + base::Vector group_use(vertex_count, 0); + base::Vector lock(vertex_count, 0); + base::Vector group_verts; + base::Vector next; bool progressed = false; for (u32 seed_pos = 0; seed_pos < count; ++seed_pos) { u32 seed = order[seed_pos]; @@ -439,7 +445,7 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { // Greedy fill: repeatedly take the ungrouped neighbor sharing the most // vertices with the group so far. - std::vector members; + base::Vector members; touched.clear(); auto add_member = [&](u32 ci) { members.push_back(ci); @@ -467,7 +473,7 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { } for (u32 nb : touched) score[nb] = 0; - std::vector group_indices; + base::Vector group_indices; group_verts.clear(); f32 max_child_error = 0.0f; Vec3 center{}; @@ -478,7 +484,7 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { if (group_use[v] == 0) group_verts.push_back(v); ++group_use[v]; } - max_child_error = std::max(max_child_error, c.self_error); + max_child_error = rx::Max(max_child_error, c.self_error); center = center + Vec3{c.self_sphere[0], c.self_sphere[1], c.self_sphere[2]}; } center = center * (1.0f / static_cast(members.size())); @@ -486,8 +492,8 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { for (u32 ci : members) { const WorkCluster& c = current[ci]; Vec3 d = Vec3{c.self_sphere[0], c.self_sphere[1], c.self_sphere[2]} - center; - radius = std::max(radius, - std::sqrt(d.x * d.x + d.y * d.y + d.z * d.z) + c.self_sphere[3]); + radius = rx::Max(radius, + ::sqrt(d.x * d.x + d.y * d.y + d.z * d.z) + c.self_sphere[3]); } f32 group_sphere[4] = {center.x, center.y, center.z, radius}; @@ -496,7 +502,7 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { } f32 err = 0.0f; - std::vector simplified = asset::SimplifyIndices( + base::Vector simplified = asset::SimplifyIndices( positions.data(), vertex_count, group_indices.data(), static_cast(group_indices.size()), static_cast(group_indices.size()) / 2, lock.data(), &err); @@ -510,12 +516,12 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { progressed = true; // Monotonic error up the DAG. - f32 group_error = std::max(err, max_child_error); + f32 group_error = rx::Max(err, max_child_error); for (u32 ci : members) { WorkCluster& c = current[ci]; DagMeshlet& d = dag[c.dag_index]; d.parent_error = group_error; - std::memcpy(d.parent_sphere, group_sphere, sizeof(d.parent_sphere)); + base::MemCopy(d.parent_sphere, group_sphere, sizeof(d.parent_sphere)); } // The group's replacement clusters inherit the exact same sphere+error // pair their children compare against: the cut cannot leave gaps. @@ -526,7 +532,7 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { for (const WorkCluster& c : next) next_tris += static_cast(c.indices.size() / 3); RX_INFO("vgeo dag: level {} {} clusters -> level {} {} clusters ({} tris)", level, count, level + 1, next.size(), next_tris); - current = std::move(next); + current = base::move(next); ++level; } @@ -534,7 +540,7 @@ void VirtualGeometryPass::Upload(Device& device, const asset::Mesh& mesh) { meshlet_count_ = static_cast(dag.size()); if (meshlet_count_ == 0) return; - std::vector verts; + base::Vector verts; verts.reserve(lod.vertices.size()); for (const asset::Vertex& v : lod.vertices) { verts.push_back({v.position[0], v.position[1], v.position[2], v.normal[0], v.normal[1], @@ -579,9 +585,9 @@ void VirtualGeometryPass::SetAlbedo(Device& device, ByteSpan rgba_mips, u32 size for (u32 mip = 0; mip < mips; ++mip) { regions.push_back({.buffer_offset = offset, .mip = mip, .extent = {extent, extent}}); offset += u64(extent) * extent * 4; - extent = std::max(1u, extent / 2); + extent = rx::Max(1u, extent / 2); } - cmd.CopyBufferToTexture(staging, albedo_, {regions.data(), regions.size()}); + cmd.CopyBufferToTexture(staging, albedo_, base::Span(regions.data(), regions.size())); cmd.Barrier(Transition(albedo_, ResourceState::kCopyDst, ResourceState::kShaderReadAll)); }); device.DestroyBuffer(staging); @@ -589,7 +595,7 @@ void VirtualGeometryPass::SetAlbedo(Device& device, ByteSpan rgba_mips, u32 size RX_INFO("vgeo albedo: {}x{}, {} mips", size, size, mips); } -void VirtualGeometryPass::SetInstances(std::span transforms) { +void VirtualGeometryPass::SetInstances(base::Span transforms) { pending_instances_.clear(); u32 mirrored = 0; for (const Mat4& m : transforms) { @@ -683,7 +689,7 @@ void VirtualGeometryPass::AddToGraph(Device& device, RenderGraph& graph, const F dst[0] = Mat4::Identity(); instance_count_ = 1; } else { - std::memcpy(dst, pending_instances_.data(), pending_instances_.size() * sizeof(Mat4)); + base::MemCopy(dst, pending_instances_.data(), pending_instances_.size() * sizeof(Mat4)); instance_count_ = static_cast(pending_instances_.size()); } } @@ -692,7 +698,7 @@ void VirtualGeometryPass::AddToGraph(Device& device, RenderGraph& graph, const F Params p{}; p.view_proj = frame.view_proj; p.prev_view_proj = has_prev_ ? prev_view_proj_ : frame.view_proj; - std::memcpy(p.planes, frame.planes, sizeof(p.planes)); + base::MemCopy(p.planes, frame.planes, sizeof(p.planes)); p.camera[0] = frame.eye.x; p.camera[1] = frame.eye.y; p.camera[2] = frame.eye.z; @@ -715,12 +721,12 @@ void VirtualGeometryPass::AddToGraph(Device& device, RenderGraph& graph, const F p.width = frame.width; p.height = frame.height; p.sw_threshold = kSwThresholdPx; - if (const char* env = std::getenv("RX_VGEO_SW_EDGE")) { - p.sw_threshold = static_cast(std::max(std::atoi(env), 0)); + if (const char* env = ::getenv("RX_VGEO_SW_EDGE")) { + p.sw_threshold = static_cast(rx::Max(::atoi(env), 0)); } p.debug = frame.debug; p.max_visible = kMaxVisible; - std::memcpy(params_[slot].mapped, &p, sizeof(p)); + base::MemCopy(params_[slot].mapped, &p, sizeof(p)); } if (frame.debug != 0 && frame.slot % 60 == 30) { @@ -899,7 +905,7 @@ void VirtualGeometryPass::AddToGraph(Device& device, RenderGraph& graph, const F DepthAttachment depth_att{.view = ctx.graph->image(depth).view, .load = LoadOp::kLoad}; ctx.cmd->BeginRendering({.extent = ctx.graph->image(color).extent, - .colors = {&att, 1}, + .colors = base::Span(&att, 1), .depth = &depth_att}); ctx.cmd->BindPipeline(resolve_pipeline_); ctx.cmd->BindTransient( @@ -930,7 +936,7 @@ void VirtualGeometryPass::AddToGraph(Device& device, RenderGraph& graph, const F void VirtualGeometryPass::AddLegacyPass(RenderGraph& graph, const Frame& frame) { LegacyPush push{}; - std::memcpy(push.planes, frame.planes, sizeof(push.planes)); + base::MemCopy(push.planes, frame.planes, sizeof(push.planes)); push.camera[0] = frame.eye.x; push.camera[1] = frame.eye.y; push.camera[2] = frame.eye.z; @@ -949,13 +955,13 @@ void VirtualGeometryPass::AddLegacyPass(RenderGraph& graph, const Frame& frame) const GpuBuffer& counter = legacy_counters_[slot]; if (counter.mapped) static_cast(counter.mapped)[0] = 0; const LegacyCamera camera{view_proj}; - std::memcpy(legacy_camera_[slot].mapped, &camera, sizeof(camera)); + base::MemCopy(legacy_camera_[slot].mapped, &camera, sizeof(camera)); ColorAttachment att{.view = ctx.graph->image(color).view, .load = LoadOp::kLoad}; DepthAttachment depth_att{.view = ctx.graph->image(depth).view, .load = LoadOp::kLoad}; ctx.cmd->BeginRendering({.extent = ctx.graph->image(color).extent, - .colors = {&att, 1}, + .colors = base::Span(&att, 1), .depth = &depth_att}); ctx.cmd->BindPipeline(legacy_pipeline_); ctx.cmd->BindTransient( diff --git a/engine/render/pipeline/virtual_geometry.h b/engine/render/pipeline/virtual_geometry.h index 6bcb44e3..3844ffec 100644 --- a/engine/render/pipeline/virtual_geometry.h +++ b/engine/render/pipeline/virtual_geometry.h @@ -30,9 +30,9 @@ // without 64-bit buffer atomics; inert without mesh shaders. Cluster // pages/streaming are out of scope: the whole DAG stays resident. -#include #include "asset/mesh.h" +#include "base/containers/span.h" #include "core/math.h" #include "render/core/render_graph.h" #include "render/rhi/device.h" @@ -70,7 +70,7 @@ class VirtualGeometryPass { // World transforms drawn next frame; empty keeps a single identity instance. // Mirrored (negative determinant) transforms are dropped with a warning: the // cluster rasterizers cull by winding and cannot flip it per instance. - void SetInstances(std::span transforms); + void SetInstances(base::Span transforms); // Albedo the resolve drapes over the mesh by planar world-xz projection // (uv = world.xz * world_to_uv + 0.5): rgba_mips is a full RGBA8 mip chain, // size x size at mip 0, levels concatenated. The pass owns the upload. diff --git a/engine/render/post/bloom.cc b/engine/render/post/bloom.cc index ef87a304..a2bd87c9 100644 --- a/engine/render/post/bloom.cc +++ b/engine/render/post/bloom.cc @@ -1,8 +1,7 @@ #include "render/post/bloom.h" -#include - #include "core/log.h" +#include "core/scalar.h" #include "render/rhi/device.h" #include "shaders/bloom_down_cs_hlsl.h" #include "shaders/bloom_up_cs_hlsl.h" @@ -62,8 +61,8 @@ ResourceHandle BloomPass::AddToGraph(RenderGraph& graph, ResourceHandle input, u u32 mip_width = width; u32 mip_height = height; for (u32 i = 0; i < kMips; ++i) { - mip_width = std::max(1u, mip_width / 2); - mip_height = std::max(1u, mip_height / 2); + mip_width = rx::Max(1u, mip_width / 2); + mip_height = rx::Max(1u, mip_height / 2); widths[i] = mip_width; heights[i] = mip_height; mips[i] = graph.CreateTexture({.name = "bloom_mip", diff --git a/engine/render/post/exposure.cc b/engine/render/post/exposure.cc index b5b28515..945f4e1b 100644 --- a/engine/render/post/exposure.cc +++ b/engine/render/post/exposure.cc @@ -1,6 +1,6 @@ #include "render/post/exposure.h" -#include +#include #include "core/log.h" #include "render/rhi/device.h" diff --git a/engine/render/post/frame_generation.cc b/engine/render/post/frame_generation.cc index 2bc0b855..e70b3ff8 100644 --- a/engine/render/post/frame_generation.cc +++ b/engine/render/post/frame_generation.cc @@ -1,13 +1,14 @@ #include "render/post/frame_generation.h" -#include -#include -#include -#include +#include +#include +#include #include "core/log.h" // Vulkan escape hatch: the FFX backend speaks raw Vulkan. Also pulls volk // (VK_NO_PROTOTYPES) before the ffx vk headers. +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" #include "render/rhi/vulkan_interop.h" #include @@ -43,10 +44,10 @@ FfxResourceDescription DescribeImage(const GpuImage& image, FfxResourceUsage usa PFN_vkVoidFunction DeviceProcAddr(VkDevice device, const char* name) { PFN_vkVoidFunction fn = vkGetDeviceProcAddr(device, name); if (fn) return fn; - size_t len = std::strlen(name); - if (len > 3 && (std::strcmp(name + len - 3, "KHR") == 0 || - std::strcmp(name + len - 3, "EXT") == 0)) { - std::string core(name, len - 3); + size_t len = ::strlen(name); + if (len > 3 && (::strcmp(name + len - 3, "KHR") == 0 || + ::strcmp(name + len - 3, "EXT") == 0)) { + base::String core(name, len - 3); fn = vkGetDeviceProcAddr(device, core.c_str()); } return fn; @@ -70,7 +71,7 @@ class FfxFrameGenerator final : public FrameGenerator { FfxDevice ffx_device = ffxGetDeviceVK(&device_context); constexpr u32 kContexts = 2; // optical flow + frame interpolation scratch_size_ = ffxGetScratchMemorySizeVK(h.physical_device, kContexts); - scratch_ = std::calloc(1, scratch_size_); + scratch_ = ::calloc(1, scratch_size_); if (!scratch_) return false; FfxErrorCode err = ffxGetInterfaceVK(&interface_, ffx_device, scratch_, scratch_size_, kContexts); @@ -246,10 +247,10 @@ class FfxFrameGenerator final : public FrameGenerator { } barriers[kSharedCount] = Transition(interpolated_, ResourceState::kUndefined, ResourceState::kGeneral); - cmd.TextureBarriers({barriers, kSharedCount + 1}); + cmd.TextureBarriers(base::Span(barriers, kSharedCount + 1)); TextureBarrier hudless_init = Transition(hudless_, ResourceState::kUndefined, ResourceState::kShaderReadCompute); - cmd.TextureBarriers({&hudless_init, 1}); + cmd.TextureBarriers(base::Span(&hudless_init, 1)); }); return true; } @@ -280,7 +281,7 @@ class FfxFrameGenerator final : public FrameGenerator { if (interpolated_) device_.DestroyImage(interpolated_); if (hudless_) device_.DestroyImage(hudless_); if (scratch_) { - std::free(scratch_); + ::free(scratch_); scratch_ = nullptr; } } @@ -302,8 +303,8 @@ class FfxFrameGenerator final : public FrameGenerator { } // namespace -std::unique_ptr CreateFrameGenerator(Device& device, const FrameGenDesc& desc) { - auto generator = std::make_unique(device); +base::UniquePointer CreateFrameGenerator(Device& device, const FrameGenDesc& desc) { + auto generator = base::MakeUnique(device); if (!generator->Initialize(desc)) return nullptr; return generator; } diff --git a/engine/render/post/frame_generation.h b/engine/render/post/frame_generation.h index 620901fc..9612deb3 100644 --- a/engine/render/post/frame_generation.h +++ b/engine/render/post/frame_generation.h @@ -9,8 +9,8 @@ // it before the real frame (Device::SubmitFrameGen); under FIFO the two // presents land a vblank apart, which is the whole pacing story. -#include +#include "base/memory/unique_pointer.h" #include "core/math.h" #include "render/rhi/device.h" @@ -54,7 +54,7 @@ class FrameGenerator { }; // Returns null when FSR3 is not compiled in or the device is not vulkan. -std::unique_ptr CreateFrameGenerator(Device& device, const FrameGenDesc& desc); +base::UniquePointer CreateFrameGenerator(Device& device, const FrameGenDesc& desc); } // namespace rx::render diff --git a/engine/render/post/ngx_context.cc b/engine/render/post/ngx_context.cc index 609207b0..3bc55c70 100644 --- a/engine/render/post/ngx_context.cc +++ b/engine/render/post/ngx_context.cc @@ -1,6 +1,5 @@ #include "render/post/ngx_context.h" -#include #include @@ -8,6 +7,7 @@ #include "render/rhi/device.h" // Vulkan escape hatch: NGX speaks raw Vulkan. Also pulls volk // (VK_NO_PROTOTYPES) before the ngx vk header. +#include "base/strings/xstring.h" #include "render/rhi/vulkan_interop.h" #include @@ -25,13 +25,13 @@ constexpr const char* kProjectId = "8d4a1f60-3c2e-4b8a-9f17-c4035c19df04"; base::Option DlssLibDir{"dlss.lib.dir", nullptr, "RX_DLSS_LIB_DIR"}; void NgxLog(const char* message, NVSDK_NGX_Logging_Level, NVSDK_NGX_Feature) { - std::string line(message ? message : ""); + base::String line(message ? message : ""); while (!line.empty() && (line.back() == '\n' || line.back() == '\r')) line.pop_back(); if (!line.empty()) RX_WARN("ngx: {}", line); } -std::wstring ToWide(const char* s) { - std::wstring w; +base::StringW ToWide(const char* s) { + base::StringW w; for (; s && *s; ++s) w.push_back(static_cast(*s)); return w; } @@ -41,7 +41,7 @@ struct Context { bool initialized = false; VkDevice vk_device = VK_NULL_HANDLE; NVSDK_NGX_Parameter* capability = nullptr; - std::wstring snippet_dir; + base::StringW snippet_dir; const wchar_t* snippet_path = nullptr; }; Context g_context; diff --git a/engine/render/post/overdraw.cc b/engine/render/post/overdraw.cc index 7618e39e..581e678f 100644 --- a/engine/render/post/overdraw.cc +++ b/engine/render/post/overdraw.cc @@ -1,6 +1,7 @@ #include "render/post/overdraw.h" #include "asset/mesh.h" +#include "base/functional/function.h" #include "core/log.h" #include "render/gi/shadow.h" #include "shaders/overdraw_ps_hlsl.h" @@ -67,12 +68,12 @@ bool OverdrawPass::Initialize(Device& device, Format color_format) { void OverdrawPass::Render(CommandList& cmd, TextureView color_view, Extent2D extent, const Mat4& view_proj, - const std::function& draw) { + const base::Function& draw) { ColorAttachment color{.view = color_view, .load = LoadOp::kClear, // start from black, then accumulate .store = StoreOp::kStore, .clear = {0.0f, 0.0f, 0.0f, 1.0f}}; - cmd.BeginRendering({.extent = extent, .colors = {&color, 1}}); + cmd.BeginRendering({.extent = extent, .colors = base::Span(&color, 1)}); cmd.BindPipeline(pipeline_); cmd.PushConstants(&view_proj, sizeof(Mat4), ShadowPass::kLightMatrixOffset); draw(cmd); diff --git a/engine/render/post/overdraw.h b/engine/render/post/overdraw.h index 09f5104d..64e2ac9d 100644 --- a/engine/render/post/overdraw.h +++ b/engine/render/post/overdraw.h @@ -1,8 +1,8 @@ #ifndef RX_RENDER_OVERDRAW_H_ #define RX_RENDER_OVERDRAW_H_ -#include +#include "base/functional/function.h" #include "core/math.h" #include "render/rhi/device.h" @@ -22,7 +22,7 @@ class OverdrawPass { // 128-byte block (push constants always start at offset 0 in the RHI) and // issues the draws. view_proj is also pushed up front. void Render(CommandList& cmd, TextureView color_view, Extent2D extent, const Mat4& view_proj, - const std::function& draw); + const base::Function& draw); void BindInstanced(CommandList& cmd, const Mat4& view_proj); private: diff --git a/engine/render/post/post.cc b/engine/render/post/post.cc index 19a5f61b..df0f5d9d 100644 --- a/engine/render/post/post.cc +++ b/engine/render/post/post.cc @@ -1,19 +1,22 @@ #include "render/post/post.h" -#include -#include -#include +#include #include +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" #include "core/log.h" +#include "core/text_reader.h" #include "shaders/fullscreen_vs_slang.h" #include "shaders/tonemap_ps_hlsl.h" namespace rx::render { -std::unique_ptr PostPass::Create(Device& device, Format output_format) { - auto pass = std::unique_ptr(new PostPass(device)); +base::UniquePointer PostPass::Create(Device& device, Format output_format) { + auto pass = base::UniquePointer(new PostPass(device)); pass->sampler_ = device.GetSampler({.address_u = AddressMode::kClampToEdge, .address_v = AddressMode::kClampToEdge, @@ -92,18 +95,19 @@ struct CubeLut { // Parses LUT_3D_SIZE + the triple list. Ignores comments, TITLE and DOMAIN_*; // rejects 1D luts and malformed files. Returns false on any error. -bool ParseCube(std::istream& in, CubeLut* out) { - std::string line; +bool ParseCube(base::StringRef text, CubeLut* out) { + LineReader lines(text); + base::StringRef line; base::Vector data; u32 size = 0; - while (std::getline(in, line)) { + while (lines.Next(&line)) { size_t s = line.find_first_not_of(" \t\r\n"); - if (s == std::string::npos || line[s] == '#') continue; - std::istringstream ls(line.substr(s)); - std::string tok; - ls >> tok; + if (s == base::StringRef::npos || line[s] == '#') continue; + TokenReader ls(line.substr(s)); + base::String tok; + ls.Next(&tok); if (tok == "LUT_3D_SIZE") { - ls >> size; + ls.Next(&size); if (size < 2 || size > 128) return false; } else if (tok == "LUT_1D_SIZE") { return false; // 1D luts are not supported by the strip path @@ -112,8 +116,8 @@ bool ParseCube(std::istream& in, CubeLut* out) { continue; } else { f32 r, g, b; - std::istringstream vs(line.substr(s)); - if (!(vs >> r >> g >> b)) continue; // tolerate stray lines + TokenReader vs(line.substr(s)); + if (!(vs.Next(&r) && vs.Next(&g) && vs.Next(&b))) continue; // tolerate stray lines data.push_back(r); data.push_back(g); data.push_back(b); @@ -121,7 +125,7 @@ bool ParseCube(std::istream& in, CubeLut* out) { } if (size == 0 || data.size() != static_cast(size) * size * size * 3) return false; out->size = size; - out->rgb = std::move(data); + out->rgb = base::move(data); return true; } @@ -185,7 +189,7 @@ void PostPass::UploadLutPixels(base::Vector& pixels) { const u32 size = kLutSize; const u32 width = size * size; GpuBuffer staging = - device_.CreateBufferWithData({pixels.data(), pixels.size()}, kBufferUsageTransferSrc); + device_.CreateBufferWithData(ByteSpan(pixels.data(), pixels.size()), kBufferUsageTransferSrc); bool first = !lut_ready_; device_.ImmediateSubmit([&](CommandList& cmd) { cmd.Barrier(Transition( @@ -193,7 +197,7 @@ void PostPass::UploadLutPixels(base::Vector& pixels) { ResourceState::kCopyDst)); BufferTextureCopy copy{.extent = {width, size}}; - cmd.CopyBufferToTexture(staging, lut_, {©, 1}); + cmd.CopyBufferToTexture(staging, lut_, base::Span(©, 1)); cmd.Barrier(Transition(lut_, ResourceState::kCopyDst, ResourceState::kShaderReadFragment)); }); @@ -201,14 +205,14 @@ void PostPass::UploadLutPixels(base::Vector& pixels) { lut_ready_ = true; } -bool PostPass::LoadCubeLut(const std::string& path) { - std::ifstream file(path); - if (!file) { +bool PostPass::LoadCubeLut(const base::String& path) { + base::String text; + if (!fs::ReadTextFile(path, &text)) { RX_WARN("cube lut: cannot open {}", path); return false; } CubeLut cube; - if (!ParseCube(file, &cube)) { + if (!ParseCube(text, &cube)) { RX_WARN("cube lut: failed to parse {} (need a valid 3D .cube)", path); return false; } @@ -255,7 +259,7 @@ void PostPass::Record(PassContext& ctx, TextureView input, TextureView bloom, Te Extent2D output_extent, const Params& params) { ColorAttachment color{.view = output, .load = LoadOp::kDontCare, // fully overwritten .store = StoreOp::kStore}; - ctx.cmd->BeginRendering({.extent = output_extent, .colors = {&color, 1}}); + ctx.cmd->BeginRendering({.extent = output_extent, .colors = base::Span(&color, 1)}); ctx.cmd->BindPipeline(pipeline_); ctx.cmd->BindTransient(0, {Bind::Combined(0, input, sampler_), Bind::Combined(1, bloom, sampler_), diff --git a/engine/render/post/post.h b/engine/render/post/post.h index 9da007a8..c76484c6 100644 --- a/engine/render/post/post.h +++ b/engine/render/post/post.h @@ -1,11 +1,11 @@ #ifndef RX_RENDER_POST_H_ #define RX_RENDER_POST_H_ -#include -#include #include +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" #include "render/core/render_graph.h" #include "render/rhi/device.h" #include "render/core/settings.h" @@ -18,7 +18,7 @@ namespace rx::render { // sampler until a real upscaler owns it. class PostPass { public: - static std::unique_ptr Create(Device& device, Format output_format); + static base::UniquePointer Create(Device& device, Format output_format); ~PostPass(); PostPass(const PostPass&) = delete; @@ -49,7 +49,7 @@ class PostPass { // Loads a Resolve/Adobe .cube 3D lut from disk, resampled into the strip lut. // Leaves the grade marked kCustom so SetGrade does not bake over it. Returns // false (and keeps the current lut) on a parse or read error. - bool LoadCubeLut(const std::string& path); + bool LoadCubeLut(const base::String& path); // bloom/flare may be the input view when bloom is off (still bound, not // read; flare_intensity is zeroed alongside). `flare` is the tight 1/4-res diff --git a/engine/render/post/reference_compare.cc b/engine/render/post/reference_compare.cc index 80db07db..b3280fff 100644 --- a/engine/render/post/reference_compare.cc +++ b/engine/render/post/reference_compare.cc @@ -1,10 +1,12 @@ #include "render/post/reference_compare.h" -#include -#include +#include +#include #include +#include "base/memory/mem_ops.h" +#include "base/strings/xstring.h" #include "core/log.h" #include "shaders/reference_compare_cs_hlsl.h" @@ -60,7 +62,7 @@ bool ReferenceCompare::Initialize(Device& device) { pipeline_ = {}; return false; } - std::memset(stats_buffer_.mapped, 0, stats_bytes); + base::MemSet(stats_buffer_.mapped, 0, stats_bytes); // "No mask loaded" must mean "every region is everywhere", not "nothing is // anything" - otherwise turning stats on before authoring a mask silently @@ -70,11 +72,11 @@ bool ReferenceCompare::Initialize(Device& device) { if (white_mask_) { const u8 px[4] = {255, 255, 255, 255}; GpuBuffer staging = device.CreateBuffer(4, kBufferUsageTransferSrc, true); - std::memcpy(staging.mapped, px, 4); + base::MemCopy(staging.mapped, px, 4); device.ImmediateSubmit([&](CommandList& cmd) { cmd.Barrier(Transition(white_mask_, ResourceState::kUndefined, ResourceState::kCopyDst)); BufferTextureCopy region{}; - cmd.CopyBufferToTexture(staging, white_mask_, {®ion, 1}); + cmd.CopyBufferToTexture(staging, white_mask_, base::Span(®ion, 1)); cmd.Barrier(Transition(white_mask_, ResourceState::kCopyDst, ResourceState::kShaderReadFragment)); }); @@ -113,11 +115,11 @@ GpuImage UploadFloatImage(Device& device, const f32* rgba, u32 width, u32 height device.DestroyImage(image); return {}; } - std::memcpy(staging.mapped, rgba, bytes); + base::MemCopy(staging.mapped, rgba, bytes); device.ImmediateSubmit([&](CommandList& cmd) { cmd.Barrier(Transition(image, ResourceState::kUndefined, ResourceState::kCopyDst)); BufferTextureCopy region{}; - cmd.CopyBufferToTexture(staging, image, {®ion, 1}); + cmd.CopyBufferToTexture(staging, image, base::Span(®ion, 1)); cmd.Barrier(Transition(image, ResourceState::kCopyDst, ResourceState::kShaderReadFragment)); }); device.DestroyBuffer(staging); @@ -126,7 +128,7 @@ GpuImage UploadFloatImage(Device& device, const f32* rgba, u32 width, u32 height } // namespace -bool ReferenceCompare::LoadReference(Device& device, const std::string& path) { +bool ReferenceCompare::LoadReference(Device& device, const base::String& path) { int w = 0, h = 0, comp = 0; // stbi_loadf returns scene-linear for .hdr and de-gammas 8-bit sources on the // way in, which is the contract this pass needs: everything downstream of @@ -145,7 +147,7 @@ bool ReferenceCompare::LoadReference(Device& device, const std::string& path) { return true; } -bool ReferenceCompare::LoadRegionMask(Device& device, const std::string& path) { +bool ReferenceCompare::LoadRegionMask(Device& device, const base::String& path) { int w = 0, h = 0, comp = 0; f32* pixels = stbi_loadf(path.c_str(), &w, &h, &comp, 4); if (!pixels) { diff --git a/engine/render/post/reference_compare.h b/engine/render/post/reference_compare.h index f90bbfd0..42530c8b 100644 --- a/engine/render/post/reference_compare.h +++ b/engine/render/post/reference_compare.h @@ -1,8 +1,8 @@ #ifndef RX_RENDER_REFERENCE_COMPARE_H_ #define RX_RENDER_REFERENCE_COMPARE_H_ -#include +#include "base/strings/xstring.h" #include "core/types.h" #include "render/core/render_graph.h" #include "render/rhi/device.h" @@ -71,9 +71,9 @@ class ReferenceCompare { // Loads a scene-linear reference. .hdr / .exr-style float sources load // as-is; 8-bit sources are de-gamma'd to linear on the way in. Returns false // and keeps the previous reference on a read error. - bool LoadReference(Device& device, const std::string& path); + bool LoadReference(Device& device, const base::String& path); // Loads the four-channel region mask (r skin, g eyes, b lips, a teeth). - bool LoadRegionMask(Device& device, const std::string& path); + bool LoadRegionMask(Device& device, const base::String& path); bool has_reference() const { return static_cast(reference_); } Settings& settings() { return settings_; } diff --git a/engine/render/post/ui_blur.cc b/engine/render/post/ui_blur.cc index 01b23a02..82447725 100644 --- a/engine/render/post/ui_blur.cc +++ b/engine/render/post/ui_blur.cc @@ -1,8 +1,8 @@ #include "render/post/ui_blur.h" -#include - +#include "base/memory/unique_pointer.h" #include "core/log.h" +#include "core/scalar.h" #include "shaders/fullscreen_vs_slang.h" #include "shaders/ui_blur_ps_hlsl.h" @@ -15,8 +15,8 @@ struct BlurPush { }; } // namespace -std::unique_ptr UiBlurPass::Create(Device& device) { - auto pass = std::unique_ptr(new UiBlurPass(device)); +base::UniquePointer UiBlurPass::Create(Device& device) { + auto pass = base::UniquePointer(new UiBlurPass(device)); pass->sampler_ = device.GetSampler({.address_u = AddressMode::kClampToEdge, .address_v = AddressMode::kClampToEdge, @@ -45,7 +45,7 @@ UiBlurPass::~UiBlurPass() { device_.DestroyPipeline(pipeline_); } void UiBlurPass::Record(PassContext& ctx, TextureView input, TextureView output, Extent2D extent, float dx, float dy) { ColorAttachment color{.view = output, .load = LoadOp::kDontCare, .store = StoreOp::kStore}; - ctx.cmd->BeginRendering({.extent = extent, .colors = {&color, 1}}); + ctx.cmd->BeginRendering({.extent = extent, .colors = base::Span(&color, 1)}); ctx.cmd->BindPipeline(pipeline_); ctx.cmd->BindTransient(0, {Bind::Combined(0, input, sampler_)}); BlurPush push{{dx, dy}}; @@ -56,8 +56,8 @@ void UiBlurPass::Record(PassContext& ctx, TextureView input, TextureView output, ResourceHandle UiBlurPass::AddToGraph(RenderGraph& graph, ResourceHandle src, u32 width, u32 height) { - const u32 dw = std::max(1u, width / 4); - const u32 dh = std::max(1u, height / 4); + const u32 dw = rx::Max(1u, width / 4); + const u32 dh = rx::Max(1u, height / 4); ResourceHandle h_blur = graph.CreateTexture({.name = "ui_frost_h", .format = kFrostFormat, .width = dw, .height = dh}); ResourceHandle frost = diff --git a/engine/render/post/ui_blur.h b/engine/render/post/ui_blur.h index a593f4cc..5474faf4 100644 --- a/engine/render/post/ui_blur.h +++ b/engine/render/post/ui_blur.h @@ -1,8 +1,8 @@ #ifndef RX_RENDER_UI_BLUR_H_ #define RX_RENDER_UI_BLUR_H_ -#include +#include "base/memory/unique_pointer.h" #include "render/core/render_graph.h" #include "render/rhi/device.h" @@ -14,7 +14,7 @@ namespace rx::render { // by the UI backend and sampled in screen space by frosted quads. class UiBlurPass { public: - static std::unique_ptr Create(Device& device); + static base::UniquePointer Create(Device& device); ~UiBlurPass(); UiBlurPass(const UiBlurPass&) = delete; diff --git a/engine/render/post/upscaler.cc b/engine/render/post/upscaler.cc index b029c6a0..6e977a41 100644 --- a/engine/render/post/upscaler.cc +++ b/engine/render/post/upscaler.cc @@ -1,20 +1,21 @@ #include "render/post/upscaler.h" +#include "base/memory/unique_pointer.h" #include "core/log.h" #include "render/rhi/device.h" namespace rx::render { #if defined(RX_HAS_FSR3) -std::unique_ptr CreateFsr3Upscaler(const UpscalerDesc& desc, Device& device); +base::UniquePointer CreateFsr3Upscaler(const UpscalerDesc& desc, Device& device); #endif #if defined(RX_HAS_DLSS) -std::unique_ptr CreateDlssUpscaler(const UpscalerDesc& desc, Device& device); +base::UniquePointer CreateDlssUpscaler(const UpscalerDesc& desc, Device& device); #endif // SDK backed implementations (FSR3, DLSS, XeSS) plug in here behind build // options. Anything not compiled in falls back to TAA. -std::unique_ptr CreateUpscaler(const UpscalerDesc& desc, Device& device) { +base::UniquePointer CreateUpscaler(const UpscalerDesc& desc, Device& device) { switch (desc.kind) { case UpscalerKind::kFsr3: #if defined(RX_HAS_FSR3) diff --git a/engine/render/post/upscaler.h b/engine/render/post/upscaler.h index 8d5e1347..91c48b7b 100644 --- a/engine/render/post/upscaler.h +++ b/engine/render/post/upscaler.h @@ -1,8 +1,8 @@ #ifndef RX_RENDER_UPSCALER_H_ #define RX_RENDER_UPSCALER_H_ -#include +#include "base/memory/unique_pointer.h" #include "core/types.h" #include "render/core/render_graph.h" @@ -61,7 +61,7 @@ class Upscaler { // Returns null if the SDK for the requested kind is not compiled in or the // device does not support it. Caller falls back to TAA. -std::unique_ptr CreateUpscaler(const UpscalerDesc& desc, class Device& device); +base::UniquePointer CreateUpscaler(const UpscalerDesc& desc, class Device& device); // The spelling the --upscaler flag and the settings ini use, for logging which // backend a fallback actually landed on. diff --git a/engine/render/post/upscaler_dlss.cc b/engine/render/post/upscaler_dlss.cc index 7e7d963e..b27d5a19 100644 --- a/engine/render/post/upscaler_dlss.cc +++ b/engine/render/post/upscaler_dlss.cc @@ -1,7 +1,6 @@ #include "render/post/upscaler.h" -#include -#include +#include #include @@ -10,6 +9,7 @@ #include "render/rhi/device.h" // Vulkan escape hatch: NGX speaks raw Vulkan. Also pulls volk // (VK_NO_PROTOTYPES) before the ngx vk header. +#include "base/memory/unique_pointer.h" #include "render/rhi/vulkan_interop.h" #include @@ -190,8 +190,8 @@ class DlssUpscaler final : public Upscaler { } // namespace -std::unique_ptr CreateDlssUpscaler(const UpscalerDesc& desc, Device& device) { - auto upscaler = std::make_unique(device); +base::UniquePointer CreateDlssUpscaler(const UpscalerDesc& desc, Device& device) { + auto upscaler = base::MakeUnique(device); if (!upscaler->Initialize(desc)) return nullptr; return upscaler; } diff --git a/engine/render/post/upscaler_fsr3.cc b/engine/render/post/upscaler_fsr3.cc index af06fddd..77e2196e 100644 --- a/engine/render/post/upscaler_fsr3.cc +++ b/engine/render/post/upscaler_fsr3.cc @@ -1,16 +1,20 @@ #include "render/post/upscaler.h" -#include -#include -#include -#include +#include +#include +#include #include "core/log.h" #include "render/rhi/device.h" // Vulkan escape hatch: the FFX backend speaks raw Vulkan. Also pulls volk // (VK_NO_PROTOTYPES) before the ffx vk header. +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" #include "render/rhi/vulkan_interop.h" +// ffx_util.h calls std::popcount without including ; FidelityFX is +// vendored C++ we do not edit, so the include it forgot is supplied here. +#include #include #include @@ -50,17 +54,17 @@ FfxResourceDescription DescribeImage(const GpuImage& image, FfxResourceUsage usa PFN_vkVoidFunction DeviceProcAddr(VkDevice device, const char* name) { PFN_vkVoidFunction fn = vkGetDeviceProcAddr(device, name); if (fn) return fn; - size_t len = std::strlen(name); - if (len > 3 && (std::strcmp(name + len - 3, "KHR") == 0 || - std::strcmp(name + len - 3, "EXT") == 0)) { - std::string core(name, len - 3); + size_t len = ::strlen(name); + if (len > 3 && (::strcmp(name + len - 3, "KHR") == 0 || + ::strcmp(name + len - 3, "EXT") == 0)) { + base::String core(name, len - 3); fn = vkGetDeviceProcAddr(device, core.c_str()); } return fn; } void MessageCallback(FfxMsgType type, const wchar_t* message) { - std::string narrow; + base::String narrow; for (const wchar_t* c = message; c && *c; ++c) { narrow.push_back(*c < 128 ? static_cast(*c) : '?'); } @@ -107,7 +111,7 @@ class Fsr3Upscaler final : public Upscaler { FfxDevice ffx_device = ffxGetDeviceVK(&device_context); scratch_size_ = ffxGetScratchMemorySizeVK(h.physical_device, FFX_FSR3UPSCALER_CONTEXT_COUNT); - scratch_ = std::calloc(1, scratch_size_); + scratch_ = ::calloc(1, scratch_size_); if (!scratch_) return false; FfxErrorCode err = ffxGetInterfaceVK(&interface_, ffx_device, scratch_, scratch_size_, FFX_FSR3UPSCALER_CONTEXT_COUNT); @@ -213,7 +217,7 @@ class Fsr3Upscaler final : public Upscaler { barriers[i] = Transition(shared_[i], ResourceState::kUndefined, ResourceState::kGeneral); } - cmd.TextureBarriers({barriers, kSharedCount}); + cmd.TextureBarriers(base::Span(barriers, kSharedCount)); }); return true; } @@ -287,7 +291,7 @@ class Fsr3Upscaler final : public Upscaler { if (image) device_.DestroyImage(image); } if (scratch_) { - std::free(scratch_); + ::free(scratch_); scratch_ = nullptr; } } @@ -309,8 +313,8 @@ class Fsr3Upscaler final : public Upscaler { } // namespace -std::unique_ptr CreateFsr3Upscaler(const UpscalerDesc& desc, Device& device) { - auto upscaler = std::make_unique(device); +base::UniquePointer CreateFsr3Upscaler(const UpscalerDesc& desc, Device& device) { + auto upscaler = base::MakeUnique(device); if (!upscaler->Initialize(desc)) return nullptr; return upscaler; } diff --git a/engine/render/rhi/command_list.h b/engine/render/rhi/command_list.h index 109a8eda..46fd5fbc 100644 --- a/engine/render/rhi/command_list.h +++ b/engine/render/rhi/command_list.h @@ -2,8 +2,8 @@ #define RX_RENDER_RHI_COMMAND_LIST_H_ #include -#include +#include "base/containers/span.h" #include "core/types.h" #include "render/rhi/bindings.h" #include "render/rhi/resources.h" @@ -43,7 +43,7 @@ struct DepthAttachment { struct RenderingInfo { Extent2D extent; - std::span colors; + base::Span colors; const DepthAttachment* depth = nullptr; // null = no depth attachment // Optional VRS rate image (kShadingRate state, R8Uint, one texel per // caps().shading_rate_texel block). Null = full rate. @@ -74,7 +74,7 @@ struct AccelTriangles { }; struct BlasBuildDesc { - std::span geometries; + base::Span geometries; bool fast_trace = true; // PREFER_FAST_TRACE; false = PREFER_FAST_BUILD // Builds with ALLOW_COMPACTION so a later CommandList::CopyAccelStruct(compact) // can shrink the structure into a tight buffer. The flag must be set both here @@ -127,9 +127,9 @@ class CommandList { // Transient bindings against the bound pipeline's layout: allocated from a // per-frame pool, written and bound in one call. This replaces the // allocate/update/bind descriptor dance. - virtual void BindTransient(u32 set_index, std::span items) = 0; + virtual void BindTransient(u32 set_index, base::Span items) = 0; void BindTransient(u32 set_index, std::initializer_list items) { - BindTransient(set_index, std::span(items.begin(), items.size())); + BindTransient(set_index, base::Span(items.begin(), items.size())); } // offset allows partial updates of a larger push block (e.g. a per-cascade // matrix at 0 with per-draw model matrices behind it). Maps to @@ -193,9 +193,9 @@ class CommandList { u32 /*max_draws*/, u32 /*stride*/) {} // synchronization - virtual void TextureBarriers(std::span barriers) = 0; + virtual void TextureBarriers(base::Span barriers) = 0; void Barrier(const TextureBarrier& barrier) { - TextureBarriers(std::span(&barrier, 1)); + TextureBarriers(base::Span(&barrier, 1)); } // Global execution+memory barrier for buffer hazards (compute -> indirect // args, transfer -> shader read, AS build -> ray query, ...). @@ -203,7 +203,7 @@ class CommandList { // transfer virtual void CopyBufferToTexture(const GpuBuffer& src, const GpuImage& dst, - std::span regions) = 0; + base::Span regions) = 0; virtual void CopyTextureToBuffer(const GpuImage& src, const GpuBuffer& dst, const BufferTextureCopy& region) = 0; // Full-extent mip-0 image copy (matching size; formats may differ in diff --git a/engine/render/rhi/device.h b/engine/render/rhi/device.h index 4861099c..f186370b 100644 --- a/engine/render/rhi/device.h +++ b/engine/render/rhi/device.h @@ -1,12 +1,14 @@ #ifndef RX_RENDER_RHI_DEVICE_H_ #define RX_RENDER_RHI_DEVICE_H_ -#include -#include -#include +#include #include +#include "base/containers/span.h" +#include "base/functional/function.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" #include "core/types.h" #include "core/window.h" #include "render/rhi/bindings.h" @@ -40,7 +42,7 @@ struct DeviceDesc { // baseline rx enables (BDA, descriptor indexing, timeline, dynamic rendering, // sync2, and every core / Vulkan 1.1-1.3 feature bit the driver reports, plus // mesh-shader and ray-query when present) does not need listing here. - base::Vector extra_device_extensions; + base::Vector extra_device_extensions; }; // What the picked GPU actually supports. Optional features are queried, @@ -50,7 +52,7 @@ struct DeviceDesc { // semaphores; D3D12 feature level 12_1 with SM 6.6 and enhanced barriers. struct DeviceCaps { Backend backend = Backend::kNull; - std::string adapter_name; + base::String adapter_name; u32 api_version = 0; bool raytracing = false; // acceleration structures + ray tracing pipeline bool ray_query = false; @@ -82,7 +84,7 @@ struct DeviceCaps { // App-requested device extensions (DeviceDesc::extra_device_extensions) that // the adapter actually granted. An app checks this to know whether its custom // GPU pass can use a given extension. - base::Vector extra_extensions; + base::Vector extra_extensions; }; enum class AccelStructType : u8 { kBlas, kTlas }; @@ -112,7 +114,7 @@ class Device { public: static constexpr u32 kMaxFramesInFlight = 2; - static std::unique_ptr Create(const DeviceDesc& desc, Window& window); + static base::UniquePointer Create(const DeviceDesc& desc, Window& window); // Surfaceless device: a real GPU device with the same feature enablement and // caps as the windowed path, but with no presentation surface or swapchain. @@ -123,7 +125,7 @@ class Device { // to the null backend when no Vulkan driver is present (is_stub() true), so // callers get a valid, safe device on any machine. D3D12 offscreen is not // wired yet (it also falls back to null). - static std::unique_ptr CreateOffscreen(const DeviceDesc& desc); + static base::UniquePointer CreateOffscreen(const DeviceDesc& desc); virtual ~Device() = default; Device(const Device&) = delete; @@ -145,7 +147,7 @@ class Device { // `hdr` requests an HDR-capable surface format (HDR10 PQ preferred, scRGB // fallback); silently falls back to SDR when the surface has neither. - virtual std::unique_ptr CreateSwapchain(u32 width, u32 height, bool vsync, + virtual base::UniquePointer CreateSwapchain(u32 width, u32 height, bool vsync, bool hdr = false) = 0; // Live gpu memory usage, summed over the device-local heaps, for the debug @@ -229,9 +231,9 @@ class Device { virtual BindingSetHandle CreateBindingSet(BindingLayoutHandle layout, u32 variable_count = 0) = 0; virtual void DestroyBindingSet(BindingSetHandle set) = 0; - virtual void UpdateBindingSet(BindingSetHandle set, std::span items) = 0; + virtual void UpdateBindingSet(BindingSetHandle set, base::Span items) = 0; void UpdateBindingSet(BindingSetHandle set, std::initializer_list items) { - UpdateBindingSet(set, std::span(items.begin(), items.size())); + UpdateBindingSet(set, base::Span(items.begin(), items.size())); } // acceleration structures (caps().ray_query gated) @@ -282,7 +284,7 @@ class Device { // recording & submission // Records into a transient command list and blocks until execution // finished. For uploads and one-off transitions, not the frame path. - virtual void ImmediateSubmit(const std::function& record) = 0; + virtual void ImmediateSubmit(const base::Function& record) = 0; // coalesced uploads // While the batch is open, CreateBufferWithData records its staging copy into @@ -315,7 +317,7 @@ class Device { // transfer work (copies/blits/barriers) and must not allocate binding sets: // the shared immediate descriptor pool is only reset by ImmediateSubmit, so // sets allocated inside a batch would accumulate until it exhausts. - virtual void RecordUpload(const std::function& record) { + virtual void RecordUpload(const base::Function& record) { ImmediateSubmit(record); } // Hands a staging buffer to the open batch to free once its copies have run diff --git a/engine/render/rhi/rhi.cc b/engine/render/rhi/rhi.cc index 11fd5c65..c6f25c56 100644 --- a/engine/render/rhi/rhi.cc +++ b/engine/render/rhi/rhi.cc @@ -1,3 +1,4 @@ +#include "base/memory/unique_pointer.h" #include "core/log.h" #include "render/rhi/device.h" #include "render/rhi/swapchain.h" @@ -8,15 +9,15 @@ namespace rx::render { // build-time property (RX_RHI_*); returning null means "not available // on this machine", and Create falls through to the next candidate. namespace vk { -std::unique_ptr CreateVulkanDevice(const DeviceDesc& desc, Window& window); -std::unique_ptr CreateVulkanDeviceOffscreen(const DeviceDesc& desc); +base::UniquePointer CreateVulkanDevice(const DeviceDesc& desc, Window& window); +base::UniquePointer CreateVulkanDeviceOffscreen(const DeviceDesc& desc); } namespace d3d12 { -std::unique_ptr CreateD3D12Device(const DeviceDesc& desc, Window& window); -std::unique_ptr CreateD3D12DeviceOffscreen(const DeviceDesc& desc); +base::UniquePointer CreateD3D12Device(const DeviceDesc& desc, Window& window); +base::UniquePointer CreateD3D12DeviceOffscreen(const DeviceDesc& desc); } namespace null { -std::unique_ptr CreateNullDevice(); +base::UniquePointer CreateNullDevice(); } const char* BackendName(Backend backend) { @@ -29,8 +30,8 @@ const char* BackendName(Backend backend) { return "?"; } -std::unique_ptr Device::Create(const DeviceDesc& desc, Window& window) { - std::unique_ptr device; +base::UniquePointer Device::Create(const DeviceDesc& desc, Window& window) { + base::UniquePointer device; switch (desc.backend) { case Backend::kVulkan: #if defined(RX_RHI_VULKAN) @@ -65,8 +66,8 @@ std::unique_ptr Device::Create(const DeviceDesc& desc, Window& window) { return device; } -std::unique_ptr Device::CreateOffscreen(const DeviceDesc& desc) { - std::unique_ptr device; +base::UniquePointer Device::CreateOffscreen(const DeviceDesc& desc) { + base::UniquePointer device; // Offscreen devices have no surface/swapchain; frames complete through the // swapchainless SubmitFrame. kAuto keeps the Vulkan-first order of Create. if (desc.backend == Backend::kVulkan || desc.backend == Backend::kAuto) { diff --git a/engine/render/screenspace/reflection_trace.cc b/engine/render/screenspace/reflection_trace.cc index 5f0d5d81..d007a690 100644 --- a/engine/render/screenspace/reflection_trace.cc +++ b/engine/render/screenspace/reflection_trace.cc @@ -1,7 +1,8 @@ #include "render/screenspace/reflection_trace.h" -#include +#include +#include "base/memory/mem_ops.h" #include "core/log.h" #include "render/gi/raytracing.h" #include "render/rhi/device.h" @@ -178,7 +179,7 @@ ResourceHandle ReflectionTrace::AddToGraph( camera.camera_pos[0] = frame.camera_pos.x; camera.camera_pos[1] = frame.camera_pos.y; camera.camera_pos[2] = frame.camera_pos.z; - std::memcpy(camera_[slot].mapped, &camera, sizeof(camera)); + base::MemCopy(camera_[slot].mapped, &camera, sizeof(camera)); base::Vector items; items.push_back(Bind::Storage(0, ctx.graph->image(raw))); @@ -233,7 +234,7 @@ ResourceHandle ReflectionTrace::AddToGraph( (fog_on ? kFlagFog : 0u) | (sh_valid ? kFlagShSkip : 0u) | (rcgi.active ? kFlagRcgi : 0u); ctx.cmd->BindPipeline(pipeline_); - ctx.cmd->BindTransient(0, {items.data(), items.size()}); + ctx.cmd->BindTransient(0, base::Span(items.data(), items.size())); ctx.cmd->BindSet(1, bindless_set); ctx.cmd->Push(p); ctx.cmd->Dispatch2D(trace); diff --git a/engine/render/screenspace/ssr.cc b/engine/render/screenspace/ssr.cc index 8ae34f80..2b22a3c7 100644 --- a/engine/render/screenspace/ssr.cc +++ b/engine/render/screenspace/ssr.cc @@ -1,7 +1,8 @@ #include "render/screenspace/ssr.h" -#include +#include +#include "base/memory/mem_ops.h" #include "core/log.h" #include "render/rhi/device.h" #include "shaders/ssr_cs_hlsl.h" @@ -85,7 +86,7 @@ ResourceHandle SsrPass::AddToGraph(RenderGraph& graph, ResourceHandle scene_colo [this, scene_color, depth, normals, out, view_proj, inv_view_proj, camera_pos, frame_index, slot = frame_index % 2](PassContext& ctx) { const SsrCamera camera{view_proj, inv_view_proj}; - std::memcpy(camera_[slot].mapped, &camera, sizeof(camera)); + base::MemCopy(camera_[slot].mapped, &camera, sizeof(camera)); SsrPush push{}; push.camera_pos[0] = camera_pos.x; diff --git a/engine/render/texturing/decal_bake.cc b/engine/render/texturing/decal_bake.cc index 1223decd..2654ea49 100644 --- a/engine/render/texturing/decal_bake.cc +++ b/engine/render/texturing/decal_bake.cc @@ -1,12 +1,15 @@ #include "render/texturing/decal_bake.h" -#include -#include +#include #include #include "asset/mesh.h" +#include "base/containers/span.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" #include "core/log.h" +#include "core/scalar.h" #include "shaders/decal_bake_ps_hlsl.h" #include "shaders/decal_bake_skin_vs_hlsl.h" #include "shaders/decal_bake_vs_hlsl.h" @@ -35,7 +38,7 @@ struct DilatePush { u32 MipCountFor(u32 tile_size) { u32 mips = 1; while ((tile_size >> mips) >= kMinMipTexels) ++mips; - return std::min(mips, 5u); + return rx::Min(mips, 5u); } void WriteRow(const Vec3& axis, f32 half_extent, const Vec3& origin, f32 out[4]) { @@ -79,12 +82,12 @@ bool DecalBaker::Initialize(Device& device, const Desc& desc) { return false; } tiles_per_row_ = desc_.atlas_size / desc_.tile_size; - tile_count_ = std::min(tiles_per_row_ * tiles_per_row_, kMaxTiles); + tile_count_ = rx::Min(tiles_per_row_ * tiles_per_row_, kMaxTiles); tile_uv_ = static_cast(desc_.tile_size) / static_cast(desc_.atlas_size); mip_count_ = MipCountFor(desc_.tile_size); // A rebake replays the whole journal in one frame, so a journal longer than // the frame budget could never fit and that receiver would defer forever. - desc_.journal_limit = std::clamp(desc_.journal_limit, 1u, kMaxFrameStamps); + desc_.journal_limit = rx::Clamp(desc_.journal_limit, 1u, kMaxFrameStamps); if (!CreateAtlases(device) || !CreatePipelines(device)) { Destroy(device); @@ -159,7 +162,7 @@ bool DecalBaker::CreateAtlases(Device& device) { return false; } u8* bytes = static_cast(clear_staging_.mapped); - std::memset(bytes + clear_albedo_offset_, 0, texels * 4); + base::MemSet(bytes + clear_albedo_offset_, 0, texels * 4); for (u64 i = 0; i < texels; ++i) { u8* fx = bytes + clear_fx_offset_ + i * 4; fx[0] = 128; @@ -167,7 +170,7 @@ bool DecalBaker::CreateAtlases(Device& device) { fx[2] = 128; fx[3] = 0; } - std::memset(bytes + clear_chart_offset_, 0, texels); + base::MemSet(bytes + clear_chart_offset_, 0, texels); // Per-tile uv mapping, read by the forward pass to reproduce what the bake // did. Identity until a receiver claims the tile and says otherwise. @@ -341,7 +344,7 @@ u32 DecalBaker::AcquireReceiver() { } Receiver fresh; fresh.alive = true; - receivers_.push_back(std::move(fresh)); + receivers_.push_back(base::move(fresh)); ++stats_.receivers; return static_cast(receivers_.size()); } @@ -363,12 +366,12 @@ bool DecalBaker::Stamp(const DecalStamp& stamp) { Receiver* r = find(stamp.receiver); if (!r) return false; GpuStamp gpu; - std::memcpy(gpu.row0, stamp.projector.row0, sizeof(gpu.row0)); - std::memcpy(gpu.row1, stamp.projector.row1, sizeof(gpu.row1)); - std::memcpy(gpu.row2, stamp.projector.row2, sizeof(gpu.row2)); - std::memcpy(gpu.uv_rect, stamp.projector.uv_rect, sizeof(gpu.uv_rect)); - std::memcpy(gpu.tint_blend, stamp.projector.tint_blend, sizeof(gpu.tint_blend)); - std::memcpy(gpu.params2, stamp.projector.params2, sizeof(gpu.params2)); + base::MemCopy(gpu.row0, stamp.projector.row0, sizeof(gpu.row0)); + base::MemCopy(gpu.row1, stamp.projector.row1, sizeof(gpu.row1)); + base::MemCopy(gpu.row2, stamp.projector.row2, sizeof(gpu.row2)); + base::MemCopy(gpu.uv_rect, stamp.projector.uv_rect, sizeof(gpu.uv_rect)); + base::MemCopy(gpu.tint_blend, stamp.projector.tint_blend, sizeof(gpu.tint_blend)); + base::MemCopy(gpu.params2, stamp.projector.params2, sizeof(gpu.params2)); r->journal.push_back(gpu); if (r->journal.size() > desc_.journal_limit) { @@ -444,7 +447,7 @@ u32 DecalBaker::AcquireTile(u32 receiver, u64 frame_index) { return victim; } -void DecalBaker::AddToGraph(RenderGraph& graph, std::span targets, u32 frame_slot, +void DecalBaker::AddToGraph(RenderGraph& graph, base::Span targets, u32 frame_slot, u64 frame_index, TextureView source_albedo, TextureView source_normal) { stats_.bakes = 0; if (!available()) return; @@ -535,7 +538,7 @@ void DecalBaker::AddToGraph(RenderGraph& graph, std::span targets, draw.uv_bias[0] = r->uv_bias[0]; draw.uv_bias[1] = r->uv_bias[1]; if (count > 0) { - std::memcpy(mapped + cursor, run.data(), count * sizeof(GpuStamp)); + base::MemCopy(mapped + cursor, run.data(), count * sizeof(GpuStamp)); cursor += count; } draws.push_back(draw); @@ -548,7 +551,7 @@ void DecalBaker::AddToGraph(RenderGraph& graph, std::span targets, graph.AddPass( "decal_bake", [](RenderGraph::PassBuilder&) {}, - [this, draws = std::move(draws), frame_slot, source_albedo, + [this, draws = base::move(draws), frame_slot, source_albedo, source_normal](PassContext& ctx) { CommandList& cmd = *ctx.cmd; ResourceState atlas = atlas_state_; @@ -572,11 +575,11 @@ void DecalBaker::AddToGraph(RenderGraph& graph, std::span targets, const i32 y = static_cast((draw.tile / tiles_per_row_) * desc_.tile_size); BufferTextureCopy region{.offset = {x, y}, .extent = {desc_.tile_size, desc_.tile_size}}; region.buffer_offset = clear_albedo_offset_; - cmd.CopyBufferToTexture(clear_staging_, albedo_, {®ion, 1}); + cmd.CopyBufferToTexture(clear_staging_, albedo_, base::Span(®ion, 1)); region.buffer_offset = clear_fx_offset_; - cmd.CopyBufferToTexture(clear_staging_, fx_, {®ion, 1}); + cmd.CopyBufferToTexture(clear_staging_, fx_, base::Span(®ion, 1)); region.buffer_offset = clear_chart_offset_; - cmd.CopyBufferToTexture(clear_staging_, chart_, {®ion, 1}); + cmd.CopyBufferToTexture(clear_staging_, chart_, base::Span(®ion, 1)); } } @@ -591,7 +594,7 @@ void DecalBaker::AddToGraph(RenderGraph& graph, std::span targets, colors[1] = {.view = fx_mip0_, .load = LoadOp::kLoad}; colors[2] = {.view = chart_.view, .load = LoadOp::kLoad}; cmd.BeginRendering({.extent = {desc_.atlas_size, desc_.atlas_size}, - .colors = {colors, 3}}); + .colors = base::Span(colors, 3)}); PipelineHandle bound{}; for (const BakeDraw& draw : draws) { if (draw.stamp_count == 0) continue; @@ -677,7 +680,7 @@ void DecalBaker::AddToGraph(RenderGraph& graph, std::span targets, u32 size = desc_.atlas_size; for (u32 mip = 1; mip < mip_count_; ++mip) { const Extent2D src{size, size}; - const Extent2D dst{std::max(size >> 1, 1u), std::max(size >> 1, 1u)}; + const Extent2D dst{rx::Max(size >> 1, 1u), rx::Max(size >> 1, 1u)}; cmd.BlitMip(albedo_, mip - 1, src, mip, dst); cmd.BlitMip(fx_, mip - 1, src, mip, dst); if (mip + 1 < mip_count_) { diff --git a/engine/render/texturing/decal_bake.h b/engine/render/texturing/decal_bake.h index e9345684..123a4fe3 100644 --- a/engine/render/texturing/decal_bake.h +++ b/engine/render/texturing/decal_bake.h @@ -23,10 +23,10 @@ // [0,7) gets a full-resolution layer); geometry outside 0..1 after the // transform takes no decal at all. -#include #include +#include "base/containers/span.h" #include "core/export.h" #include "core/math.h" #include "render/core/render_graph.h" @@ -125,7 +125,7 @@ class DecalBaker { // `source_albedo` / `source_normal` are the authored decal atlas the stamps' // uv_rect indexes; either may be null, and a built-in white / flat page // stands in. - void AddToGraph(RenderGraph& graph, std::span targets, u32 frame_slot, + void AddToGraph(RenderGraph& graph, base::Span targets, u32 frame_slot, u64 frame_index, TextureView source_albedo, TextureView source_normal); TextureView albedo_view() const { return albedo_.view; } diff --git a/engine/render/texturing/virtual_texture.cc b/engine/render/texturing/virtual_texture.cc index b7265e19..a1bfa16f 100644 --- a/engine/render/texturing/virtual_texture.cc +++ b/engine/render/texturing/virtual_texture.cc @@ -1,11 +1,15 @@ #include "render/texturing/virtual_texture.h" -#include -#include -#include -#include +#include +#include +#include "base/containers/unordered_set.h" +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/threading/lock_guard.h" #include "core/log.h" +#include "core/scalar.h" namespace rx::render { namespace { @@ -58,7 +62,7 @@ bool VirtualTexture::Initialize(Device& device) { pages_[m].assign(static_cast(PagesAt(m)) * PagesAt(m), PageState{}); indirection_cpu_[m].assign(static_cast(PagesAt(m)) * PagesAt(m) * 4, 0); } - for (GpuBuffer& rb : readback_) std::memset(rb.mapped, 0, sizeof(u32)); + for (GpuBuffer& rb : readback_) base::MemSet(rb.mapped, 0, sizeof(u32)); device.ImmediateSubmit([this](CommandList& cmd) { TextureBarrier init[2] = { @@ -69,12 +73,12 @@ bool VirtualTexture::Initialize(Device& device) { cmd.FillBuffer(feedback_, 0, sizeof(u32), 0); }); - worker_ = std::thread([this] { + worker_ = base::MakeUnique("rx-vt-pages", [this] { for (;;) { PageKey key; { - std::unique_lock lock(queue_mutex_); - queue_cv_.wait(lock, [this] { return worker_quit_ || !requests_.empty(); }); + base::UniqueLock lock(queue_mutex_); + queue_cv_.Wait(lock, [this] { return worker_quit_ || !requests_.empty(); }); if (worker_quit_) return; key = requests_.front(); requests_.pop_front(); @@ -82,29 +86,30 @@ bool VirtualTexture::Initialize(Device& device) { GeneratedPage page; page.key = key; GeneratePage(key, &page.pixels); - std::lock_guard lock(queue_mutex_); - completed_.push_back(std::move(page)); + base::LockGuard lock(queue_mutex_); + completed_.push_back(base::move(page)); } - }); + }, /*start_now=*/true); // Seed the coarsest page so every pixel has a fallback from frame one. { - std::lock_guard lock(queue_mutex_); + base::LockGuard lock(queue_mutex_); requests_.push_back({kMips - 1, 0, 0}); Page({kMips - 1, 0, 0}).pending = true; } - queue_cv_.notify_one(); + queue_cv_.NotifyOne(); return true; } void VirtualTexture::Destroy(Device& device) { - if (worker_.joinable()) { + if (worker_) { { - std::lock_guard lock(queue_mutex_); + base::LockGuard lock(queue_mutex_); worker_quit_ = true; } - queue_cv_.notify_all(); - worker_.join(); + queue_cv_.NotifyAll(); + worker_->Join(); + worker_.Reset(); } if (atlas_) device.DestroyImage(atlas_); atlas_ = {}; @@ -121,7 +126,7 @@ void VirtualTexture::Destroy(Device& device) { // The demo "megatexture": a continuous survey pattern over the whole virtual // space - kilometer-grid lines, a hue that drifts across the space, per-page // hairlines and a mip tint, so residency, borders and LOD are all visible. -void VirtualTexture::GeneratePage(const PageKey& key, std::vector* pixels) const { +void VirtualTexture::GeneratePage(const PageKey& key, base::Vector* pixels) const { pixels->resize(kPageBytes); const f32 scale = static_cast(1u << key.mip); // virtual texels per texel here const f32 virtual_size = static_cast(kVirtualPages) * kPayload; @@ -136,15 +141,15 @@ void VirtualTexture::GeneratePage(const PageKey& key, std::vector* pixels) c f32 u = vx / virtual_size, v = vy / virtual_size; // Base: two-tone checker drifting through hue across the space. - f32 checker = (static_cast(std::floor(vx / 64.0f)) ^ - static_cast(std::floor(vy / 64.0f))) & 1 + f32 checker = (static_cast(::floor(vx / 64.0f)) ^ + static_cast(::floor(vy / 64.0f))) & 1 ? 0.55f : 0.45f; f32 r = checker * (0.6f + 0.4f * u); f32 g = checker * (0.6f + 0.4f * v); f32 b = checker * (0.6f + 0.4f * (1.0f - u)); // Coarse grid lines every 1024 virtual texels. - f32 gx = std::fmod(vx, 1024.0f), gy = std::fmod(vy, 1024.0f); + f32 gx = ::fmod(vx, 1024.0f), gy = ::fmod(vy, 1024.0f); if (gx < 3.0f * scale || gy < 3.0f * scale) { r = g = b = 0.05f; } @@ -154,9 +159,9 @@ void VirtualTexture::GeneratePage(const PageKey& key, std::vector* pixels) c g = g * (1.3f - 0.6f * t); size_t o = (static_cast(y) * kPageStored + x) * 4; - (*pixels)[o + 0] = static_cast(std::clamp(r, 0.0f, 1.0f) * 255.0f); - (*pixels)[o + 1] = static_cast(std::clamp(g, 0.0f, 1.0f) * 255.0f); - (*pixels)[o + 2] = static_cast(std::clamp(b, 0.0f, 1.0f) * 255.0f); + (*pixels)[o + 0] = static_cast(rx::Clamp(r, 0.0f, 1.0f) * 255.0f); + (*pixels)[o + 1] = static_cast(rx::Clamp(g, 0.0f, 1.0f) * 255.0f); + (*pixels)[o + 2] = static_cast(rx::Clamp(b, 0.0f, 1.0f) * 255.0f); (*pixels)[o + 3] = 255; } } @@ -177,7 +182,7 @@ void VirtualTexture::WriteIndirection(const PageKey& key) { return; } } - std::memset(&indirection_cpu_[mip][static_cast(PageIndex(key)) * 4], 0, 4); + base::MemSet(&indirection_cpu_[mip][static_cast(PageIndex(key)) * 4], 0, 4); } // Refresh every indirection cell in the page's footprint (its own cell plus @@ -230,12 +235,12 @@ void VirtualTexture::AddToGraph(RenderGraph& graph, u64 frame_index) { // safely past the fence) and turn misses into generation requests. const GpuBuffer& rb = readback_[frame_index % kReadbackRing]; const u32* data = static_cast(rb.mapped); - u32 count = std::min(data[0], kFeedbackCapacity); - std::unordered_set seen; + u32 count = rx::Min(data[0], kFeedbackCapacity); + base::UnorderedSet seen; u32 enqueued = 0; for (u32 i = 0; i < count; ++i) { u32 packed = data[1 + i]; - if (!seen.insert(packed).second) continue; + if (!seen.insert(packed)) continue; PageKey key{packed >> 24, packed & 0xfffu, (packed >> 12) & 0xfffu}; if (key.mip >= kMips) continue; u32 pages = PagesAt(key.mip); @@ -245,19 +250,19 @@ void VirtualTexture::AddToGraph(RenderGraph& graph, u64 frame_index) { if (state.atlas_slot != 0xffff || state.pending) continue; state.pending = true; { - std::lock_guard lock(queue_mutex_); + base::LockGuard lock(queue_mutex_); requests_.push_back(key); } ++enqueued; } - if (enqueued > 0) queue_cv_.notify_one(); + if (enqueued > 0) queue_cv_.NotifyOne(); // 2) Collect finished pages (bounded per frame by the staging budget). - std::vector ready; + base::Vector ready; { - std::lock_guard lock(queue_mutex_); + base::LockGuard lock(queue_mutex_); while (!completed_.empty() && ready.size() < kMaxUploadsPerFrame) { - ready.push_back(std::move(completed_.front())); + ready.push_back(base::move(completed_.front())); completed_.pop_front(); } } @@ -265,14 +270,14 @@ void VirtualTexture::AddToGraph(RenderGraph& graph, u64 frame_index) { PageKey key; u16 slot; }; - std::vector uploads; + base::Vector uploads; for (size_t i = 0; i < ready.size(); ++i) { GeneratedPage& page = ready[i]; PageState& state = Page(page.key); state.pending = false; u16 slot = AcquireSlot(frame_index); if (slot == 0xffff) continue; // atlas exhausted this frame - std::memcpy(static_cast(upload_staging_.mapped) + uploads.size() * kPageBytes, + base::MemCopy(static_cast(upload_staging_.mapped) + uploads.size() * kPageBytes, page.pixels.data(), kPageBytes); state.atlas_slot = slot; state.last_used = frame_index; @@ -289,14 +294,14 @@ void VirtualTexture::AddToGraph(RenderGraph& graph, u64 frame_index) { if (upload_indirection) { u64 offset = 0; for (u32 m = 0; m < kMips; ++m) { - std::memcpy(static_cast(indirection_staging_.mapped) + offset, + base::MemCopy(static_cast(indirection_staging_.mapped) + offset, indirection_cpu_[m].data(), indirection_cpu_[m].size()); offset += indirection_cpu_[m].size(); } } graph.AddPass( "vt_upload", [](RenderGraph::PassBuilder&) {}, - [this, uploads = std::move(uploads), upload_indirection](PassContext& ctx) { + [this, uploads = base::move(uploads), upload_indirection](PassContext& ctx) { if (!uploads.empty()) { TextureBarrier to_copy[1] = {Transition(atlas_, ResourceState::kShaderReadFragment, ResourceState::kCopyDst)}; @@ -311,7 +316,7 @@ void VirtualTexture::AddToGraph(RenderGraph& graph, u64 frame_index) { regions.push_back(copy); } ctx.cmd->CopyBufferToTexture(upload_staging_, atlas_, - {regions.data(), regions.size()}); + base::Span(regions.data(), regions.size())); TextureBarrier to_read[1] = { Transition(atlas_, ResourceState::kCopyDst, ResourceState::kShaderReadFragment)}; ctx.cmd->TextureBarriers(to_read); @@ -330,7 +335,7 @@ void VirtualTexture::AddToGraph(RenderGraph& graph, u64 frame_index) { offset += static_cast(PagesAt(m)) * PagesAt(m) * 4; } ctx.cmd->CopyBufferToTexture(indirection_staging_, indirection_, - {regions.data(), regions.size()}); + base::Span(regions.data(), regions.size())); TextureBarrier to_read[1] = {Transition(indirection_, ResourceState::kCopyDst, ResourceState::kShaderReadFragment)}; ctx.cmd->TextureBarriers(to_read); diff --git a/engine/render/texturing/virtual_texture.h b/engine/render/texturing/virtual_texture.h index afd9e64a..eef21af7 100644 --- a/engine/render/texturing/virtual_texture.h +++ b/engine/render/texturing/virtual_texture.h @@ -14,13 +14,13 @@ // pattern that makes residency and mip level visible); the machinery - // feedback, residency, indirection maintenance, streaming - is the product. -#include -#include -#include -#include -#include -#include +#include "base/containers/deque.h" +#include "base/containers/vector.h" +#include "base/memory/unique_pointer.h" +#include "base/threading/condition_variable.h" +#include "base/threading/mutex.h" +#include "base/threading/thread.h" #include "core/math.h" #include "render/core/render_graph.h" #include "render/rhi/device.h" @@ -61,7 +61,7 @@ class VirtualTexture { }; struct GeneratedPage { PageKey key; - std::vector pixels; // kPageStored^2 RGBA8, border included + base::Vector pixels; // kPageStored^2 RGBA8, border included }; struct PageState { u16 atlas_slot = 0xffff; // 0xffff = not resident @@ -71,7 +71,7 @@ class VirtualTexture { u32 PageIndex(const PageKey& key) const; PageState& Page(const PageKey& key); - void GeneratePage(const PageKey& key, std::vector* pixels) const; + void GeneratePage(const PageKey& key, base::Vector* pixels) const; void WriteIndirection(const PageKey& key); void PropagateIndirection(const PageKey& key); u16 AcquireSlot(u64 frame_index); @@ -86,20 +86,20 @@ class VirtualTexture { GpuBuffer indirection_staging_; // full pyramid, re-uploaded when dirty // CPU residency state, indexed per mip then page. - std::vector pages_[kMips]; + base::Vector pages_[kMips]; // CPU indirection mirror (RGBA8 per entry), uploaded when dirty. - std::vector indirection_cpu_[kMips]; - std::vector slot_owner_ = std::vector(kAtlasPages * kAtlasPages); - std::vector slot_used_ = std::vector(kAtlasPages * kAtlasPages, false); + base::Vector indirection_cpu_[kMips]; + base::Vector slot_owner_ = base::Vector(kAtlasPages * kAtlasPages); + base::Vector slot_used_ = base::Vector(kAtlasPages * kAtlasPages, false); u32 resident_count_ = 0; bool indirection_dirty_ = true; // Generation worker. - std::thread worker_; - std::mutex queue_mutex_; - std::condition_variable queue_cv_; - std::deque requests_; - std::deque completed_; + base::UniquePointer worker_; + base::Mutex queue_mutex_; + base::ConditionVariable queue_cv_; + base::SimpleDeque requests_; + base::SimpleDeque completed_; bool worker_quit_ = false; }; diff --git a/engine/render/util/exr_write.cc b/engine/render/util/exr_write.cc index b226699b..8c6580ab 100644 --- a/engine/render/util/exr_write.cc +++ b/engine/render/util/exr_write.cc @@ -1,25 +1,26 @@ +#include "base/containers/vector.h" +#include "base/strings/xstring.h" #include "render/util/exr_write.h" -#include -#include -#include +#include +#include namespace rx::render { namespace { // The platform is little-endian x86_64 and EXR stores little-endian, so scalars // serialize by raw copy. -void Push(std::vector& b, const void* data, size_t n) { +void Push(base::Vector& b, const void* data, size_t n) { const u8* p = static_cast(data); b.insert(b.end(), p, p + n); } -void PushI32(std::vector& b, i32 v) { Push(b, &v, 4); } -void PushU64(std::vector& b, u64 v) { Push(b, &v, 8); } -void PushF32(std::vector& b, f32 v) { Push(b, &v, 4); } -void PushStr(std::vector& b, const char* s) { Push(b, s, std::strlen(s) + 1); } // with null +void PushI32(base::Vector& b, i32 v) { Push(b, &v, 4); } +void PushU64(base::Vector& b, u64 v) { Push(b, &v, 8); } +void PushF32(base::Vector& b, f32 v) { Push(b, &v, 4); } +void PushStr(base::Vector& b, const char* s) { Push(b, s, ::strlen(s) + 1); } // with null // One header attribute: name, type, value-size, value bytes. -void PushAttr(std::vector& b, const char* name, const char* type, const std::vector& value) { +void PushAttr(base::Vector& b, const char* name, const char* type, const base::Vector& value) { PushStr(b, name); PushStr(b, type); PushI32(b, static_cast(value.size())); @@ -28,15 +29,15 @@ void PushAttr(std::vector& b, const char* name, const char* type, const std: } // namespace -bool WriteExrRgbF32(const std::string& path, u32 width, u32 height, const f32* rgb) { +bool WriteExrRgbF32(const base::String& path, u32 width, u32 height, const f32* rgb) { if (width == 0 || height == 0 || !rgb) return false; - std::vector out; + base::Vector out; PushI32(out, 0x01312f76); // magic PushI32(out, 0x00000002); // version 2, no flags (single-part scanline) // channels: alphabetical B, G, R, each 32-bit float, sampling 1x1. - std::vector channels; + base::Vector channels; for (const char* name : {"B", "G", "R"}) { PushStr(channels, name); PushI32(channels, 2); // FLOAT @@ -50,10 +51,10 @@ bool WriteExrRgbF32(const std::string& path, u32 width, u32 height, const f32* r channels.push_back(0); // terminates the channel list PushAttr(out, "channels", "chlist", channels); - std::vector compression{0}; // NO_COMPRESSION + base::Vector compression{0}; // NO_COMPRESSION PushAttr(out, "compression", "compression", compression); - std::vector data_window; + base::Vector data_window; PushI32(data_window, 0); PushI32(data_window, 0); PushI32(data_window, static_cast(width) - 1); @@ -61,19 +62,19 @@ bool WriteExrRgbF32(const std::string& path, u32 width, u32 height, const f32* r PushAttr(out, "dataWindow", "box2i", data_window); PushAttr(out, "displayWindow", "box2i", data_window); - std::vector line_order{0}; // INCREASING_Y + base::Vector line_order{0}; // INCREASING_Y PushAttr(out, "lineOrder", "lineOrder", line_order); - std::vector aspect; + base::Vector aspect; PushF32(aspect, 1.0f); PushAttr(out, "pixelAspectRatio", "float", aspect); - std::vector window_center; + base::Vector window_center; PushF32(window_center, 0.0f); PushF32(window_center, 0.0f); PushAttr(out, "screenWindowCenter", "v2f", window_center); - std::vector window_width; + base::Vector window_width; PushF32(window_width, 1.0f); PushAttr(out, "screenWindowWidth", "float", window_width); @@ -94,10 +95,10 @@ bool WriteExrRgbF32(const std::string& path, u32 width, u32 height, const f32* r } } - std::FILE* f = std::fopen(path.c_str(), "wb"); + FILE* f = ::fopen(path.c_str(), "wb"); if (!f) return false; - size_t written = std::fwrite(out.data(), 1, out.size(), f); - std::fclose(f); + size_t written = ::fwrite(out.data(), 1, out.size(), f); + ::fclose(f); return written == out.size(); } diff --git a/engine/render/util/exr_write.h b/engine/render/util/exr_write.h index 9dd6171b..9bdc84db 100644 --- a/engine/render/util/exr_write.h +++ b/engine/render/util/exr_write.h @@ -1,8 +1,8 @@ #ifndef RX_RENDER_EXR_WRITE_H_ #define RX_RENDER_EXR_WRITE_H_ -#include +#include "base/strings/xstring.h" #include "core/types.h" namespace rx::render { @@ -11,7 +11,7 @@ namespace rx::render { // is width*height*3 floats, row-major, top row first. Returns false on a file // error. Minimal but spec-correct: enough for compositors (Nuke, Resolve, oiio, // ffmpeg) to read the engine's linear-hdr captures as a production container. -bool WriteExrRgbF32(const std::string& path, u32 width, u32 height, const f32* rgb); +bool WriteExrRgbF32(const base::String& path, u32 width, u32 height, const f32* rgb); } // namespace rx::render diff --git a/engine/render/util/gpu_profiler.cc b/engine/render/util/gpu_profiler.cc index b9c6bcab..a9358e6b 100644 --- a/engine/render/util/gpu_profiler.cc +++ b/engine/render/util/gpu_profiler.cc @@ -1,7 +1,9 @@ #include "render/util/gpu_profiler.h" #include +#include +#include "base/memory/move.h" #include "core/log.h" #include "render/rhi/command_list.h" #include "render/rhi/device.h" @@ -23,13 +25,13 @@ bool GpuProfiler::Initialize(Device& device, u32 frames_in_flight) { fp.pool = device.CreateTimestampPool(kInitialPasses * kQueriesPerPass); if (!fp.pool) return false; fp.capacity = kInitialPasses; - frames_.push_back(std::move(fp)); + frames_.push_back(base::move(fp)); } device_ = &device; if (const char* path = TimingFile.get(); path && *path) { - timing_file_ = std::fopen(path, "w"); + timing_file_ = ::fopen(path, "w"); if (timing_file_) { - std::fprintf(timing_file_, "frame\tpass\tms\n"); + ::fprintf(timing_file_, "frame\tpass\tms\n"); } else { RX_ERROR("Cannot open GPU timing file: {}", path); } @@ -46,7 +48,7 @@ void GpuProfiler::Shutdown() { frames_.clear(); device_ = nullptr; if (timing_file_) { - std::fclose(timing_file_); + ::fclose(timing_file_); timing_file_ = nullptr; } } @@ -70,7 +72,7 @@ void GpuProfiler::BeginFrame(CommandList& cmd, u32 frame_slot) { results_.push_back({fp.names[i], ms}); total_ms_ += ms; if (timing_file_) { - std::fprintf(timing_file_, "%llu\t%s\t%.6f\n", + ::fprintf(timing_file_, "%llu\t%s\t%.6f\n", static_cast(resolved_frames_), fp.names[i].c_str(), ms); } } diff --git a/engine/render/util/gpu_profiler.h b/engine/render/util/gpu_profiler.h index d23ffcd0..198c5266 100644 --- a/engine/render/util/gpu_profiler.h +++ b/engine/render/util/gpu_profiler.h @@ -1,11 +1,11 @@ #ifndef RX_RENDER_GPU_PROFILER_H_ #define RX_RENDER_GPU_PROFILER_H_ -#include -#include +#include #include +#include "base/strings/xstring.h" #include "core/types.h" #include "render/rhi/types.h" @@ -26,7 +26,7 @@ class Device; class GpuProfiler { public: struct PassTiming { - std::string name; + base::String name; f32 ms = 0.0f; }; @@ -69,7 +69,7 @@ class GpuProfiler { struct FramePool { TimestampPoolHandle pool; - base::Vector names; // one per pass, in record order + base::Vector names; // one per pass, in record order u32 pass_count = 0; u32 capacity = 0; // passes the pool holds bool recorded = false; @@ -88,7 +88,7 @@ class GpuProfiler { base::Vector results_; f32 total_ms_ = 0.0f; - std::FILE* timing_file_ = nullptr; + FILE* timing_file_ = nullptr; u64 resolved_frames_ = 0; }; diff --git a/engine/render/util/imgui_renderer.cc b/engine/render/util/imgui_renderer.cc index c046075e..201cf194 100644 --- a/engine/render/util/imgui_renderer.cc +++ b/engine/render/util/imgui_renderer.cc @@ -1,18 +1,20 @@ #include "render/util/imgui_renderer.h" -#include -#include -#include -#include +#include +#include #include +#include "base/algorithm.h" #include "core/log.h" #include "render/rhi/bindings.h" #include "render/rhi/command_list.h" #include "render/rhi/pipeline.h" // Build-embedded imgui shaders (engine/render/shaders/util/imgui.{vs,ps}.hlsl). +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "core/scalar.h" #include "shaders/imgui_vs_hlsl.h" #include "shaders/imgui_ps_hlsl.h" @@ -64,7 +66,7 @@ bool ImGuiRenderer::Initialize(Device& device, Format target_format) { {.location = 1, .format = Format::kRG32Float, .offset = offsetof(ImDrawVert, uv)}); vb.attributes.push_back( {.location = 2, .format = Format::kRGBA8Unorm, .offset = offsetof(ImDrawVert, col)}); - pd.vertex_buffers.push_back(std::move(vb)); + pd.vertex_buffers.push_back(base::move(vb)); pd.color_formats.push_back(target_format); // Premultiplied, not straight alpha: the pixel stage resolves the widget tint @@ -78,7 +80,7 @@ bool ImGuiRenderer::Initialize(Device& device, Format target_format) { set0.stages = kShaderStageFragment; set0.slots.push_back({.binding = 0, .type = BindingType::kCombinedTextureSampler}); set0.slots.push_back({.binding = 1, .type = BindingType::kCombinedTextureSampler}); - pd.sets.push_back(std::move(set0)); + pd.sets.push_back(base::move(set0)); pd.push_constant_size = PushSize(); pd.debug_name = "imgui"; @@ -104,7 +106,7 @@ void ImGuiRenderer::DestroyTexture(ImTextureData* tex) { tex->SetTexID(ImTextureID_Invalid); tex->BackendUserData = nullptr; tex->SetStatus(ImTextureStatus_Destroyed); - textures_.erase(std::remove(textures_.begin(), textures_.end(), tex), textures_.end()); + base::EraseIf(textures_, [tex](ImTextureData* t) { return t == tex; }); } void ImGuiRenderer::UpdateTexture(ImTextureData* tex) { @@ -138,13 +140,13 @@ void ImGuiRenderer::UpdateTexture(ImTextureData* tex) { // simple and correct - imgui only issues updates when glyphs are added. const u64 size = static_cast(tex->Width) * tex->Height * 4; GpuBuffer staging = device_->CreateBuffer(size, kBufferUsageTransferSrc, true); - std::memcpy(staging.mapped, tex->GetPixels(), size); + base::MemCopy(staging.mapped, tex->GetPixels(), size); device_->ImmediateSubmit([&](CommandList& cmd) { cmd.Barrier(Transition(backend->image, create ? ResourceState::kUndefined : ResourceState::kShaderReadFragment, ResourceState::kCopyDst)); BufferTextureCopy region{}; // whole mip 0 at the origin - cmd.CopyBufferToTexture(staging, backend->image, {®ion, 1}); + cmd.CopyBufferToTexture(staging, backend->image, base::Span(®ion, 1)); cmd.Barrier(Transition(backend->image, ResourceState::kCopyDst, ResourceState::kShaderReadFragment)); }); @@ -157,7 +159,8 @@ void ImGuiRenderer::Render(ImDrawData* draw_data, CommandList& cmd) { // One backdrop per Render, so a frame that sets none cannot resample the // previous frame's blur texture (a render-graph transient, long recycled). - const TextureView backdrop = std::exchange(backdrop_, TextureView{}); + const TextureView backdrop = backdrop_; + backdrop_ = TextureView{}; const SamplerHandle backdrop_sampler = backdrop_sampler_ ? backdrop_sampler_ : sampler_; // Service texture create/update/destroy requests before drawing. @@ -178,19 +181,19 @@ void ImGuiRenderer::Render(ImDrawData* draw_data, CommandList& cmd) { const u64 idx_size = static_cast(draw_data->TotalIdxCount) * sizeof(ImDrawIdx); if (!rb.vertices || rb.vertices.size < vtx_size) { if (rb.vertices) device_->DestroyBufferDeferred(rb.vertices); - rb.vertices = device_->CreateBuffer(std::max(vtx_size, 1), kBufferUsageVertex, true); + rb.vertices = device_->CreateBuffer(rx::Max(vtx_size, 1), kBufferUsageVertex, true); } if (!rb.indices || rb.indices.size < idx_size) { if (rb.indices) device_->DestroyBufferDeferred(rb.indices); - rb.indices = device_->CreateBuffer(std::max(idx_size, 1), kBufferUsageIndex, true); + rb.indices = device_->CreateBuffer(rx::Max(idx_size, 1), kBufferUsageIndex, true); } if (!rb.vertices.mapped || !rb.indices.mapped) return; auto* vtx_dst = static_cast(rb.vertices.mapped); auto* idx_dst = static_cast(rb.indices.mapped); for (const ImDrawList* list : draw_data->CmdLists) { - std::memcpy(vtx_dst, list->VtxBuffer.Data, list->VtxBuffer.Size * sizeof(ImDrawVert)); - std::memcpy(idx_dst, list->IdxBuffer.Data, list->IdxBuffer.Size * sizeof(ImDrawIdx)); + base::MemCopy(vtx_dst, list->VtxBuffer.Data, list->VtxBuffer.Size * sizeof(ImDrawVert)); + base::MemCopy(idx_dst, list->IdxBuffer.Data, list->IdxBuffer.Size * sizeof(ImDrawIdx)); vtx_dst += list->VtxBuffer.Size; idx_dst += list->IdxBuffer.Size; } @@ -225,10 +228,10 @@ void ImGuiRenderer::Render(ImDrawData* draw_data, CommandList& cmd) { f32 min_y = (pcmd.ClipRect.y - clip_off.y) * clip_scale.y; f32 max_x = (pcmd.ClipRect.z - clip_off.x) * clip_scale.x; f32 max_y = (pcmd.ClipRect.w - clip_off.y) * clip_scale.y; - min_x = std::max(min_x, 0.0f); - min_y = std::max(min_y, 0.0f); - max_x = std::min(max_x, static_cast(fb_width)); - max_y = std::min(max_y, static_cast(fb_height)); + min_x = rx::Max(min_x, 0.0f); + min_y = rx::Max(min_y, 0.0f); + max_x = rx::Min(max_x, static_cast(fb_width)); + max_y = rx::Min(max_y, static_cast(fb_height)); if (max_x <= min_x || max_y <= min_y) continue; cmd.SetScissor(static_cast(min_x), static_cast(min_y), static_cast(max_x - min_x), static_cast(max_y - min_y)); diff --git a/engine/render/util/imgui_renderer.h b/engine/render/util/imgui_renderer.h index dff7ad1a..39072d6e 100644 --- a/engine/render/util/imgui_renderer.h +++ b/engine/render/util/imgui_renderer.h @@ -18,9 +18,9 @@ // Compiled only when the vendored imgui target exists; engine/render never // hard-depends on imgui. -#include -#include +#include +#include "base/containers/vector.h" #include "core/export.h" #include "render/rhi/device.h" #include "render/rhi/types.h" @@ -86,7 +86,7 @@ class RX_RENDER_EXPORT ImGuiRenderer { // Textures this backend created (owns the GPU backing behind BackendUserData), // tracked so Shutdown frees them without a global ImGui:: call (RX_SHARED keeps // one imgui context in the app DSO, not this one). - std::vector textures_; + base::Vector textures_; }; } // namespace rx::render diff --git a/engine/render/util/imgui_theme.h b/engine/render/util/imgui_theme.h index 651a7701..9835e3fe 100644 --- a/engine/render/util/imgui_theme.h +++ b/engine/render/util/imgui_theme.h @@ -12,11 +12,12 @@ // here, it compiles into the caller and touches the caller's context - the same // reason ImGuiRenderer itself calls no global ImGui:: function. -#include -#include +#include +#include #include +#include "base/memory/mem_ops.h" #include "core/types.h" namespace rx::render { @@ -151,7 +152,7 @@ inline ImFont* LoadRxImGuiFont(const void* ttf, size_t size, f32 size_pixels = kRxDefaultFontSize) { if (ttf == nullptr || size == 0) return nullptr; void* owned = IM_ALLOC(size); - std::memcpy(owned, ttf, size); + base::MemCopy(owned, ttf, size); return ImGui::GetIO().Fonts->AddFontFromMemoryTTF(owned, static_cast(size), size_pixels); } diff --git a/engine/render/util/shader_util.cc b/engine/render/util/shader_util.cc index 36c00d9e..d7efd8be 100644 --- a/engine/render/util/shader_util.cc +++ b/engine/render/util/shader_util.cc @@ -1,16 +1,17 @@ #include "render/util/shader_util.h" -#include +#include #include +#include "base/memory/mem_ops.h" #include "core/types.h" namespace rx::render { VkShaderModule CreateShaderModule(VkDevice device, const unsigned char* code, size_t size) { base::Vector words((size + 3) / 4); - std::memcpy(words.data(), code, size); + base::MemCopy(words.data(), code, size); VkShaderModuleCreateInfo info{.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; info.codeSize = size; diff --git a/engine/render/util/shader_util.h b/engine/render/util/shader_util.h index 47ae74bd..4f029c5b 100644 --- a/engine/render/util/shader_util.h +++ b/engine/render/util/shader_util.h @@ -7,7 +7,7 @@ #include -#include +#include #include "core/export.h" diff --git a/engine/render/vulkan/vk_backend.h b/engine/render/vulkan/vk_backend.h index 2b888cfe..3da47861 100644 --- a/engine/render/vulkan/vk_backend.h +++ b/engine/render/vulkan/vk_backend.h @@ -5,23 +5,22 @@ // include this; pass code sees rhi/ headers exclusively. #include -#include -#include -#include -#include -#include -#include -#include - #include -#include -#include - #include #include +#include "base/atomic.h" +#include "base/containers/span.h" +#include "base/memory/unique_pointer.h" +#include "base/containers/deque.h" +#include "base/functional/function.h" +#include "base/strings/xstring.h" +#include "base/threading/condition_variable.h" +#include "base/threading/lock_guard.h" +#include "base/threading/mutex.h" +#include "base/threading/thread.h" #include "render/rhi/device.h" #include "render/rhi/swapchain.h" #include "render/rhi/vulkan_interop.h" @@ -72,7 +71,7 @@ struct PipelineRecord { base::Vector set_layouts; // Batched creation compiles on a worker thread; readers wait via // WaitPipelineReady before touching any other field. - std::atomic build_state{kBuilt}; + base::Atomic build_state{kBuilt}; }; struct AccelStructRecord { @@ -157,7 +156,7 @@ class VulkanCommandList final : public CommandList { void BindPipeline(PipelineHandle pipeline) override; void BindSet(u32 set_index, BindingSetHandle set) override; - void BindTransient(u32 set_index, std::span items) override; + void BindTransient(u32 set_index, base::Span items) override; void PushConstants(const void* data, u32 size, u32 offset) override; void Dispatch(u32 x, u32 y, u32 z) override; void DispatchIndirect(const GpuBuffer& args, u64 offset) override; @@ -180,10 +179,10 @@ class VulkanCommandList final : public CommandList { u32 stride) override; void DrawMeshTasksIndirectCount(const GpuBuffer& args, u64 offset, const GpuBuffer& count_buffer, u64 count_offset, u32 max_draws, u32 stride) override; - void TextureBarriers(std::span barriers) override; + void TextureBarriers(base::Span barriers) override; void MemoryBarrier(BarrierScope src, BarrierScope dst) override; void CopyBufferToTexture(const GpuBuffer& src, const GpuImage& dst, - std::span regions) override; + base::Span regions) override; void CopyTexture(const GpuImage& src, const GpuImage& dst) override; void CopyTextureToBuffer(const GpuImage& src, const GpuBuffer& dst, const BufferTextureCopy& region) override; @@ -225,7 +224,7 @@ class VulkanCommandList final : public CommandList { class VulkanSwapchain final : public Swapchain { public: - static std::unique_ptr Create(VulkanDevice& device, u32 width, u32 height, + static base::UniquePointer Create(VulkanDevice& device, u32 width, u32 height, bool vsync, bool hdr); ~VulkanSwapchain() override; @@ -260,16 +259,16 @@ class VulkanSwapchain final : public Swapchain { class VulkanDevice final : public Device { public: - static std::unique_ptr Create(const DeviceDesc& desc, Window& window); + static base::UniquePointer Create(const DeviceDesc& desc, Window& window); // Surfaceless device: no window, no surface, no swapchain extension. Same // adapter selection, feature enablement and caps as the windowed path. - static std::unique_ptr CreateOffscreen(const DeviceDesc& desc); + static base::UniquePointer CreateOffscreen(const DeviceDesc& desc); ~VulkanDevice() override; void WaitIdle() override; bool RecreateSurface(Window& window) override; void DestroySurface() override; - std::unique_ptr CreateSwapchain(u32 width, u32 height, bool vsync, + base::UniquePointer CreateSwapchain(u32 width, u32 height, bool vsync, bool hdr) override; MemoryBudget memory_budget() const override; @@ -307,7 +306,7 @@ class VulkanDevice final : public Device { void DestroyBindingLayout(BindingLayoutHandle layout) override; BindingSetHandle CreateBindingSet(BindingLayoutHandle layout, u32 variable_count) override; void DestroyBindingSet(BindingSetHandle set) override; - void UpdateBindingSet(BindingSetHandle set, std::span items) override; + void UpdateBindingSet(BindingSetHandle set, base::Span items) override; AccelSizes GetBlasSizes(const BlasBuildDesc& desc) override; AccelSizes GetTlasSizes(u32 instance_count) override; @@ -322,14 +321,14 @@ class VulkanDevice final : public Device { void DestroyTimestampPool(TimestampPoolHandle pool) override; bool GetTimestamps(TimestampPoolHandle pool, u32 first, u32 count, u64* out) override; - void ImmediateSubmit(const std::function& record) override; + void ImmediateSubmit(const base::Function& record) override; void BeginUploadBatch() override; void FlushUploadBatch() override; bool UploadBatchActive() const override { CheckUploadBatchThread("UploadBatchActive"); return upload_batch_depth_ > 0; } - void RecordUpload(const std::function& record) override; + void RecordUpload(const base::Function& record) override; void ParkBatchStaging(GpuBuffer& buffer) override; bool ReadbackImage(const GpuImage& image, ResourceState current, void* out, size_t out_size) override; @@ -352,10 +351,10 @@ class VulkanDevice final : public Device { VmaAllocator allocator() const { return allocator_; } // Descriptor writes shared by BindTransient and UpdateBindingSet. - void WriteDescriptors(VkDescriptorSet set, std::span items); + void WriteDescriptors(VkDescriptorSet set, base::Span items); bool BuildComputePipeline(const ComputePipelineDesc& desc, PipelineRecord* record); bool BuildGraphicsPipeline(const GraphicsPipelineDesc& desc, PipelineRecord* record); - void EnqueuePipelineJob(std::function job); + void EnqueuePipelineJob(base::Function job); // Inside a batch the build runs on a worker with a deep copy of the desc // (the desc's vectors copy; debug_name is re-pointed at an owned string); @@ -370,23 +369,26 @@ class VulkanDevice final : public Device { } return MakeHandle(record); } - record->build_state.store(PipelineRecord::kBuilding, std::memory_order_relaxed); + record->build_state.store(PipelineRecord::kBuilding, base::memory_order_relaxed); EnqueuePipelineJob([this, copy = Desc(desc), - name = std::string(desc.debug_name ? desc.debug_name : ""), record, + name = base::String(desc.debug_name ? desc.debug_name : ""), record, build]() mutable { copy.debug_name = name.empty() ? nullptr : name.c_str(); const bool ok = build(copy, record); - if (!ok) pipeline_batch_failures_.fetch_add(1, std::memory_order_relaxed); + if (!ok) pipeline_batch_failures_.fetch_add(1, base::memory_order_relaxed); record->build_state.store(ok ? PipelineRecord::kBuilt : PipelineRecord::kFailed, - std::memory_order_release); - record->build_state.notify_all(); + base::memory_order_release); + // Passing through the mutex orders the store before any waiter's + // predicate check that could otherwise miss it and sleep. + { base::LockGuard lock(pipeline_ready_mutex_); } + pipeline_ready_cv_.NotifyAll(); }); return MakeHandle(record); } // Fails (and says why) when push_size is past the adapter's // maxPushConstantsSize instead of letting the driver reject the layout. - VkPipelineLayout GetOrCreatePipelineLayout(std::span sets, + VkPipelineLayout GetOrCreatePipelineLayout(base::Span sets, VkShaderStageFlags push_stages, u32 push_size, const char* debug_name); VkDescriptorSetLayout GetOrCreateSetLayout(const BindingLayoutDesc& desc); @@ -399,7 +401,7 @@ class VulkanDevice final : public Device { // swapchain device extension, and the graphics family is picked without a // present-support requirement. Everything else (adapter scoring, feature // enablement, caps, queues, resources) is identical. - static std::unique_ptr CreateImpl(const DeviceDesc& desc, Window* window); + static base::UniquePointer CreateImpl(const DeviceDesc& desc, Window* window); bool InitResources(); void ShutdownResources(); @@ -427,7 +429,7 @@ class VulkanDevice final : public Device { VkSemaphore image_available_fg = VK_NULL_HANDLE; VkFence in_flight = VK_NULL_HANDLE; VkDescriptorPool descriptor_pool = VK_NULL_HANDLE; - std::unique_ptr list; + base::UniquePointer list; bool fence_armed = false; // submit happened; next BeginFrame must wait // Async compute: extra graphics segments (fork/join splits) + the compute // queue's list, all reset with the shared pool. The fork semaphore gates @@ -435,10 +437,10 @@ class VulkanDevice final : public Device { // gates the final segment behind the compute work. static constexpr u32 kMaxSegments = 3; VkCommandBuffer seg_cmds[kMaxSegments - 1] = {}; - std::unique_ptr seg_lists[kMaxSegments - 1]; + base::UniquePointer seg_lists[kMaxSegments - 1]; VkCommandPool async_pool = VK_NULL_HANDLE; // compute-family pool when dedicated VkCommandBuffer async_cmd = VK_NULL_HANDLE; - std::unique_ptr async_list; + base::UniquePointer async_list; VkSemaphore fork_sem = VK_NULL_HANDLE; VkSemaphore async_sem = VK_NULL_HANDLE; u32 active_segment = 0; @@ -459,7 +461,7 @@ class VulkanDevice final : public Device { u32 push_constant_budget_ = 128; u32 api_version_ = VK_API_VERSION_1_3; VkPipelineCache pipeline_cache_ = VK_NULL_HANDLE; - std::string pipeline_cache_path_; + base::String pipeline_cache_path_; VkQueue graphics_queue_ = VK_NULL_HANDLE; VkQueue compute_queue_ = VK_NULL_HANDLE; // async compute (dedicated family when available) u32 graphics_family_ = 0; @@ -480,7 +482,7 @@ class VulkanDevice final : public Device { // Main-thread only, like every other immediate_* member. u32 upload_batch_depth_ = 0; VkCommandBuffer upload_batch_cmd_ = VK_NULL_HANDLE; - std::unique_ptr upload_batch_list_; + base::UniquePointer upload_batch_list_; base::Vector upload_batch_stagings_; // Host-visible bytes currently parked in upload_batch_stagings_. When it // crosses the budget the pending copies are submitted early (batch stays @@ -491,7 +493,7 @@ class VulkanDevice final : public Device { u32 upload_batch_records_ = 0; // The thread the device was created on; the batch state above is // unsynchronized, so every batch entry point enforces thread affinity. - std::thread::id upload_batch_thread_ = std::this_thread::get_id(); + u32 upload_batch_thread_ = base::GetCurrentThreadIndex(); void CheckUploadBatchThread(const char* what) const; // Lazily begins the batch command buffer on the first copy (null if that // fails: the caller falls back to an unbatched ImmediateSubmit); submits any @@ -526,7 +528,7 @@ class VulkanDevice final : public Device { // tell whether a batch might still reference what it is about to free. // Atomic because deferred destroys run off the device thread; the device // thread is the only writer. - std::atomic upload_park_serial_{0}; + base::Atomic upload_park_serial_{0}; VkFence AcquireUploadBatchFence(); void RetireCompletedUploadBatches(); // frees batches whose fence has signaled void DrainUploadBatchesInFlight(); // blocks until every in-flight batch retires @@ -547,12 +549,12 @@ class VulkanDevice final : public Device { u64 upload_serial = 0; }; Graveyard graveyard_[kMaxFramesInFlight]; - std::mutex graveyard_mutex_; + base::Mutex graveyard_mutex_; void NoteGraveyardUploadSerial(u32 slot); // graveyard_mutex_ held // Caches, keyed by content hash; entries live for the device's lifetime. // The layout caches are shared with pipeline-batch worker threads. - std::mutex layout_cache_mutex_; + base::Mutex layout_cache_mutex_; base::UnorderedMap set_layout_cache_; base::UnorderedMap pipeline_layout_cache_; @@ -560,11 +562,15 @@ class VulkanDevice final : public Device { // while the main thread keeps issuing Create*Pipeline calls. bool pipeline_batch_active_ = false; bool pipeline_workers_quit_ = false; - std::mutex pipeline_queue_mutex_; - std::condition_variable pipeline_queue_cv_; - std::deque> pipeline_jobs_; - std::vector pipeline_workers_; - std::atomic pipeline_batch_failures_{0}; + base::Mutex pipeline_queue_mutex_; + base::ConditionVariable pipeline_queue_cv_; + base::SimpleDeque> pipeline_jobs_; + // base::Thread is not movable; the vector must not relocate live threads. + base::Vector> pipeline_workers_; + // Signalled whenever a batched pipeline leaves kBuilding. + base::Mutex pipeline_ready_mutex_; + base::ConditionVariable pipeline_ready_cv_; + base::Atomic pipeline_batch_failures_{0}; base::UnorderedMap sampler_cache_; friend class VulkanSwapchain; diff --git a/engine/render/vulkan/vk_command_list.cc b/engine/render/vulkan/vk_command_list.cc index 88aa0bd1..8d2255ee 100644 --- a/engine/render/vulkan/vk_command_list.cc +++ b/engine/render/vulkan/vk_command_list.cc @@ -1,4 +1,6 @@ +#include "base/containers/span.h" #include "core/log.h" +#include "core/scalar.h" #include "render/vulkan/vk_backend.h" namespace rx::render::vk { @@ -18,7 +20,7 @@ void VulkanCommandList::BindSet(u32 set_index, BindingSetHandle set) { nullptr); } -void VulkanCommandList::BindTransient(u32 set_index, std::span items) { +void VulkanCommandList::BindTransient(u32 set_index, base::Span items) { VkDescriptorSetAllocateInfo info{.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; info.descriptorPool = transient_pool_; info.descriptorSetCount = 1; @@ -105,7 +107,8 @@ void VulkanCommandList::SetScissor(i32 x, i32 y, u32 width, u32 height) { void VulkanCommandList::BindVertexBuffer(u32 binding, const GpuBuffer& buffer, u64 offset) { VkBuffer handle = Rec(buffer.handle)->buffer; - vkCmdBindVertexBuffers(cmd_, binding, 1, &handle, &offset); + const VkDeviceSize vk_offset = offset; + vkCmdBindVertexBuffers(cmd_, binding, 1, &handle, &vk_offset); } void VulkanCommandList::BindIndexBuffer(const GpuBuffer& buffer, u64 offset, IndexType type) { @@ -196,11 +199,11 @@ VkAccessFlags2 VulkanCommandList::FilterAccess(VkAccessFlags2 access) const { return access & kComputeLegal; } -void VulkanCommandList::TextureBarriers(std::span barriers) { +void VulkanCommandList::TextureBarriers(base::Span barriers) { VkImageMemoryBarrier2 image_barriers[16]; size_t offset = 0; while (offset < barriers.size()) { - size_t batch = std::min(barriers.size() - offset, 16); + size_t batch = rx::Min(barriers.size() - offset, 16); for (size_t i = 0; i < batch; ++i) { const TextureBarrier& src = barriers[offset + i]; const TextureRecord* texture = Rec(src.texture); @@ -242,13 +245,13 @@ void VulkanCommandList::MemoryBarrier(BarrierScope src, BarrierScope dst) { } void VulkanCommandList::CopyBufferToTexture(const GpuBuffer& src, const GpuImage& dst, - std::span regions) { + base::Span regions) { const TextureRecord* texture = Rec(dst.handle); base::Vector copies(regions.size()); for (size_t i = 0; i < regions.size(); ++i) { const BufferTextureCopy& r = regions[i]; - u32 width = r.extent.width ? r.extent.width : std::max(dst.extent.width >> r.mip, 1u); - u32 height = r.extent.height ? r.extent.height : std::max(dst.extent.height >> r.mip, 1u); + u32 width = r.extent.width ? r.extent.width : rx::Max(dst.extent.width >> r.mip, 1u); + u32 height = r.extent.height ? r.extent.height : rx::Max(dst.extent.height >> r.mip, 1u); copies[i] = {}; copies[i].bufferOffset = r.buffer_offset; copies[i].imageSubresource = {texture->aspect, r.mip, r.array_layer, 1}; @@ -264,9 +267,9 @@ void VulkanCommandList::CopyTextureToBuffer(const GpuImage& src, const GpuBuffer const BufferTextureCopy& region) { const TextureRecord* texture = Rec(src.handle); u32 width = region.extent.width ? region.extent.width - : std::max(src.extent.width >> region.mip, 1u); + : rx::Max(src.extent.width >> region.mip, 1u); u32 height = region.extent.height ? region.extent.height - : std::max(src.extent.height >> region.mip, 1u); + : rx::Max(src.extent.height >> region.mip, 1u); VkBufferImageCopy copy{}; copy.bufferOffset = region.buffer_offset; copy.imageSubresource = {texture->aspect, region.mip, region.array_layer, 1}; diff --git a/engine/render/vulkan/vk_device.cc b/engine/render/vulkan/vk_device.cc index 7cf767cf..2ae6e6a6 100644 --- a/engine/render/vulkan/vk_device.cc +++ b/engine/render/vulkan/vk_device.cc @@ -1,14 +1,21 @@ -#include -#include -#include -#include -#include -#include - -#include - +#include +#include + +#include "base/algorithm.h" +#include "base/atomic.h" +#include "base/containers/span.h" +#include "base/containers/vector.h" +#include "base/functional/function.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" +#include "base/threading/lock_guard.h" +#include "core/format.h" +#include "core/file_system.h" #include "core/log.h" #include "core/memory/small_vector.h" +#include "core/scalar.h" #include "render/vulkan/vk_backend.h" #if defined(RX_HAS_DLSS) @@ -38,8 +45,8 @@ bool HasValidationLayer() { vkEnumerateInstanceLayerProperties(&count, nullptr); base::Vector layers(count); vkEnumerateInstanceLayerProperties(&count, layers.data()); - return std::ranges::any_of(layers, [](const auto& layer) { - return std::strcmp(layer.layerName, kValidationLayer) == 0; + return base::AnyOf(layers.begin(), layers.end(), [](const auto& layer) { + return ::strcmp(layer.layerName, kValidationLayer) == 0; }); } @@ -52,8 +59,9 @@ base::Vector DeviceExtensions(VkPhysicalDevice physical) } bool HasExtension(const base::Vector& available, const char* name) { - return std::ranges::any_of( - available, [name](const auto& ext) { return std::strcmp(ext.extensionName, name) == 0; }); + return base::AnyOf(available.begin(), available.end(), [name](const auto& ext) { + return ::strcmp(ext.extensionName, name) == 0; + }); } bool HasInstanceExtension(const char* name) { @@ -117,21 +125,21 @@ constexpr PromotedExtension kPromotedExtensions[] = { // this. It exists because the extension path is otherwise unreachable on any // hardware we have, and dead code that nobody runs is code that does not work. u32 ApiVersionBudget() { - const char* env = std::getenv("RX_VK_MAX_VERSION"); + const char* env = ::getenv("RX_VK_MAX_VERSION"); if (!env) return kMaxApiVersion; char* end = nullptr; - const unsigned long major = std::strtoul(env, &end, 10); + const unsigned long major = ::strtoul(env, &end, 10); if (end == env || *end != '.') { RX_WARN("RX_VK_MAX_VERSION=\"{}\" is not major.minor, ignoring", env); return kMaxApiVersion; } - const unsigned long minor = std::strtoul(end + 1, nullptr, 10); + const unsigned long minor = ::strtoul(end + 1, nullptr, 10); const u32 budget = VK_MAKE_API_VERSION(0, major, minor, 0); if (budget < kMinApiVersion) { RX_WARN("RX_VK_MAX_VERSION={} is below the 1.1 floor, using 1.1", env); return kMinApiVersion; } - return std::min(kMaxApiVersion, budget); + return rx::Min(kMaxApiVersion, budget); } // Promoted extensions `available` still has to supply at `api_version`; the @@ -151,8 +159,8 @@ base::Vector PromotedExtensionsFor( return needed; } -std::string JoinNames(const base::Vector& names) { - std::string joined; +base::String JoinNames(const base::Vector& names) { + base::String joined; for (const char* name : names) { if (!joined.empty()) joined += ", "; joined += name; @@ -309,7 +317,7 @@ VkPhysicalDevice PickPhysicalDevice(VkInstance instance, VkSurfaceKHR surface, u auto available = DeviceExtensions(candidate); if (need_present && !HasExtension(available, VK_KHR_SWAPCHAIN_EXTENSION_NAME)) continue; base::Vector missing; - PromotedExtensionsFor(available, std::min(props.apiVersion, budget), &missing); + PromotedExtensionsFor(available, rx::Min(props.apiVersion, budget), &missing); if (!missing.empty()) { RX_WARN("{}: vulkan {}.{} without {}", props.deviceName, VK_API_VERSION_MAJOR(props.apiVersion), VK_API_VERSION_MINOR(props.apiVersion), @@ -318,7 +326,7 @@ VkPhysicalDevice PickPhysicalDevice(VkInstance instance, VkSurfaceKHR surface, u } base::Vector unmet; - BaselineFeatures(candidate, std::min(props.apiVersion, budget), &unmet); + BaselineFeatures(candidate, rx::Min(props.apiVersion, budget), &unmet); if (!unmet.empty()) { RX_WARN("{}: vulkan {}.{} without {}", props.deviceName, VK_API_VERSION_MAJOR(props.apiVersion), VK_API_VERSION_MINOR(props.apiVersion), @@ -341,7 +349,7 @@ VkShaderModule CreateModule(VkDevice device, const ShaderBlob& blob) { if (!blob.valid() || blob.format != ShaderFormat::kSpirv) return VK_NULL_HANDLE; // The embedded arrays are byte aligned, spirv wants words, so copy. base::Vector words((blob.size + 3) / 4); - std::memcpy(words.data(), blob.data, blob.size); + base::MemCopy(words.data(), blob.data, blob.size); VkShaderModuleCreateInfo info{.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; info.codeSize = words.size() * 4; info.pCode = words.data(); @@ -419,7 +427,7 @@ constexpr VkDescriptorPoolSize kTransientPoolSizes[] = { VkDescriptorPool CreateTransientPool(VkDevice device, bool with_accel) { VkDescriptorPoolCreateInfo info{.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; info.maxSets = kTransientMaxSets; - info.poolSizeCount = static_cast(std::size(kTransientPoolSizes)) - (with_accel ? 0 : 1); + info.poolSizeCount = static_cast((sizeof(kTransientPoolSizes) / sizeof(kTransientPoolSizes[0]))) - (with_accel ? 0 : 1); info.pPoolSizes = kTransientPoolSizes; VkDescriptorPool pool = VK_NULL_HANDLE; vkCreateDescriptorPool(device, &info, nullptr, &pool); @@ -428,20 +436,20 @@ VkDescriptorPool CreateTransientPool(VkDevice device, bool with_accel) { } // namespace -std::unique_ptr VulkanDevice::Create(const DeviceDesc& desc, Window& window) { +base::UniquePointer VulkanDevice::Create(const DeviceDesc& desc, Window& window) { return CreateImpl(desc, &window); } -std::unique_ptr VulkanDevice::CreateOffscreen(const DeviceDesc& desc) { +base::UniquePointer VulkanDevice::CreateOffscreen(const DeviceDesc& desc) { return CreateImpl(desc, nullptr); } -std::unique_ptr VulkanDevice::CreateImpl(const DeviceDesc& desc, Window* window) { - auto device = std::unique_ptr(new VulkanDevice()); +base::UniquePointer VulkanDevice::CreateImpl(const DeviceDesc& desc, Window* window) { + auto device = base::UniquePointer(new VulkanDevice()); // A windowed device needs the platform surface instance extensions; an // offscreen device (window == nullptr) creates no surface and needs none. - std::vector surface_extensions; + base::Vector surface_extensions; if (window) { surface_extensions = window->vulkan_instance_extensions(); if (surface_extensions.empty()) { @@ -462,7 +470,7 @@ std::unique_ptr VulkanDevice::CreateImpl(const DeviceDesc& desc, Window* // Never ask the instance for more than the loader has or the budget allows: // the promoted core entry points are only legal up to this version, and // LoadVulkanEntryPoints picks the KHR spelling for everything above it. - const u32 api_budget = std::min(volkGetInstanceVersion(), ApiVersionBudget()); + const u32 api_budget = rx::Min(volkGetInstanceVersion(), ApiVersionBudget()); if (api_budget < kMaxApiVersion) { RX_INFO("vulkan: capped to {}.{} (loader {}.{})", VK_API_VERSION_MAJOR(api_budget), VK_API_VERSION_MINOR(api_budget), VK_API_VERSION_MAJOR(volkGetInstanceVersion()), @@ -508,9 +516,8 @@ std::unique_ptr VulkanDevice::CreateImpl(const DeviceDesc& desc, Window* NVSDK_NGX_Result_Success) { for (unsigned i = 0; i < ngx_instance_ext_count; ++i) { const char* name = ngx_instance_exts[i]; - if (std::ranges::none_of(instance_extensions, [name](const char* e) { - return std::strcmp(e, name) == 0; - })) { + if (!base::AnyOf(instance_extensions.begin(), instance_extensions.end(), + [name](const char* e) { return ::strcmp(e, name) == 0; })) { instance_extensions.push_back(name); } } @@ -572,7 +579,7 @@ std::unique_ptr VulkanDevice::CreateImpl(const DeviceDesc& desc, Window* device->caps_.api_version = props.apiVersion; // caps_.api_version stays the adapter's own report; this is only what rx // spends of it, and it is what decides core-vs-extension everywhere below. - device->api_version_ = std::min(props.apiVersion, api_budget); + device->api_version_ = rx::Min(props.apiVersion, api_budget); device->caps_.integrated = props.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; device->caps_.max_push_constant_bytes = props.limits.maxPushConstantsSize; // Desktop adapters hand out 256 bytes and up, so a block that only fits here @@ -582,14 +589,14 @@ std::unique_ptr VulkanDevice::CreateImpl(const DeviceDesc& desc, Window* // the adapter: caps_ keeps reporting what the driver said, so nothing else // concludes the hardware is smaller than it is. device->push_constant_budget_ = device->caps_.max_push_constant_bytes; - if (const char* push_limit = std::getenv("RX_MAX_PUSH_CONSTANTS")) { + if (const char* push_limit = ::getenv("RX_MAX_PUSH_CONSTANTS")) { char* end = nullptr; - const unsigned long parsed = std::strtoul(push_limit, &end, 10); + const unsigned long parsed = ::strtoul(push_limit, &end, 10); if (end == push_limit || *end != '\0' || parsed < 4) { RX_WARN("RX_MAX_PUSH_CONSTANTS='{}' is not a byte count; ignoring", push_limit); } else { device->push_constant_budget_ = - std::min(device->push_constant_budget_, static_cast(parsed)); + rx::Min(device->push_constant_budget_, static_cast(parsed)); } } @@ -787,11 +794,11 @@ std::unique_ptr VulkanDevice::CreateImpl(const DeviceDesc& desc, Window* // feature bits, when core / 1.1-1.3, are already enabled by the full-features // query below; extensions carrying their own feature struct would need the app // to also supply it, which this generic path does not chain. - for (const std::string& name : desc.extra_device_extensions) { + for (const base::String& name : desc.extra_device_extensions) { if (!HasExtension(available, name.c_str())) continue; const char* cname = name.c_str(); - if (std::ranges::any_of(device_extensions, - [cname](const char* e) { return std::strcmp(e, cname) == 0; })) { + if (base::AnyOf(device_extensions.begin(), device_extensions.end(), + [cname](const char* e) { return ::strcmp(e, cname) == 0; })) { device->caps_.extra_extensions.push_back(name); continue; } @@ -808,9 +815,9 @@ std::unique_ptr VulkanDevice::CreateImpl(const DeviceDesc& desc, Window* // Core-1.2 bufferDeviceAddress is enabled; the EXT variant NGX asks for // conflicts with it at device creation (VUID 04748). NGX works with the // core feature. - if (!std::strcmp(name, "VK_EXT_buffer_device_address")) continue; - if (std::ranges::any_of(device_extensions, - [name](const char* e) { return std::strcmp(e, name) == 0; })) { + if (!::strcmp(name, "VK_EXT_buffer_device_address")) continue; + if (base::AnyOf(device_extensions.begin(), device_extensions.end(), + [name](const char* e) { return ::strcmp(e, name) == 0; })) { continue; } device_extensions.push_back(name); @@ -844,8 +851,8 @@ std::unique_ptr VulkanDevice::CreateImpl(const DeviceDesc& desc, Window* props2.pNext = &fsr_props; vkGetPhysicalDeviceProperties2(device->physical_device_, &props2); device->caps_.shading_rate_texel = - std::max(fsr_props.minFragmentShadingRateAttachmentTexelSize.width, 1u); - device->caps_.shading_rate_max_size = std::max(fsr_props.maxFragmentSize.width, 1u); + rx::Max(fsr_props.minFragmentShadingRateAttachmentTexelSize.width, 1u); + device->caps_.shading_rate_max_size = rx::Max(fsr_props.maxFragmentSize.width, 1u); } device->caps_.fill_mode_non_solid = features.features.fillModeNonSolid; device->caps_.texture_compression_bc = features.features.textureCompressionBC; @@ -865,7 +872,7 @@ std::unique_ptr VulkanDevice::CreateImpl(const DeviceDesc& desc, Window* props2.pNext = &accel_props; vkGetPhysicalDeviceProperties2(device->physical_device_, &props2); device->caps_.accel_scratch_alignment = - std::max(accel_props.minAccelerationStructureScratchOffsetAlignment, 1); + rx::Max(accel_props.minAccelerationStructureScratchOffsetAlignment, 1); } // Async compute queue: prefer a dedicated compute-only family (its own @@ -882,7 +889,7 @@ std::unique_ptr VulkanDevice::CreateImpl(const DeviceDesc& desc, Window* // RX_ASYNC_DEDICATED=0 forces the same-family fallback (diagnostics; // also the only way to exercise that path on hardware with a dedicated // compute family). - const char* dedicated_env = std::getenv("RX_ASYNC_DEDICATED"); + const char* dedicated_env = ::getenv("RX_ASYNC_DEDICATED"); if (!dedicated_env || dedicated_env[0] != '0') { for (u32 i = 0; i < count; ++i) { const VkQueueFlags flags = families[i].queueFlags; @@ -895,7 +902,7 @@ std::unique_ptr VulkanDevice::CreateImpl(const DeviceDesc& desc, Window* } const bool dedicated_compute = device->compute_family_ != VK_QUEUE_FAMILY_IGNORED; const u32 queue_count = - dedicated_compute ? 1 : std::min(family_queue_count, 2); + dedicated_compute ? 1 : rx::Min(family_queue_count, 2); device->caps_.async_compute = dedicated_compute || queue_count >= 2; if (!dedicated_compute) device->compute_family_ = device->graphics_family_; @@ -938,15 +945,12 @@ std::unique_ptr VulkanDevice::CreateImpl(const DeviceDesc& desc, Window* // happen once per driver/app version. Stale or corrupt blobs are rejected by // the driver, so loading is fire-and-forget. { - const char* home = std::getenv("HOME"); - std::string dir = std::string(home ? home : ".") + "/.cache/rx"; - std::error_code ec; - std::filesystem::create_directories(dir, ec); + const char* home = ::getenv("HOME"); + base::String dir = base::String(home ? home : ".") + "/.cache/rx"; + fs::CreateDirectories(dir); device->pipeline_cache_path_ = dir + "/pipeline.cache"; - std::vector blob; - if (std::ifstream in{device->pipeline_cache_path_, std::ios::binary}) { - blob.assign(std::istreambuf_iterator(in), std::istreambuf_iterator()); - } + base::Vector blob; + fs::ReadFile(device->pipeline_cache_path_, &blob); VkPipelineCacheCreateInfo cache_info{.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO}; cache_info.initialDataSize = blob.size(); cache_info.pInitialData = blob.empty() ? nullptr : blob.data(); @@ -975,10 +979,10 @@ std::unique_ptr VulkanDevice::CreateImpl(const DeviceDesc& desc, Window* } // Two versions when they differ: what the adapter reports, then what rx is // actually driving it at, so a capped run cannot be mistaken for the default. - std::string api = std::format("{}.{}", VK_API_VERSION_MAJOR(props.apiVersion), + base::String api = rx::StrFormat("{}.{}", VK_API_VERSION_MAJOR(props.apiVersion), VK_API_VERSION_MINOR(props.apiVersion)); if (device->api_version_ != props.apiVersion) { - api += std::format(" driven as {}.{}", VK_API_VERSION_MAJOR(device->api_version_), + api += rx::StrFormat(" driven as {}.{}", VK_API_VERSION_MAJOR(device->api_version_), VK_API_VERSION_MINOR(device->api_version_)); } RX_INFO("gpu: {} ({}{}, {} MB, vk {}, rt={} rayquery={} mesh={} vrs={})", @@ -997,11 +1001,11 @@ std::unique_ptr VulkanDevice::CreateImpl(const DeviceDesc& desc, Window* return device; } -std::unique_ptr CreateVulkanDevice(const DeviceDesc& desc, Window& window) { +base::UniquePointer CreateVulkanDevice(const DeviceDesc& desc, Window& window) { return VulkanDevice::Create(desc, window); } -std::unique_ptr CreateVulkanDeviceOffscreen(const DeviceDesc& desc) { +base::UniquePointer CreateVulkanDeviceOffscreen(const DeviceDesc& desc) { return VulkanDevice::CreateOffscreen(desc); } @@ -1051,7 +1055,7 @@ bool VulkanDevice::InitResources() { frame_fence.flags = VK_FENCE_CREATE_SIGNALED_BIT; vkCreateFence(device_, &frame_fence, nullptr, &frame.in_flight); frame.descriptor_pool = CreateTransientPool(device_, caps_.raytracing); - frame.list = std::make_unique(*this, frame.cmd, frame.descriptor_pool); + frame.list = base::MakeUnique(*this, frame.cmd, frame.descriptor_pool); if (caps_.async_compute) { // Segment command buffers (graphics splits) share the frame's pool; the @@ -1064,7 +1068,7 @@ bool VulkanDevice::InitResources() { for (u32 i = 0; i + 1 < FrameRing::kMaxSegments; ++i) { frame.seg_cmds[i] = extra[i]; frame.seg_lists[i] = - std::make_unique(*this, extra[i], frame.descriptor_pool); + base::MakeUnique(*this, extra[i], frame.descriptor_pool); } VkCommandPoolCreateInfo async_pool{.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; async_pool.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; @@ -1078,7 +1082,7 @@ bool VulkanDevice::InitResources() { async_alloc.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; async_alloc.commandBufferCount = 1; vkAllocateCommandBuffers(device_, &async_alloc, &frame.async_cmd); - frame.async_list = std::make_unique( + frame.async_list = base::MakeUnique( *this, frame.async_cmd, frame.descriptor_pool, /*compute_only=*/compute_family_ != graphics_family_); vkCreateSemaphore(device_, &semaphore_info, nullptr, &frame.fork_sem); @@ -1136,11 +1140,9 @@ VulkanDevice::~VulkanDevice() { size_t size = 0; vkGetPipelineCacheData(device_, pipeline_cache_, &size, nullptr); if (size > 0 && !pipeline_cache_path_.empty()) { - std::vector blob(size); + base::Vector blob(size); if (vkGetPipelineCacheData(device_, pipeline_cache_, &size, blob.data()) == VK_SUCCESS) { - if (std::ofstream out{pipeline_cache_path_, std::ios::binary}) { - out.write(blob.data(), static_cast(size)); - } + fs::WriteFile(pipeline_cache_path_, base::Span(blob.data(), size)); } } vkDestroyPipelineCache(device_, pipeline_cache_, nullptr); @@ -1188,7 +1190,7 @@ void VulkanDevice::DestroySurface() { } } -std::unique_ptr VulkanDevice::CreateSwapchain(u32 width, u32 height, bool vsync, +base::UniquePointer VulkanDevice::CreateSwapchain(u32 width, u32 height, bool vsync, bool hdr) { return VulkanSwapchain::Create(*this, width, height, vsync, hdr); } @@ -1245,7 +1247,7 @@ GpuBuffer VulkanDevice::CreateBuffer(u64 size, BufferUsageFlags usage, bool host return {}; } - if (std::getenv("RX_BUFFER_TRACE")) { + if (::getenv("RX_BUFFER_TRACE")) { RX_INFO("buffer created: {:#x} ({} bytes, usage {:#x})", reinterpret_cast(buffer), size, static_cast(usage)); } @@ -1267,7 +1269,7 @@ GpuBuffer VulkanDevice::CreateBufferWithData(ByteSpan data, BufferUsageFlags usa CheckUploadBatchThread("CreateBufferWithData"); GpuBuffer staging = CreateBuffer(data.size(), kBufferUsageTransferSrc, true); if (!staging.mapped) return {}; - std::memcpy(staging.mapped, data.data(), data.size()); + base::MemCopy(staging.mapped, data.data(), data.size()); FlushBuffer(staging, 0, data.size()); GpuBuffer buffer = CreateBuffer(data.size(), usage | kBufferUsageTransferDst, false); @@ -1309,11 +1311,11 @@ void VulkanDevice::InvalidateBuffer(const GpuBuffer& buffer, u64 offset, u64 siz } void VulkanDevice::CheckUploadBatchThread(const char* what) const { - if (std::this_thread::get_id() != upload_batch_thread_) { + if (base::GetCurrentThreadIndex() != upload_batch_thread_) { RX_ERROR("{} called off the device thread; the upload batch is unsynchronized and this " "would corrupt it. Convert on workers, upload on the device thread.", what); - std::abort(); + ::abort(); } } @@ -1341,13 +1343,13 @@ VulkanCommandList* VulkanDevice::EnsureUploadBatchCmd() { return nullptr; } upload_batch_list_ = - std::make_unique(*this, upload_batch_cmd_, immediate_descriptor_pool_); + base::MakeUnique(*this, upload_batch_cmd_, immediate_descriptor_pool_); // Anything retired from here on may be read by this batch, which will carry // the next serial. - upload_park_serial_.store(upload_batch_serial_ + 1, std::memory_order_release); + upload_park_serial_.store(upload_batch_serial_ + 1, base::memory_order_release); } ++upload_batch_records_; // one recorded upload per call; reported if the batch is dropped - return upload_batch_list_.get(); + return upload_batch_list_.Get_UseOnlyIfYouKnowWhatYouareDoing(); } VkFence VulkanDevice::AcquireUploadBatchFence() { @@ -1378,7 +1380,7 @@ void VulkanDevice::ResetPendingUploadBatch() { vkFreeCommandBuffers(device_, immediate_pool_, 1, &upload_batch_cmd_); upload_batch_cmd_ = VK_NULL_HANDLE; } - upload_batch_list_.reset(); + upload_batch_list_.Reset(); for (GpuBuffer& staging : upload_batch_stagings_) DestroyBuffer(staging); upload_batch_stagings_.clear(); upload_batch_staging_bytes_ = 0; @@ -1389,9 +1391,9 @@ void VulkanDevice::DiscardPendingUploadBatch() { // The discarded copies never run, so the projected serial they would have // carried must be removed from both future and already-parked resources. const u64 canceled = upload_batch_serial_ + 1; - upload_park_serial_.store(upload_batch_serial_, std::memory_order_release); + upload_park_serial_.store(upload_batch_serial_, base::memory_order_release); { - std::lock_guard lock(graveyard_mutex_); + base::LockGuard lock(graveyard_mutex_); for (Graveyard& graveyard : graveyard_) { if (graveyard.upload_serial == canceled) graveyard.upload_serial = upload_batch_serial_; } @@ -1477,16 +1479,16 @@ void VulkanDevice::SubmitUploadBatchIfPending() { // retires, which proves this one did too by submission order. ReportDroppedUploadBatch("fallback drain failed"); upload_batch_cmd_ = VK_NULL_HANDLE; - upload_batch_list_.reset(); // wrapper only; the VkCommandBuffer stays alive + upload_batch_list_.Reset(); // wrapper only; the VkCommandBuffer stays alive upload_batch_stagings_.clear(); upload_batch_staging_bytes_ = 0; upload_batch_records_ = 0; - upload_park_serial_.store(upload_batch_serial_, std::memory_order_release); + upload_park_serial_.store(upload_batch_serial_, base::memory_order_release); return; } // The queue drain is the completion proof a fence would normally provide. upload_retired_serial_ = ++upload_batch_serial_; - upload_park_serial_.store(upload_batch_serial_, std::memory_order_release); + upload_park_serial_.store(upload_batch_serial_, base::memory_order_release); ResetPendingUploadBatch(); return; } @@ -1501,19 +1503,19 @@ void VulkanDevice::SubmitUploadBatchIfPending() { UploadBatchInFlight in_flight; in_flight.cmd = upload_batch_cmd_; in_flight.fence = fence; - in_flight.stagings = std::move(upload_batch_stagings_); + in_flight.stagings = base::move(upload_batch_stagings_); in_flight.staging_bytes = upload_batch_staging_bytes_; in_flight.serial = ++upload_batch_serial_; upload_inflight_staging_bytes_ += upload_batch_staging_bytes_; - upload_batches_in_flight_.push_back(std::move(in_flight)); + upload_batches_in_flight_.push_back(base::move(in_flight)); upload_batch_cmd_ = VK_NULL_HANDLE; - upload_batch_list_.reset(); + upload_batch_list_.Reset(); upload_batch_stagings_.clear(); upload_batch_staging_bytes_ = 0; upload_batch_records_ = 0; // Nothing is recording now, so a retire from here on is only covered by the // batch just submitted. - upload_park_serial_.store(upload_batch_serial_, std::memory_order_release); + upload_park_serial_.store(upload_batch_serial_, base::memory_order_release); // Hard cap on outstanding staging memory: without a wait anywhere, a burst // could submit 64 MiB chunks faster than the GPU copies them and grow the @@ -1574,7 +1576,7 @@ void VulkanDevice::ParkBatchStaging(GpuBuffer& buffer) { if (upload_batch_staging_bytes_ >= kStagingBudget) SubmitUploadBatchIfPending(); } -void VulkanDevice::RecordUpload(const std::function& record) { +void VulkanDevice::RecordUpload(const base::Function& record) { CheckUploadBatchThread("RecordUpload"); // Batched: record into the shared batch command buffer (submitted at flush). // Otherwise a blocking ImmediateSubmit, exactly as the caller would have done. @@ -1600,13 +1602,13 @@ void VulkanDevice::DestroyBuffer(GpuBuffer& buffer) { // Records the upload batch that may still be reading whatever is being parked. // Call with graveyard_mutex_ held. void VulkanDevice::NoteGraveyardUploadSerial(u32 slot) { - const u64 serial = upload_park_serial_.load(std::memory_order_acquire); + const u64 serial = upload_park_serial_.load(base::memory_order_acquire); if (serial > graveyard_[slot].upload_serial) graveyard_[slot].upload_serial = serial; } void VulkanDevice::DestroyBufferDeferred(GpuBuffer& buffer) { if (BufferRecord* record = Rec(buffer.handle)) { - std::lock_guard lock(graveyard_mutex_); + base::LockGuard lock(graveyard_mutex_); const u32 slot = current_slot_; graveyard_[slot].buffers.push_back(record); NoteGraveyardUploadSerial(slot); @@ -1616,7 +1618,7 @@ void VulkanDevice::DestroyBufferDeferred(GpuBuffer& buffer) { void VulkanDevice::DestroyImageDeferred(GpuImage& image) { if (TextureRecord* record = Rec(image.handle)) { - std::lock_guard lock(graveyard_mutex_); + base::LockGuard lock(graveyard_mutex_); const u32 slot = current_slot_; graveyard_[slot].images.push_back(record); NoteGraveyardUploadSerial(slot); @@ -1626,7 +1628,7 @@ void VulkanDevice::DestroyImageDeferred(GpuImage& image) { void VulkanDevice::DestroyAccelStructDeferred(AccelStructHandle accel) { if (AccelStructRecord* record = Rec(accel)) { - std::lock_guard lock(graveyard_mutex_); + base::LockGuard lock(graveyard_mutex_); const u32 slot = current_slot_; graveyard_[slot].accels.push_back(record); NoteGraveyardUploadSerial(slot); @@ -1641,9 +1643,9 @@ void VulkanDevice::DrainGraveyard(u32 slot) { RetireCompletedUploadBatches(); Graveyard drained; { - std::lock_guard lock(graveyard_mutex_); + base::LockGuard lock(graveyard_mutex_); if (graveyard_[slot].upload_serial > upload_retired_serial_) return; - std::swap(drained, graveyard_[slot]); + base::Swap(drained, graveyard_[slot]); } for (BufferRecord* record : drained.buffers) FreeBufferRecord(record); for (TextureRecord* record : drained.images) FreeTextureRecord(record); @@ -1921,7 +1923,7 @@ SamplerHandle VulkanDevice::GetSampler(const SamplerDesc& desc) { info.addressModeW = address(desc.address_w); if (desc.max_anisotropy > 1.0f && caps_.max_anisotropy > 1.0f) { info.anisotropyEnable = VK_TRUE; - info.maxAnisotropy = std::min(desc.max_anisotropy, caps_.max_anisotropy); + info.maxAnisotropy = rx::Min(desc.max_anisotropy, caps_.max_anisotropy); } info.minLod = desc.min_lod; info.maxLod = desc.max_lod; @@ -1947,7 +1949,7 @@ SamplerHandle VulkanDevice::GetSampler(const SamplerDesc& desc) { // bindings VkDescriptorSetLayout VulkanDevice::GetOrCreateSetLayout(const BindingLayoutDesc& desc) { - std::lock_guard lock(layout_cache_mutex_); + base::LockGuard lock(layout_cache_mutex_); u64 key = HashLayoutDesc(desc); if (VkDescriptorSetLayout* cached = set_layout_cache_.find(key)) return *cached; @@ -1996,7 +1998,7 @@ VkDescriptorSetLayout VulkanDevice::GetOrCreateSetLayout(const BindingLayoutDesc } VkPipelineLayout VulkanDevice::GetOrCreatePipelineLayout( - std::span sets, VkShaderStageFlags push_stages, u32 push_size, + base::Span sets, VkShaderStageFlags push_stages, u32 push_size, const char* debug_name) { // The one place a push block reaches the driver, so the one place worth // checking it. Over the limit the layout is invalid and the pass would be @@ -2010,7 +2012,7 @@ VkPipelineLayout VulkanDevice::GetOrCreatePipelineLayout( return VK_NULL_HANDLE; } - std::lock_guard lock(layout_cache_mutex_); + base::LockGuard lock(layout_cache_mutex_); u64 key = HashBytes(sets.data(), sets.size() * sizeof(VkDescriptorSetLayout)); key = HashBytes(&push_stages, sizeof(push_stages), key); key = HashBytes(&push_size, sizeof(push_size), key); @@ -2050,12 +2052,12 @@ BindingSetHandle VulkanDevice::CreateBindingSet(BindingLayoutHandle layout, u32 // Dedicated pool sized exactly for this set; persistent sets are few. base::Vector sizes; for (const BindingSlot& slot : layout_record->desc.slots) { - sizes.push_back({ToVkDescriptorType(slot.type), std::max(slot.count, 1u)}); + sizes.push_back({ToVkDescriptorType(slot.type), rx::Max(slot.count, 1u)}); } VkDescriptorPoolCreateInfo pool_info{.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; if (layout_record->desc.update_after_bind || - std::ranges::any_of(layout_record->desc.slots, - [](const BindingSlot& s) { return s.variable_count; })) { + base::AnyOf(layout_record->desc.slots.begin(), layout_record->desc.slots.end(), + [](const BindingSlot& s) { return s.variable_count; })) { pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT; } pool_info.maxSets = 1; @@ -2089,7 +2091,7 @@ void VulkanDevice::DestroyBindingSet(BindingSetHandle set) { } } -void VulkanDevice::WriteDescriptors(VkDescriptorSet set, std::span items) { +void VulkanDevice::WriteDescriptors(VkDescriptorSet set, base::Span items) { // Hot path: dozens of BindTransient calls per frame, almost always with a // handful of items; inline storage keeps this allocation-free. The write // entries point into the side arrays, which is safe because each is sized @@ -2158,7 +2160,7 @@ void VulkanDevice::WriteDescriptors(VkDescriptorSet set, std::span(writes.size()), writes.data(), 0, nullptr); } -void VulkanDevice::UpdateBindingSet(BindingSetHandle set, std::span items) { +void VulkanDevice::UpdateBindingSet(BindingSetHandle set, base::Span items) { WriteDescriptors(Rec(set)->set, items); } @@ -2192,23 +2194,23 @@ void VulkanDevice::BeginPipelineBatch() { if (pipeline_batch_active_) return; pipeline_batch_active_ = true; pipeline_workers_quit_ = false; - pipeline_batch_failures_.store(0, std::memory_order_relaxed); - const u32 workers = std::clamp(std::thread::hardware_concurrency(), 2u, 16u); + pipeline_batch_failures_.store(0, base::memory_order_relaxed); + const u32 workers = rx::Clamp(base::GetProcessorCount(), 2u, 16u); for (u32 i = 0; i < workers; ++i) { - pipeline_workers_.emplace_back([this] { + pipeline_workers_.push_back(base::MakeUnique("rx-vk-pipelines", [this] { for (;;) { - std::function job; + base::Function job; { - std::unique_lock lock(pipeline_queue_mutex_); - pipeline_queue_cv_.wait( + base::UniqueLock lock(pipeline_queue_mutex_); + pipeline_queue_cv_.Wait( lock, [this] { return pipeline_workers_quit_ || !pipeline_jobs_.empty(); }); if (pipeline_jobs_.empty()) return; - job = std::move(pipeline_jobs_.front()); + job = base::move(pipeline_jobs_.front()); pipeline_jobs_.pop_front(); } job(); } - }); + }, /*start_now=*/true)); } } @@ -2216,28 +2218,31 @@ bool VulkanDevice::EndPipelineBatch() { if (!pipeline_batch_active_) return true; pipeline_batch_active_ = false; { - std::lock_guard lock(pipeline_queue_mutex_); + base::LockGuard lock(pipeline_queue_mutex_); pipeline_workers_quit_ = true; } - pipeline_queue_cv_.notify_all(); - for (std::thread& worker : pipeline_workers_) worker.join(); + pipeline_queue_cv_.NotifyAll(); + for (auto& worker : pipeline_workers_) worker->Join(); pipeline_workers_.clear(); - return pipeline_batch_failures_.load(std::memory_order_relaxed) == 0; + return pipeline_batch_failures_.load(base::memory_order_relaxed) == 0; } -void VulkanDevice::EnqueuePipelineJob(std::function job) { +void VulkanDevice::EnqueuePipelineJob(base::Function job) { { - std::lock_guard lock(pipeline_queue_mutex_); - pipeline_jobs_.push_back(std::move(job)); + base::LockGuard lock(pipeline_queue_mutex_); + pipeline_jobs_.push_back(base::move(job)); } - pipeline_queue_cv_.notify_one(); + pipeline_queue_cv_.NotifyOne(); } bool VulkanDevice::WaitPipelineReady(PipelineRecord* record) { - u32 state = record->build_state.load(std::memory_order_acquire); - while (state == PipelineRecord::kBuilding) { - record->build_state.wait(PipelineRecord::kBuilding, std::memory_order_acquire); - state = record->build_state.load(std::memory_order_acquire); + u32 state = record->build_state.load(base::memory_order_acquire); + if (state == PipelineRecord::kBuilding) { + base::UniqueLock lock(pipeline_ready_mutex_); + pipeline_ready_cv_.Wait(lock, [&] { + state = record->build_state.load(base::memory_order_acquire); + return state != PipelineRecord::kBuilding; + }); } return state == PipelineRecord::kBuilt; } @@ -2248,7 +2253,7 @@ bool VulkanDevice::BuildComputePipeline(const ComputePipelineDesc& desc, if (!ResolveSetLayouts(*this, desc.sets, kShaderStageCompute, &set_layouts)) return false; VkPipelineLayout layout = GetOrCreatePipelineLayout( - {set_layouts.data(), set_layouts.size()}, VK_SHADER_STAGE_COMPUTE_BIT, + base::Span(set_layouts.data(), set_layouts.size()), VK_SHADER_STAGE_COMPUTE_BIT, desc.push_constant_size, desc.debug_name); if (layout == VK_NULL_HANDLE) return false; @@ -2279,7 +2284,7 @@ bool VulkanDevice::BuildComputePipeline(const ComputePipelineDesc& desc, record->bind_point = VK_PIPELINE_BIND_POINT_COMPUTE; record->push_stages = VK_SHADER_STAGE_COMPUTE_BIT; record->push_size = desc.push_constant_size; - record->set_layouts = std::move(set_layouts); + record->set_layouts = base::move(set_layouts); return true; } @@ -2301,7 +2306,7 @@ bool VulkanDevice::BuildGraphicsPipeline(const GraphicsPipelineDesc& desc, VkShaderStageFlags push_stages = ToVkStages(all_stages); VkPipelineLayout layout = GetOrCreatePipelineLayout( - {set_layouts.data(), set_layouts.size()}, push_stages, desc.push_constant_size, + base::Span(set_layouts.data(), set_layouts.size()), push_stages, desc.push_constant_size, desc.debug_name); if (layout == VK_NULL_HANDLE) return false; @@ -2488,7 +2493,7 @@ bool VulkanDevice::BuildGraphicsPipeline(const GraphicsPipelineDesc& desc, record->bind_point = VK_PIPELINE_BIND_POINT_GRAPHICS; record->push_stages = push_stages; record->push_size = desc.push_constant_size; - record->set_layouts = std::move(set_layouts); + record->set_layouts = base::move(set_layouts); return true; } @@ -2688,7 +2693,7 @@ bool VulkanDevice::GetTimestamps(TimestampPoolHandle pool, u32 first, u32 count, // recording & submission -void VulkanDevice::ImmediateSubmit(const std::function& record) { +void VulkanDevice::ImmediateSubmit(const base::Function& record) { // A buffer batched but not yet submitted must land before this submit's work, // which may read it (e.g. a BLAS build over just-created vertex/index buffers). SubmitUploadBatchIfPending(); @@ -2753,7 +2758,7 @@ bool VulkanDevice::ReadbackImage(const GpuImage& image, ResourceState current, v }); vmaInvalidateAllocation(allocator_, allocation, 0, VK_WHOLE_SIZE); - std::memcpy(out, mapped.pMappedData, needed); + base::MemCopy(out, mapped.pMappedData, needed); vmaDestroyBuffer(allocator_, buffer, allocation); return true; } @@ -2771,7 +2776,7 @@ CommandList* VulkanDevice::BeginFrame(u32 slot) { if (frame.async_pool) vkResetCommandPool(device_, frame.async_pool, 0); vkResetDescriptorPool(device_, frame.descriptor_pool, 0); { - std::lock_guard lock(graveyard_mutex_); + base::LockGuard lock(graveyard_mutex_); current_slot_ = slot; } frame.active_segment = 0; @@ -2781,7 +2786,7 @@ CommandList* VulkanDevice::BeginFrame(u32 slot) { VkCommandBufferBeginInfo begin{.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(frame.cmd, &begin); - return frame.list.get(); + return frame.list.Get_UseOnlyIfYouKnowWhatYouareDoing(); } CommandList* VulkanDevice::SplitFrame(CommandList* cmd, bool signal_fork) { @@ -2820,7 +2825,7 @@ CommandList* VulkanDevice::SplitFrame(CommandList* cmd, bool signal_fork) { VkCommandBufferBeginInfo begin{.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(next, &begin); - return frame.seg_lists[frame.active_segment - 1].get(); + return frame.seg_lists[frame.active_segment - 1].Get_UseOnlyIfYouKnowWhatYouareDoing(); } CommandList* VulkanDevice::BeginAsync() { @@ -2829,7 +2834,7 @@ CommandList* VulkanDevice::BeginAsync() { VkCommandBufferBeginInfo begin{.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(frame.async_cmd, &begin); - return frame.async_list.get(); + return frame.async_list.Get_UseOnlyIfYouKnowWhatYouareDoing(); } void VulkanDevice::SubmitAsync(CommandList*) { @@ -2883,9 +2888,9 @@ PresentResult VulkanDevice::SubmitFrame(CommandList* cmd, Swapchain& swapchain, auto& vk_swapchain = static_cast(swapchain); FrameRing* frame = nullptr; for (FrameRing& candidate : frames_) { - if (candidate.list.get() == cmd) frame = &candidate; + if (candidate.list.Get_UseOnlyIfYouKnowWhatYouareDoing() == cmd) frame = &candidate; for (const auto& seg : candidate.seg_lists) { - if (seg && seg.get() == cmd) frame = &candidate; + if (seg && seg.Get_UseOnlyIfYouKnowWhatYouareDoing() == cmd) frame = &candidate; } } if (!frame) return PresentResult::kFailed; @@ -2946,9 +2951,9 @@ void VulkanDevice::SubmitFrame(CommandList* cmd) { SubmitUploadBatchIfPending(); // see the presenting overload FrameRing* frame = nullptr; for (FrameRing& candidate : frames_) { - if (candidate.list.get() == cmd) frame = &candidate; + if (candidate.list.Get_UseOnlyIfYouKnowWhatYouareDoing() == cmd) frame = &candidate; for (const auto& seg : candidate.seg_lists) { - if (seg && seg.get() == cmd) frame = &candidate; + if (seg && seg.Get_UseOnlyIfYouKnowWhatYouareDoing() == cmd) frame = &candidate; } } if (!frame) return; @@ -2982,9 +2987,9 @@ PresentResult VulkanDevice::SubmitFrameGen(CommandList* cmd, Swapchain& swapchai auto& vk_swapchain = static_cast(swapchain); FrameRing* frame = nullptr; for (FrameRing& candidate : frames_) { - if (candidate.list.get() == cmd) frame = &candidate; + if (candidate.list.Get_UseOnlyIfYouKnowWhatYouareDoing() == cmd) frame = &candidate; for (const auto& seg : candidate.seg_lists) { - if (seg && seg.get() == cmd) frame = &candidate; + if (seg && seg.Get_UseOnlyIfYouKnowWhatYouareDoing() == cmd) frame = &candidate; } } if (!frame) return PresentResult::kFailed; diff --git a/engine/render/vulkan/vk_swapchain.cc b/engine/render/vulkan/vk_swapchain.cc index 4fd2fdb3..fb94572e 100644 --- a/engine/render/vulkan/vk_swapchain.cc +++ b/engine/render/vulkan/vk_swapchain.cc @@ -1,13 +1,14 @@ -#include +#include "base/memory/unique_pointer.h" #include "core/log.h" +#include "core/scalar.h" #include "render/vulkan/vk_backend.h" namespace rx::render::vk { -std::unique_ptr VulkanSwapchain::Create(VulkanDevice& device, u32 width, +base::UniquePointer VulkanSwapchain::Create(VulkanDevice& device, u32 width, u32 height, bool vsync, bool hdr) { - auto swapchain = std::unique_ptr(new VulkanSwapchain(device)); + auto swapchain = base::UniquePointer(new VulkanSwapchain(device)); if (!swapchain->Init(width, height, vsync, hdr)) return nullptr; return swapchain; } @@ -85,15 +86,15 @@ bool VulkanSwapchain::Init(u32 width, u32 height, bool vsync, bool hdr) { if (caps.currentExtent.width != 0xffffffff) { extent_ = {caps.currentExtent.width, caps.currentExtent.height}; } else { - extent_.width = std::clamp(width, caps.minImageExtent.width, caps.maxImageExtent.width); - extent_.height = std::clamp(height, caps.minImageExtent.height, caps.maxImageExtent.height); + extent_.width = rx::Clamp(width, caps.minImageExtent.width, caps.maxImageExtent.width); + extent_.height = rx::Clamp(height, caps.minImageExtent.height, caps.maxImageExtent.height); } // +2 over the minimum: frame generation acquires two images per engine frame // (interpolated + real); the extra slack keeps the second acquire's // forward-progress guarantee (outstanding acquires <= count - min). u32 image_count = caps.minImageCount + 2; - if (caps.maxImageCount > 0) image_count = std::min(image_count, caps.maxImageCount); + if (caps.maxImageCount > 0) image_count = rx::Min(image_count, caps.maxImageCount); // Desktop surfaces support OPAQUE; Android surfaces often only offer INHERIT. // Pick the first supported value rather than assuming OPAQUE. diff --git a/engine/render/vulkan/vma_config.h b/engine/render/vulkan/vma_config.h new file mode 100644 index 00000000..1209234a --- /dev/null +++ b/engine/render/vulkan/vma_config.h @@ -0,0 +1,56 @@ +#ifndef RX_RENDER_VULKAN_VMA_CONFIG_H_ +#define RX_RENDER_VULKAN_VMA_CONFIG_H_ + +// VMA's configuration hooks, pointed at base and rx instead of the STL pieces +// VMA falls back to (std::mutex, std::shared_mutex, std::atomic, std::sort, +// std::min/max). Included through VMA_CONFIGURATION_USER_INCLUDES_H, which +// also keeps VMA from pulling and for its defaults. Only +// the implementation half of vk_mem_alloc.h uses these, so vma_impl.cc alone +// includes this. + +#include + +#include "base/atomic.h" +#include "base/threading/mutex.h" +#include "core/scalar.h" +#include "core/sort.h" + +class RxVmaMutex { + public: + RxVmaMutex() = default; + RxVmaMutex(const RxVmaMutex&) = delete; + RxVmaMutex& operator=(const RxVmaMutex&) = delete; + + void Lock() { mutex_.lock(); } + void Unlock() { mutex_.unlock(); } + bool TryLock() { return mutex_.try_lock(); } + + private: + base::Mutex mutex_; +}; + +class RxVmaRWMutex { + public: + void LockRead() { mutex_.lock_shared(); } + void UnlockRead() { mutex_.unlock_shared(); } + bool TryLockRead() { return mutex_.try_lock_shared(); } + void LockWrite() { mutex_.lock(); } + void UnlockWrite() { mutex_.unlock(); } + bool TryLockWrite() { return mutex_.try_lock(); } + + private: + base::SharedMutex mutex_; +}; + +#define VMA_MUTEX RxVmaMutex +#define VMA_RW_MUTEX RxVmaRWMutex +#define VMA_ATOMIC_UINT32 base::Atomic +#define VMA_ATOMIC_UINT64 base::Atomic +#define VMA_MIN(v1, v2) (rx::Min((v1), (v2))) +#define VMA_MAX(v1, v2) (rx::Max((v1), (v2))) +// VMA sorts only when defragmenting (block order by free size), which rx does +// not do; a stable sort keeps the result a function of the input if it ever +// does. +#define VMA_SORT(beg, end, cmp) (rx::StableSort((beg), (end), (cmp))) + +#endif // RX_RENDER_VULKAN_VMA_CONFIG_H_ diff --git a/engine/render/vulkan/vma_impl.cc b/engine/render/vulkan/vma_impl.cc index 6da8cc61..fe8a56fe 100644 --- a/engine/render/vulkan/vma_impl.cc +++ b/engine/render/vulkan/vma_impl.cc @@ -5,4 +5,5 @@ #define VMA_IMPLEMENTATION #define VMA_STATIC_VULKAN_FUNCTIONS 0 #define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 +#define VMA_CONFIGURATION_USER_INCLUDES_H "render/vulkan/vma_config.h" #include diff --git a/engine/render2d/camera2d.h b/engine/render2d/camera2d.h index c1cd5341..11ef0181 100644 --- a/engine/render2d/camera2d.h +++ b/engine/render2d/camera2d.h @@ -1,7 +1,7 @@ #ifndef RX_RENDER2D_CAMERA2D_H_ #define RX_RENDER2D_CAMERA2D_H_ -#include +#include #include "core/math.h" #include "render2d/types2d.h" @@ -30,7 +30,7 @@ class Camera2D { void MoveBy(Vec2 delta) { center_ += delta; } // Pixels per world unit. Clamped positive so the mapping stays invertible. void SetZoom(f32 zoom) { - zoom_ = std::isfinite(zoom) && zoom > 1e-3f ? zoom : 1e-3f; + zoom_ = ::isfinite(zoom) && zoom > 1e-3f ? zoom : 1e-3f; } Vec2 center() const { return center_; } diff --git a/engine/render2d/iso.h b/engine/render2d/iso.h index fec1f745..4422b60d 100644 --- a/engine/render2d/iso.h +++ b/engine/render2d/iso.h @@ -1,7 +1,7 @@ #ifndef RX_RENDER2D_ISO_H_ #define RX_RENDER2D_ISO_H_ -#include +#include #include "render2d/types2d.h" @@ -49,7 +49,7 @@ struct IsoGrid { // Nearest integer cell for a world point (floor of the fractional map). Vec2i WorldToCell(Vec2 world) const { Vec2 f = WorldToCellF(world); - return {static_cast(std::floor(f.x)), static_cast(std::floor(f.y))}; + return {static_cast(::floor(f.x)), static_cast(::floor(f.y))}; } // Painter's-order sort key: cells with a larger (col+row) are nearer the diff --git a/engine/render2d/sprite_renderer.cc b/engine/render2d/sprite_renderer.cc index b80c0246..7adfa8da 100644 --- a/engine/render2d/sprite_renderer.cc +++ b/engine/render2d/sprite_renderer.cc @@ -1,19 +1,21 @@ #include "render2d/sprite_renderer.h" -#include -#include -#include -#include +#include +#include #include "core/log.h" #include "render/rhi/bindings.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "core/scalar.h" #include "shaders/sprite_vs_hlsl.h" #include "shaders/sprite_ps_hlsl.h" #include "shaders/light2d_vs_hlsl.h" #include "shaders/light2d_ps_hlsl.h" #include "shaders/composite_vs_hlsl.h" #include "shaders/composite_ps_hlsl.h" +#include "core/sort.h" namespace rx::render2d { @@ -161,14 +163,14 @@ TextureId SpriteRenderer::CreateTexture(u32 width, u32 height, const u8* rgba, device_->DestroyImage(image); return 0; } - std::memcpy(staging.mapped, rgba, bytes); + base::MemCopy(staging.mapped, rgba, bytes); device_->FlushBuffer(staging, 0, bytes); device_->ImmediateSubmit([&](CommandList& cmd) { cmd.Barrier(Transition(image, ResourceState::kUndefined, ResourceState::kCopyDst)); BufferTextureCopy region{.buffer_offset = 0, .mip = 0, .array_layer = 0, .extent = {width, height}}; - cmd.CopyBufferToTexture(staging, image, {®ion, 1}); + cmd.CopyBufferToTexture(staging, image, base::Span(®ion, 1)); cmd.Barrier(Transition(image, ResourceState::kCopyDst, ResourceState::kShaderReadFragment)); }); device_->DestroyBuffer(staging); @@ -220,7 +222,7 @@ void SpriteRenderer::DrawSprite(const SpriteParams& s) { q.texture = (s.texture != 0 && s.texture < textures_.size() && textures_[s.texture].valid) ? s.texture : kWhiteTexture; - q.sort_key = std::isfinite(s.sort_key) ? s.sort_key : 0.0f; + q.sort_key = ::isfinite(s.sort_key) ? s.sort_key : 0.0f; queue_.push_back(q); } @@ -236,21 +238,21 @@ void SpriteRenderer::DrawQuad(Vec2 pos, Vec2 size, Color color, f32 sort_key) { void SpriteRenderer::DrawTileMap(TextureId tileset_texture, const TileMap& map, const Camera2D& camera) { - if (!(map.tile_size > 0.0f) || !std::isfinite(map.tile_size)) return; + if (!(map.tile_size > 0.0f) || !::isfinite(map.tile_size)) return; Rect view = camera.VisibleRect(); for (const TileLayer& layer : map.layers) { // Parallax shifts a distant layer so it scrolls slower than the camera. Vec2 offset = camera.center() * (1.0f - layer.parallax); // Visible tile range in this layer's own space (undo the parallax offset). f32 x0 = view.x - offset.x, y0 = view.y - offset.y; - i32 tx0 = static_cast(std::floor(x0 / map.tile_size)) - 1; - i32 ty0 = static_cast(std::floor(y0 / map.tile_size)) - 1; - i32 tx1 = static_cast(std::floor((x0 + view.w) / map.tile_size)) + 1; - i32 ty1 = static_cast(std::floor((y0 + view.h) / map.tile_size)) + 1; - tx0 = std::max(tx0, 0); - ty0 = std::max(ty0, 0); - tx1 = std::min(tx1, static_cast(layer.width) - 1); - ty1 = std::min(ty1, static_cast(layer.height) - 1); + i32 tx0 = static_cast(::floor(x0 / map.tile_size)) - 1; + i32 ty0 = static_cast(::floor(y0 / map.tile_size)) - 1; + i32 tx1 = static_cast(::floor((x0 + view.w) / map.tile_size)) + 1; + i32 ty1 = static_cast(::floor((y0 + view.h) / map.tile_size)) + 1; + tx0 = rx::Max(tx0, 0); + ty0 = rx::Max(ty0, 0); + tx1 = rx::Min(tx1, static_cast(layer.width) - 1); + ty1 = rx::Min(ty1, static_cast(layer.height) - 1); for (i32 ty = ty0; ty <= ty1; ++ty) { for (i32 tx = tx0; tx <= tx1; ++tx) { i32 id = layer.At(tx, ty); @@ -289,8 +291,8 @@ void SpriteRenderer::AddLight(const Light2D& l) { void SpriteRenderer::InstallInto(render::FrameView& view) { if (!ready_) return; - auto previous = std::move(view.hdr_overlay); - view.hdr_overlay = [this, previous = std::move(previous)](const HdrOverlayContext& ctx) { + auto previous = base::move(view.hdr_overlay); + view.hdr_overlay = [this, previous = base::move(previous)](const HdrOverlayContext& ctx) { if (previous) { previous(ctx); ctx.cmd->Barrier(Transition(*ctx.color, ResourceState::kColorTarget, @@ -302,7 +304,7 @@ void SpriteRenderer::InstallInto(render::FrameView& view) { bool SpriteRenderer::EnsureSprites(FrameSlot& slot, u32 count) { if (slot.sprite_cap >= count && slot.sprites.mapped) return true; - u32 cap = std::max(count, 256); + u32 cap = rx::Max(count, 256); cap = cap + cap / 2; // headroom if (slot.sprites) device_->DestroyBufferDeferred(slot.sprites); slot.sprites = device_->CreateBuffer(static_cast(cap) * sizeof(GpuSprite), @@ -313,7 +315,7 @@ bool SpriteRenderer::EnsureSprites(FrameSlot& slot, u32 count) { bool SpriteRenderer::EnsureLights(FrameSlot& slot, u32 count) { if (slot.light_cap >= count && slot.lights.mapped) return true; - u32 cap = std::max(count, 64); + u32 cap = rx::Max(count, 64); cap = cap + cap / 2; if (slot.lights) device_->DestroyBufferDeferred(slot.lights); slot.lights = device_->CreateBuffer(static_cast(cap) * sizeof(GpuLight), @@ -338,10 +340,10 @@ u32 SpriteRenderer::UploadSprites(FrameSlot& slot) { if (count == 0) return 0; // Stable painter's order. DrawSpriteRuns still batches adjacent sprites that // use the same atlas without changing the caller's order for equal keys. - std::stable_sort(queue_.data(), queue_.data() + count, - [](const QueuedSprite& a, const QueuedSprite& b) { - return a.sort_key < b.sort_key; - }); + rx::StableSort(queue_.data(), queue_.data() + count, + [](const QueuedSprite& a, const QueuedSprite& b) { + return a.sort_key < b.sort_key; + }); if (!EnsureSprites(slot, count)) return 0; GpuSprite* dst = static_cast(slot.sprites.mapped); for (u32 i = 0; i < count; ++i) dst[i] = queue_[i].gpu; @@ -384,7 +386,7 @@ void SpriteRenderer::RecordUnlit(const HdrOverlayContext& ctx, FrameSlot& slot) color.clear[2] = scene_clear_.b; color.clear[3] = scene_clear_.a; } - ctx.cmd->BeginRendering({.extent = ctx.extent, .colors = {&color, 1}}); + ctx.cmd->BeginRendering({.extent = ctx.extent, .colors = base::Span(&color, 1)}); if (count > 0) DrawSpriteRuns(*ctx.cmd, slot, count); ctx.cmd->EndRendering(); } @@ -397,7 +399,7 @@ void SpriteRenderer::RecordLit(const HdrOverlayContext& ctx, FrameSlot& slot) { u32 sprite_count = UploadSprites(slot); u32 light_count = static_cast(lights_.size()); if (light_count > 0 && EnsureLights(slot, light_count)) { - std::memcpy(slot.lights.mapped, lights_.data(), light_count * sizeof(GpuLight)); + base::MemCopy(slot.lights.mapped, lights_.data(), light_count * sizeof(GpuLight)); device_->FlushBuffer(slot.lights, 0, static_cast(light_count) * sizeof(GpuLight)); } else { light_count = 0; @@ -407,7 +409,7 @@ void SpriteRenderer::RecordLit(const HdrOverlayContext& ctx, FrameSlot& slot) { ctx.cmd->Barrier(Transition(slot.albedo, ResourceState::kUndefined, ResourceState::kColorTarget)); { ColorAttachment a{.view = slot.albedo.view, .load = LoadOp::kClear, .clear = {0, 0, 0, 0}}; - ctx.cmd->BeginRendering({.extent = ctx.extent, .colors = {&a, 1}}); + ctx.cmd->BeginRendering({.extent = ctx.extent, .colors = base::Span(&a, 1)}); if (sprite_count > 0) DrawSpriteRuns(*ctx.cmd, slot, sprite_count); ctx.cmd->EndRendering(); } @@ -421,7 +423,7 @@ void SpriteRenderer::RecordLit(const HdrOverlayContext& ctx, FrameSlot& slot) { ColorAttachment lc{.view = slot.light_target.view, .load = LoadOp::kClear, .clear = {ambient_.r, ambient_.g, ambient_.b, 1.0f}}; - ctx.cmd->BeginRendering({.extent = ctx.extent, .colors = {&lc, 1}}); + ctx.cmd->BeginRendering({.extent = ctx.extent, .colors = base::Span(&lc, 1)}); if (light_count > 0) { ctx.cmd->BindPipeline(light_pipeline_); ctx.cmd->Push(view_proj_); @@ -442,7 +444,7 @@ void SpriteRenderer::RecordLit(const HdrOverlayContext& ctx, FrameSlot& slot) { sc.clear[2] = scene_clear_.b; sc.clear[3] = scene_clear_.a; } - ctx.cmd->BeginRendering({.extent = ctx.extent, .colors = {&sc, 1}}); + ctx.cmd->BeginRendering({.extent = ctx.extent, .colors = base::Span(&sc, 1)}); ctx.cmd->BindPipeline(composite_pipeline_); ctx.cmd->BindTransient(0, {Bind::Combined(0, slot.albedo.view, sampler_), Bind::Combined(1, slot.light_target.view, sampler_)}); diff --git a/engine/render2d/tile_map.cc b/engine/render2d/tile_map.cc index a6385700..1014357b 100644 --- a/engine/render2d/tile_map.cc +++ b/engine/render2d/tile_map.cc @@ -1,20 +1,21 @@ +#include "base/memory/move.h" +#include "base/numeric_limits.h" #include "render2d/tile_map.h" -#include -#include +#include namespace rx::render2d { namespace { bool TryWorldToTile(f32 tile_size, Vec2 world, Vec2i* tile) { - if (!(tile_size > 0.0f) || !std::isfinite(tile_size) || - !std::isfinite(world.x) || !std::isfinite(world.y)) { + if (!(tile_size > 0.0f) || !::isfinite(tile_size) || + !::isfinite(world.x) || !::isfinite(world.y)) { return false; } - const f64 x = std::floor(static_cast(world.x) / tile_size); - const f64 y = std::floor(static_cast(world.y) / tile_size); - constexpr f64 kMin = std::numeric_limits::min(); - constexpr f64 kMax = std::numeric_limits::max(); + const f64 x = ::floor(static_cast(world.x) / tile_size); + const f64 y = ::floor(static_cast(world.y) / tile_size); + constexpr f64 kMin = base::MinMax::min(); + constexpr f64 kMax = base::MinMax::max(); if (x < kMin || x > kMax || y < kMin || y > kMax) return false; *tile = {static_cast(x), static_cast(y)}; return true; @@ -30,7 +31,7 @@ TileLayer& TileMap::AddLayer(u32 w, u32 h, f32 sort_key, f32 parallax, bool coll layer.parallax = parallax; layer.collision = collision; layer.tiles.resize(static_cast(w) * h, -1); - layers.push_back(std::move(layer)); + layers.push_back(base::move(layer)); return layers.back(); } @@ -55,7 +56,7 @@ bool TileMap::IsSolidWorld(Vec2 world) const { } Rect TileMap::WorldBounds() const { - if (!(tile_size > 0.0f) || !std::isfinite(tile_size)) return {}; + if (!(tile_size > 0.0f) || !::isfinite(tile_size)) return {}; u32 max_w = 0, max_h = 0; for (const TileLayer& layer : layers) { max_w = layer.width > max_w ? layer.width : max_w; diff --git a/engine/render2d/tile_map.h b/engine/render2d/tile_map.h index 44dd7fa9..1854b27e 100644 --- a/engine/render2d/tile_map.h +++ b/engine/render2d/tile_map.h @@ -1,7 +1,7 @@ #ifndef RX_RENDER2D_TILE_MAP_H_ #define RX_RENDER2D_TILE_MAP_H_ -#include +#include #include @@ -74,7 +74,7 @@ class RX_RENDER2D_EXPORT TileMap { Vec2i WorldToTile(Vec2 world) const; // Top-left world corner of tile (tx, ty). Vec2 TileToWorld(i32 tx, i32 ty) const { - if (!(tile_size > 0.0f) || !std::isfinite(tile_size)) return {}; + if (!(tile_size > 0.0f) || !::isfinite(tile_size)) return {}; return {static_cast(tx) * tile_size, static_cast(ty) * tile_size}; } diff --git a/engine/render2d/types2d.h b/engine/render2d/types2d.h index 3a798090..df981aff 100644 --- a/engine/render2d/types2d.h +++ b/engine/render2d/types2d.h @@ -2,6 +2,8 @@ #define RX_RENDER2D_TYPES2D_H_ #include "core/math.h" + +#include #include "core/types.h" // Small value types shared across the 2D renderer. Kept header-only and free of @@ -23,7 +25,7 @@ struct Vec2 { }; inline f32 Dot(Vec2 a, Vec2 b) { return a.x * b.x + a.y * b.y; } -inline f32 Length(Vec2 v) { return std::sqrt(Dot(v, v)); } +inline f32 Length(Vec2 v) { return ::sqrt(Dot(v, v)); } inline Vec2 Lerp(Vec2 a, Vec2 b, f32 t) { return a + (b - a) * t; } inline Vec2 Normalize(Vec2 v) { f32 l = Length(v); diff --git a/engine/rpc/rpc_message.cc b/engine/rpc/rpc_message.cc index 6223b291..223fc222 100644 --- a/engine/rpc/rpc_message.cc +++ b/engine/rpc/rpc_message.cc @@ -1,6 +1,11 @@ +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/optional.h" +#include "base/strings/xstring.h" #include "rpc/rpc_message.h" -#include +#include namespace rx::rpc { namespace { @@ -15,20 +20,20 @@ constexpr size_t kMaxName = 256; constexpr size_t kMaxArgs = 1024; constexpr size_t kMaxBytes = 16u * 1024 * 1024; // 16 MiB per string/blob -void PutU16(std::vector& b, u16 v) { +void PutU16(base::Vector& b, u16 v) { b.push_back(u8(v)); b.push_back(u8(v >> 8)); } -void PutU32(std::vector& b, u32 v) { +void PutU32(base::Vector& b, u32 v) { for (int i = 0; i < 4; ++i) b.push_back(u8(v >> (8 * i))); } -void PutU64(std::vector& b, u64 v) { +void PutU64(base::Vector& b, u64 v) { for (int i = 0; i < 8; ++i) b.push_back(u8(v >> (8 * i))); } -void PutBytes(std::vector& b, const u8* p, size_t n) { +void PutBytes(base::Vector& b, const u8* p, size_t n) { b.insert(b.end(), p, p + n); } @@ -74,21 +79,21 @@ struct Reader { i64 I64() { u64 v = U64(); i64 r; - std::memcpy(&r, &v, 8); + base::MemCopy(&r, &v, 8); return r; } f64 F64() { u64 v = U64(); f64 r; - std::memcpy(&r, &v, 8); + base::MemCopy(&r, &v, 8); return r; } }; } // namespace -std::vector EncodeCall(const RpcCall& call) { - std::vector b; +base::Vector EncodeCall(const RpcCall& call) { + base::Vector b; PutU32(b, kMagic); PutU16(b, static_cast(call.name.size())); PutBytes(b, reinterpret_cast(call.name.data()), call.name.size()); @@ -104,25 +109,25 @@ std::vector EncodeCall(const RpcCall& call) { case RpcValue::Type::kInt: { u64 v; i64 s = a.as_int(); - std::memcpy(&v, &s, 8); + base::MemCopy(&v, &s, 8); PutU64(b, v); break; } case RpcValue::Type::kFloat: { u64 v; f64 s = a.as_float(); - std::memcpy(&v, &s, 8); + base::MemCopy(&v, &s, 8); PutU64(b, v); break; } case RpcValue::Type::kString: { - const std::string& s = a.as_string(); + const base::String& s = a.as_string(); PutU32(b, static_cast(s.size())); PutBytes(b, reinterpret_cast(s.data()), s.size()); break; } case RpcValue::Type::kBlob: { - const std::vector& s = a.as_blob(); + const base::Vector& s = a.as_blob(); PutU32(b, static_cast(s.size())); PutBytes(b, s.data(), s.size()); break; @@ -132,19 +137,19 @@ std::vector EncodeCall(const RpcCall& call) { return b; } -std::optional DecodeCall(const u8* data, size_t size) { +base::Optional DecodeCall(const u8* data, size_t size) { Reader r{data, size}; - if (r.U32() != kMagic) return std::nullopt; + if (r.U32() != kMagic) return base::nullopt; RpcCall call; u16 name_len = r.U16(); - if (name_len > kMaxName) return std::nullopt; - if (!r.Need(name_len)) return std::nullopt; + if (name_len > kMaxName) return base::nullopt; + if (!r.Need(name_len)) return base::nullopt; call.name.assign(reinterpret_cast(data + r.pos), name_len); r.pos += name_len; u16 arg_count = r.U16(); - if (arg_count > kMaxArgs) return std::nullopt; + if (arg_count > kMaxArgs) return base::nullopt; call.args.reserve(arg_count); for (u16 i = 0; i < arg_count; ++i) { u8 tag = r.U8(); @@ -163,28 +168,28 @@ std::optional DecodeCall(const u8* data, size_t size) { break; case RpcValue::Type::kString: { u32 len = r.U32(); - if (len > kMaxBytes || !r.Need(len)) return std::nullopt; - std::string s(reinterpret_cast(data + r.pos), len); + if (len > kMaxBytes || !r.Need(len)) return base::nullopt; + base::String s(reinterpret_cast(data + r.pos), len); r.pos += len; - call.args.emplace_back(std::move(s)); + call.args.emplace_back(base::move(s)); break; } case RpcValue::Type::kBlob: { u32 len = r.U32(); - if (len > kMaxBytes || !r.Need(len)) return std::nullopt; - std::vector blob(data + r.pos, data + r.pos + len); + if (len > kMaxBytes || !r.Need(len)) return base::nullopt; + base::Vector blob(data + r.pos, data + r.pos + len); r.pos += len; - call.args.emplace_back(std::move(blob)); + call.args.emplace_back(base::move(blob)); break; } default: - return std::nullopt; // unknown type tag means the stream is corrupt + return base::nullopt; // unknown type tag means the stream is corrupt } - if (!r.ok) return std::nullopt; + if (!r.ok) return base::nullopt; } - if (!r.ok) return std::nullopt; - if (r.pos != size) return std::nullopt; // trailing garbage + if (!r.ok) return base::nullopt; + if (r.pos != size) return base::nullopt; // trailing garbage return call; } diff --git a/engine/rpc/rpc_message.h b/engine/rpc/rpc_message.h index 1933850b..0e1e56c8 100644 --- a/engine/rpc/rpc_message.h +++ b/engine/rpc/rpc_message.h @@ -1,10 +1,10 @@ #ifndef RX_RPC_RPC_MESSAGE_H_ #define RX_RPC_RPC_MESSAGE_H_ -#include -#include -#include +#include "base/containers/vector.h" +#include "base/optional.h" +#include "base/strings/xstring.h" #include "core/types.h" #include "rpc/rpc_value.h" @@ -14,19 +14,19 @@ namespace rx::rpc { // argument list. This is the in-memory form; EncodeCall/DecodeCall translate it // to and from the compact wire bytes that the separate net layer transports. struct RpcCall { - std::string name; + base::String name; RpcArgs args; }; // Serializes a call to the little-endian wire form (see rpc_message.cc for the // exact byte layout). Always succeeds for in-memory calls under the wire limits. -std::vector EncodeCall(const RpcCall& call); +base::Vector EncodeCall(const RpcCall& call); // Decodes a call from a raw byte buffer that arrived over the network. The input // is treated as hostile: every field is bounds-checked, the structural limits // are enforced, and any malformed, truncated, oversized, or trailing-garbage -// input yields std::nullopt rather than a partial or out-of-range result. -std::optional DecodeCall(const u8* data, size_t size); +// input yields base::nullopt rather than a partial or out-of-range result. +base::Optional DecodeCall(const u8* data, size_t size); } // namespace rx::rpc diff --git a/engine/rpc/rpc_registry.cc b/engine/rpc/rpc_registry.cc index ffdf2919..395c8aa8 100644 --- a/engine/rpc/rpc_registry.cc +++ b/engine/rpc/rpc_registry.cc @@ -1,19 +1,22 @@ +#include "base/memory/move.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "rpc/rpc_registry.h" namespace rx::rpc { -void RpcRegistry::On(std::string name, RpcHandler handler) { - handlers_[std::move(name)] = std::move(handler); +void RpcRegistry::On(base::String name, RpcHandler handler) { + handlers_[name] = base::move(handler); } -bool RpcRegistry::Has(std::string_view name) const { - return handlers_.find(std::string(name)) != handlers_.end(); +bool RpcRegistry::Has(base::StringRef name) const { + return handlers_.contains(base::String(name)); } bool RpcRegistry::Dispatch(const RpcContext& ctx, const RpcCall& call) const { - auto it = handlers_.find(call.name); - if (it == handlers_.end()) return false; - it->second(ctx, call.args); + const RpcHandler* handler = handlers_.find(call.name); + if (!handler) return false; + (*handler)(ctx, call.args); return true; } diff --git a/engine/rpc/rpc_registry.h b/engine/rpc/rpc_registry.h index 7f7b7664..d73b114e 100644 --- a/engine/rpc/rpc_registry.h +++ b/engine/rpc/rpc_registry.h @@ -1,11 +1,10 @@ #ifndef RX_RPC_RPC_REGISTRY_H_ #define RX_RPC_RPC_REGISTRY_H_ -#include -#include -#include -#include - +#include "base/containers/unordered_map.h" +#include "base/functional/function.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/types.h" #include "rpc/rpc_message.h" #include "rpc/rpc_value.h" @@ -20,7 +19,7 @@ struct RpcContext { bool from_server = false; }; -using RpcHandler = std::function; +using RpcHandler = base::Function; // Maps RPC names to handlers and dispatches decoded calls to them. The net layer // decodes incoming bytes into an RpcCall, builds the RpcContext from the peer it @@ -28,8 +27,8 @@ using RpcHandler = std::function; // an error, because a peer may send names this build does not implement. class RpcRegistry { public: - void On(std::string name, RpcHandler handler); // registers or replaces - bool Has(std::string_view name) const; + void On(base::String name, RpcHandler handler); // registers or replaces + bool Has(base::StringRef name) const; // Looks up the call's name and invokes its handler. Returns false (and does // nothing) when no handler is registered; the caller logs and drops. Never @@ -40,7 +39,7 @@ class RpcRegistry { size_t size() const; private: - std::unordered_map handlers_; + base::UnorderedMap handlers_; }; } // namespace rx::rpc diff --git a/engine/rpc/rpc_value.cc b/engine/rpc/rpc_value.cc index 537fd63f..1a4a89b8 100644 --- a/engine/rpc/rpc_value.cc +++ b/engine/rpc/rpc_value.cc @@ -1,51 +1,60 @@ +#include "base/containers/vector.h" +#include "base/memory/move.h" +#include "base/strings/xstring.h" #include "rpc/rpc_value.h" namespace rx::rpc { -RpcValue::RpcValue() : v_(std::monostate{}) {} -RpcValue::RpcValue(bool v) : v_(v) {} -RpcValue::RpcValue(i64 v) : v_(v) {} -RpcValue::RpcValue(f64 v) : v_(v) {} -RpcValue::RpcValue(std::string v) : v_(std::move(v)) {} -RpcValue::RpcValue(std::vector v) : v_(std::move(v)) {} +RpcValue::RpcValue() = default; +RpcValue::RpcValue(bool v) : type_(Type::kBool), bool_(v) {} +RpcValue::RpcValue(i64 v) : type_(Type::kInt), int_(v) {} +RpcValue::RpcValue(f64 v) : type_(Type::kFloat), float_(v) {} +RpcValue::RpcValue(base::String v) : type_(Type::kString), string_(base::move(v)) {} +RpcValue::RpcValue(base::Vector v) : type_(Type::kBlob), blob_(base::move(v)) {} RpcValue::Type RpcValue::type() const { - return static_cast(v_.index()); + return type_; } bool RpcValue::is_null() const { - return std::holds_alternative(v_); + return type_ == Type::kNull; } bool RpcValue::as_bool(bool def) const { - if (const bool* p = std::get_if(&v_)) return *p; - return def; + return type_ == Type::kBool ? bool_ : def; } i64 RpcValue::as_int(i64 def) const { - if (const i64* p = std::get_if(&v_)) return *p; - return def; + return type_ == Type::kInt ? int_ : def; } f64 RpcValue::as_float(f64 def) const { - if (const f64* p = std::get_if(&v_)) return *p; - return def; + return type_ == Type::kFloat ? float_ : def; } -const std::string& RpcValue::as_string() const { - static const std::string kEmpty; - if (const std::string* p = std::get_if(&v_)) return *p; - return kEmpty; +const base::String& RpcValue::as_string() const { + static const base::String kEmpty; + return type_ == Type::kString ? string_ : kEmpty; } -const std::vector& RpcValue::as_blob() const { - static const std::vector kEmpty; - if (const std::vector* p = std::get_if>(&v_)) return *p; - return kEmpty; +const base::Vector& RpcValue::as_blob() const { + static const base::Vector kEmpty; + return type_ == Type::kBlob ? blob_ : kEmpty; } +// std::variant's equality: same alternative, then that alternative's ==. The +// float compares as a double does, so NaN is unequal to itself. bool RpcValue::operator==(const RpcValue& other) const { - return v_ == other.v_; + if (type_ != other.type_) return false; + switch (type_) { + case Type::kNull: return true; + case Type::kBool: return bool_ == other.bool_; + case Type::kInt: return int_ == other.int_; + case Type::kFloat: return float_ == other.float_; + case Type::kString: return string_ == other.string_; + case Type::kBlob: return blob_ == other.blob_; + } + return false; } } // namespace rx::rpc diff --git a/engine/rpc/rpc_value.h b/engine/rpc/rpc_value.h index 3d93d6b8..c61f9681 100644 --- a/engine/rpc/rpc_value.h +++ b/engine/rpc/rpc_value.h @@ -1,10 +1,8 @@ #ifndef RX_RPC_RPC_VALUE_H_ #define RX_RPC_RPC_VALUE_H_ -#include -#include -#include - +#include "base/containers/vector.h" +#include "base/strings/xstring.h" #include "core/types.h" namespace rx::rpc { @@ -22,8 +20,8 @@ class RpcValue { explicit RpcValue(bool v); explicit RpcValue(i64 v); explicit RpcValue(f64 v); - explicit RpcValue(std::string v); - explicit RpcValue(std::vector v); // kBlob + explicit RpcValue(base::String v); + explicit RpcValue(base::Vector v); // kBlob Type type() const; bool is_null() const; @@ -35,16 +33,23 @@ class RpcValue { bool as_bool(bool def = false) const; i64 as_int(i64 def = 0) const; f64 as_float(f64 def = 0.0) const; - const std::string& as_string() const; - const std::vector& as_blob() const; + const base::String& as_string() const; + const base::Vector& as_blob() const; bool operator==(const RpcValue& other) const; private: - std::variant> v_; + // A tagged value rather than a union: the string and blob own heap memory, + // and one live member at a time buys nothing at these sizes. + Type type_ = Type::kNull; + bool bool_ = false; + i64 int_ = 0; + f64 float_ = 0.0; + base::String string_; + base::Vector blob_; }; -using RpcArgs = std::vector; +using RpcArgs = base::Vector; } // namespace rx::rpc diff --git a/engine/scene/camera.cc b/engine/scene/camera.cc index 84c8466d..4f32545d 100644 --- a/engine/scene/camera.cc +++ b/engine/scene/camera.cc @@ -1,9 +1,9 @@ #include "scene/camera.h" -#include -#include -#include +#include +#include "base/memory/move.h" +#include "core/scalar.h" #include "ecs/world.h" namespace rx::scene { @@ -14,7 +14,7 @@ bool IsModeValid(ecs::World& world, ecs::Entity mode) { } f32 ApplyEasing(CameraEasing easing, f32 amount) { - const f32 t = std::clamp(amount, 0.0f, 1.0f); + const f32 t = rx::Clamp(amount, 0.0f, 1.0f); switch (easing) { case CameraEasing::kLinear: return t; @@ -46,7 +46,7 @@ void BeginTransition(CameraOutput& output, CameraStack& stack, ecs::Entity mode, const bool compatible_projection = output.view.lens.projection == camera_mode.view.lens.projection; - if (!std::isfinite(spec.duration) || spec.duration <= 0 || !had_output || + if (!::isfinite(spec.duration) || spec.duration <= 0 || !had_output || !compatible_projection) { CutToMode(output, stack, mode, camera_mode); return; @@ -135,7 +135,7 @@ CameraStackResult InitializeCameraStack(ecs::World& world, ecs::Entity output, CameraStack stack; stack.entries.push_back({base_mode, 0}); - world.Add(output, std::move(stack)); + world.Add(output, base::move(stack)); CameraOutput camera_output; camera_output.view = initial_mode.view; @@ -143,7 +143,7 @@ CameraStackResult InitializeCameraStack(ecs::World& world, ecs::Entity output, camera_output.active_mode = base_mode; camera_output.observed_discontinuity_revision = initial_mode.discontinuity_revision; camera_output.valid = true; - world.Add(output, std::move(camera_output)); + world.Add(output, base::move(camera_output)); return CameraStackResult::kSuccess; } @@ -194,26 +194,26 @@ void InvalidateCameraMode(ecs::World& world, ecs::Entity mode) { CameraView InterpolateCameraView(const CameraView& source, const CameraView& destination, f32 amount) { - const f32 t = std::clamp(amount, 0.0f, 1.0f); + const f32 t = rx::Clamp(amount, 0.0f, 1.0f); CameraView view; view.position = Lerp(source.position, destination.position, t); view.orientation = Slerp(Normalize(source.orientation), Normalize(destination.orientation), t); view.lens = destination.lens; if (destination.lens.projection == CameraProjection::kPerspective) { - const f32 source_scale = std::tan(source.lens.fov_y * 0.5f); - const f32 destination_scale = std::tan(destination.lens.fov_y * 0.5f); - view.lens.fov_y = 2.0f * std::atan(std::lerp(source_scale, destination_scale, t)); + const f32 source_scale = ::tan(source.lens.fov_y * 0.5f); + const f32 destination_scale = ::tan(destination.lens.fov_y * 0.5f); + view.lens.fov_y = 2.0f * ::atan(rx::Lerp(source_scale, destination_scale, t)); } else { - view.lens.ortho_height = std::lerp(source.lens.ortho_height, destination.lens.ortho_height, t); - view.lens.ortho_near = std::lerp(source.lens.ortho_near, destination.lens.ortho_near, t); - view.lens.ortho_far = std::lerp(source.lens.ortho_far, destination.lens.ortho_far, t); + view.lens.ortho_height = rx::Lerp(source.lens.ortho_height, destination.lens.ortho_height, t); + view.lens.ortho_near = rx::Lerp(source.lens.ortho_near, destination.lens.ortho_near, t); + view.lens.ortho_far = rx::Lerp(source.lens.ortho_far, destination.lens.ortho_far, t); } return view; } void ResolveCameraStacks(ecs::World& world, f32 dt) { - if (!std::isfinite(dt) || dt < 0) dt = 0; + if (!::isfinite(dt) || dt < 0) dt = 0; world.Each([&](ecs::Entity, CameraStack& stack, CameraOutput& output) { if (!PruneInvalidModes(world, stack, output)) return; diff --git a/engine/scene/camera_rig.cc b/engine/scene/camera_rig.cc index 2a2dac8b..a9acb280 100644 --- a/engine/scene/camera_rig.cc +++ b/engine/scene/camera_rig.cc @@ -1,8 +1,9 @@ #include "scene/camera_rig.h" -#include -#include +#include +#include +#include "core/scalar.h" #include "ecs/world.h" namespace rx::scene { @@ -13,14 +14,14 @@ constexpr f32 kTwoPi = 2.0f * kPi; f32 BlendForHalfLife(f32 half_life, f32 dt) { if (half_life <= 0) return 1.0f; - return 1.0f - std::exp2(-dt / half_life); + return 1.0f - ::exp2(-dt / half_life); } f32 MoveAngle(f32 current, f32 target, f32 amount) { - return current + std::remainder(target - current, kTwoPi) * amount; + return current + ::remainder(target - current, kTwoPi) * amount; } -f32 YawFromDirection(const Vec3& direction) { return std::atan2(direction.x, -direction.z); } +f32 YawFromDirection(const Vec3& direction) { return ::atan2(direction.x, -direction.z); } Quat OrbitRotation(f32 yaw, f32 pitch) { return Normalize(QuatFromAxisAngle({0, -1, 0}, yaw) * QuatFromAxisAngle({1, 0, 0}, pitch)); @@ -34,9 +35,9 @@ Quat LookRotation(const Vec3& direction, const Vec3& up_reference, const Quat& f if (Length(right) <= 1e-5f) right = Cross(forward, Rotate(fallback, Vec3{0, 1, 0})); if (Length(right) <= 1e-5f) { const Vec3 axis = - std::abs(forward.x) <= std::abs(forward.y) && std::abs(forward.x) <= std::abs(forward.z) + ::abs(forward.x) <= ::abs(forward.y) && ::abs(forward.x) <= ::abs(forward.z) ? Vec3{1, 0, 0} - : (std::abs(forward.y) <= std::abs(forward.z) ? Vec3{0, 1, 0} : Vec3{0, 0, 1}); + : (::abs(forward.y) <= ::abs(forward.z) ? Vec3{0, 1, 0} : Vec3{0, 0, 1}); right = Cross(forward, axis); } right = Normalize(right); @@ -84,7 +85,7 @@ void UpdateDeadZone(CameraFollowDeadZone& dead_zone, const Vec3& anchor) { } auto update_axis = [](f32 value, f32 extent, f32* center) { - extent = std::max(extent, 0.0f); + extent = rx::Max(extent, 0.0f); if (value < *center - extent) *center = value + extent; if (value > *center + extent) *center = value - extent; }; @@ -98,13 +99,13 @@ CameraLens DampLens(const CameraLens& current, const CameraLens& target, f32 amo CameraLens lens = target; if (target.projection == CameraProjection::kPerspective) { - const f32 current_scale = std::tan(current.fov_y * 0.5f); - const f32 target_scale = std::tan(target.fov_y * 0.5f); - lens.fov_y = 2.0f * std::atan(std::lerp(current_scale, target_scale, amount)); + const f32 current_scale = ::tan(current.fov_y * 0.5f); + const f32 target_scale = ::tan(target.fov_y * 0.5f); + lens.fov_y = 2.0f * ::atan(rx::Lerp(current_scale, target_scale, amount)); } else { - lens.ortho_height = std::lerp(current.ortho_height, target.ortho_height, amount); - lens.ortho_near = std::lerp(current.ortho_near, target.ortho_near, amount); - lens.ortho_far = std::lerp(current.ortho_far, target.ortho_far, amount); + lens.ortho_height = rx::Lerp(current.ortho_height, target.ortho_height, amount); + lens.ortho_near = rx::Lerp(current.ortho_near, target.ortho_near, amount); + lens.ortho_far = rx::Lerp(current.ortho_far, target.ortho_far, amount); } return lens; } @@ -112,7 +113,7 @@ CameraLens DampLens(const CameraLens& current, const CameraLens& target, f32 amo } // namespace void BuildCameraRigs(ecs::World& world, f32 dt) { - if (!std::isfinite(dt) || dt < 0) dt = 0; + if (!::isfinite(dt) || dt < 0) dt = 0; world.Each([&](ecs::Entity entity, CameraRigPose& pose, CameraAnchor& anchor, CameraMode& mode) { @@ -137,8 +138,8 @@ void BuildCameraRigs(ecs::World& world, f32 dt) { } if (CameraLookAhead* look_ahead = world.Get(entity)) { - const Vec3 offset = ClampLength(anchor.velocity * std::max(look_ahead->seconds, 0.0f), - std::max(look_ahead->maximum_distance, 0.0f)); + const Vec3 offset = ClampLength(anchor.velocity * rx::Max(look_ahead->seconds, 0.0f), + rx::Max(look_ahead->maximum_distance, 0.0f)); pose.pivot += offset; pose.desired.position = pose.pivot; } @@ -159,12 +160,12 @@ void BuildCameraRigs(ecs::World& world, f32 dt) { const f32 pitch_delta = intent ? intent->pitch_delta : 0; orbit.yaw += yaw_delta; orbit.pitch = - std::clamp(orbit.pitch + pitch_delta, std::min(orbit.min_pitch, orbit.max_pitch), - std::max(orbit.min_pitch, orbit.max_pitch)); + rx::Clamp(orbit.pitch + pitch_delta, rx::Min(orbit.min_pitch, orbit.max_pitch), + rx::Max(orbit.min_pitch, orbit.max_pitch)); const Quat anchor_rotation = Normalize(anchor.orientation); if (CameraRecenter* recenter = world.Get(entity)) { - const bool manual = std::abs(yaw_delta) > 1e-6f || std::abs(pitch_delta) > 1e-6f; + const bool manual = ::abs(yaw_delta) > 1e-6f || ::abs(pitch_delta) > 1e-6f; if (manual) { recenter->idle_time = 0; } else { @@ -177,8 +178,8 @@ void BuildCameraRigs(ecs::World& world, f32 dt) { const Vec3 horizontal_velocity{recenter_velocity.x, 0, recenter_velocity.z}; const bool requested = intent && intent->recenter; const bool moving = Length(horizontal_velocity) > - std::max(std::max(recenter->minimum_speed, 0.0f), 1e-5f); - if (requested || (moving && recenter->idle_time >= std::max(recenter->delay, 0.0f))) { + rx::Max(rx::Max(recenter->minimum_speed, 0.0f), 1e-5f); + if (requested || (moving && recenter->idle_time >= rx::Max(recenter->delay, 0.0f))) { const f32 target_yaw = moving ? YawFromDirection(horizontal_velocity) : (orbit.space == CameraOrbitSpace::kAnchor @@ -212,7 +213,7 @@ void BuildCameraRigs(ecs::World& world, f32 dt) { const Vec3 forward = Rotate(pose.desired.orientation, {0, 0, -1}); const Vec3 right = Rotate(pose.desired.orientation, {1, 0, 0}); const Vec3 up = Rotate(pose.desired.orientation, {0, 1, 0}); - pose.desired.position = pose.pivot + forward * -std::max(boom.distance, 0.0f) + + pose.desired.position = pose.pivot + forward * -rx::Max(boom.distance, 0.0f) + right * boom.shoulder_offset + up * boom.height_offset; }); @@ -220,14 +221,14 @@ void BuildCameraRigs(ecs::World& world, f32 dt) { [&](ecs::Entity entity, CameraRigPose& pose, CameraLensDrive& drive, CameraMode&) { if (CameraIntent* intent = world.Get(entity)) { if (drive.lens.projection == CameraProjection::kPerspective) { - drive.lens.fov_y = std::clamp(drive.lens.fov_y - intent->zoom_delta * drive.zoom_speed, - std::min(drive.minimum_fov, drive.maximum_fov), - std::max(drive.minimum_fov, drive.maximum_fov)); + drive.lens.fov_y = rx::Clamp(drive.lens.fov_y - intent->zoom_delta * drive.zoom_speed, + rx::Min(drive.minimum_fov, drive.maximum_fov), + rx::Max(drive.minimum_fov, drive.maximum_fov)); } else { drive.lens.ortho_height = - std::clamp(drive.lens.ortho_height - intent->zoom_delta * drive.zoom_speed, - std::min(drive.minimum_ortho_height, drive.maximum_ortho_height), - std::max(drive.minimum_ortho_height, drive.maximum_ortho_height)); + rx::Clamp(drive.lens.ortho_height - intent->zoom_delta * drive.zoom_speed, + rx::Min(drive.minimum_ortho_height, drive.maximum_ortho_height), + rx::Max(drive.minimum_ortho_height, drive.maximum_ortho_height)); } intent->zoom_delta = 0; } @@ -236,7 +237,7 @@ void BuildCameraRigs(ecs::World& world, f32 dt) { } void PrepareCameraRigConstraints(ecs::World& world, f32 dt) { - if (!std::isfinite(dt) || dt < 0) dt = 0; + if (!::isfinite(dt) || dt < 0) dt = 0; world.Each( [&](ecs::Entity entity, CameraRigPose& pose, CameraAnchor&, CameraMode&) { @@ -264,7 +265,7 @@ void PrepareCameraRigConstraints(ecs::World& world, f32 dt) { } void ResolveCameraRigs(ecs::World& world, f32 dt) { - if (!std::isfinite(dt) || dt < 0) dt = 0; + if (!::isfinite(dt) || dt < 0) dt = 0; world.Each([&](ecs::Entity entity, CameraRigPose& pose, CameraAnchor& anchor, CameraMode& mode) { @@ -296,7 +297,7 @@ void ResolveCameraRigs(ecs::World& world, f32 dt) { if (Length(to_target) > 1e-5f) { target_orientation = Slerp(target_orientation, LookRotation(to_target, up, target_orientation), - std::clamp(framing->weight, 0.0f, 1.0f)); + rx::Clamp(framing->weight, 0.0f, 1.0f)); } } diff --git a/engine/scene/components.h b/engine/scene/components.h index 347be9f0..0ad67e5c 100644 --- a/engine/scene/components.h +++ b/engine/scene/components.h @@ -1,9 +1,9 @@ #ifndef RX_SCENE_COMPONENTS_H_ #define RX_SCENE_COMPONENTS_H_ -#include #include "asset/asset_id.h" +#include "base/strings/xstring.h" #include "core/types.h" #include "ecs/entity.h" @@ -46,7 +46,7 @@ struct DecalReceiver { // A human-readable label for an entity (editor outliner, scene text). Optional: // unnamed entities simply lack it. struct Name { - std::string value; + base::String value; }; // A stable identity independent of the ecs handle (which is reused after a diff --git a/engine/scene/scene_handlers.cc b/engine/scene/scene_handlers.cc index c117564c..11a70026 100644 --- a/engine/scene/scene_handlers.cc +++ b/engine/scene/scene_handlers.cc @@ -5,6 +5,10 @@ // shape as every category: typed handlers, unpacking trampolines, Setup*. #include "scene/scene_handlers.h" +#include + +#include "base/memory/move.h" +#include "base/strings/xstring.h" #include "core/math.h" #include "ecs/entity.h" #include "ecs/world.h" @@ -60,7 +64,7 @@ bool InverseTransformPoint(const Mat4& m, Vec3 point, Vec3* result) { const f64 c1xc2y = c1z * c2x - c1x * c2z; const f64 c1xc2z = c1x * c2y - c1y * c2x; const f64 det = c0x * c1xc2x + c0y * c1xc2y + c0z * c1xc2z; - if (det == 0 || !std::isfinite(det)) return false; + if (det == 0 || !::isfinite(det)) return false; const f64 dx = static_cast(point.x) - m.m[12]; const f64 dy = static_cast(point.y) - m.m[13]; @@ -74,9 +78,9 @@ bool InverseTransformPoint(const Mat4& m, Vec3 point, Vec3* result) { dy * (c0z * c1x - c0x * c1z) + dz * (c0x * c1y - c0y * c1x)) / det; - if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z)) return false; + if (!::isfinite(x) || !::isfinite(y) || !::isfinite(z)) return false; const Vec3 local{static_cast(x), static_cast(y), static_cast(z)}; - if (!std::isfinite(local.x) || !std::isfinite(local.y) || !std::isfinite(local.z)) + if (!::isfinite(local.x) || !::isfinite(local.y) || !::isfinite(local.z)) return false; *result = local; return true; @@ -161,11 +165,11 @@ ecs::Entity FindByPrefab(HandlerContext& c, StrId prefab) { } void SetName(HandlerContext& c, ecs::Entity e, ScriptStringView name) { if (!c.Ecs().IsAlive(e)) return; - std::string value(name.view()); + base::String value(name.view()); if (Name* n = c.Ecs().Get(e)) - n->value = std::move(value); + n->value = base::move(value); else - c.Ecs().Add(e, Name{std::move(value)}); + c.Ecs().Add(e, Name{base::move(value)}); } // Returns a view into the per-call scratch heap (the arena owns the bytes); the // ScriptValue on the stack only views them, so nothing is allocated on or copied diff --git a/engine/scene/world_streaming.cc b/engine/scene/world_streaming.cc index 69a6acb3..32a1306c 100644 --- a/engine/scene/world_streaming.cc +++ b/engine/scene/world_streaming.cc @@ -1,11 +1,12 @@ #include "scene/world_streaming.h" +#include "base/algorithm.h" +#include "base/containers/span.h" +#include "core/scalar.h" #include "scene/world_streaming_ecs.h" -#include -#include -#include -#include +#include +#include namespace rx::scene { namespace { @@ -13,10 +14,10 @@ namespace { constexpr f32 kDistanceQuantization = 1024.0f; bool IsFinite(const Vec3& value) { - return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z); + return ::isfinite(value.x) && ::isfinite(value.y) && ::isfinite(value.z); } -f32 NonNegative(f32 value) { return std::isfinite(value) && value > 0 ? value : 0; } +f32 NonNegative(f32 value) { return ::isfinite(value) && value > 0 ? value : 0; } u8 SanitizeAxes(u8 axes) { axes &= kWorldStreamXYZ; @@ -26,43 +27,43 @@ u8 SanitizeAxes(u8 axes) { WorldStreamObservation SanitizeObservation(WorldStreamObservation observer) { observer.axes = SanitizeAxes(observer.axes); auto sanitize_axis = [&](u8 axis, f32* position, f32* velocity) { - if ((observer.axes & axis) == 0 && !std::isfinite(*position)) *position = 0; - if (!std::isfinite(*velocity)) *velocity = 0; + if ((observer.axes & axis) == 0 && !::isfinite(*position)) *position = 0; + if (!::isfinite(*velocity)) *velocity = 0; }; sanitize_axis(kWorldStreamX, &observer.position.x, &observer.velocity.x); sanitize_axis(kWorldStreamY, &observer.position.y, &observer.velocity.y); sanitize_axis(kWorldStreamZ, &observer.position.z, &observer.velocity.z); observer.load_distance = NonNegative(observer.load_distance); observer.retain_distance = - std::max(observer.load_distance, NonNegative(observer.retain_distance)); + rx::Max(observer.load_distance, NonNegative(observer.retain_distance)); observer.prediction_seconds = NonNegative(observer.prediction_seconds); observer.maximum_prediction_distance = NonNegative(observer.maximum_prediction_distance); return observer; } bool HasFiniteActivePosition(const WorldStreamObservation& observer) { - return ((observer.axes & kWorldStreamX) == 0 || std::isfinite(observer.position.x)) && - ((observer.axes & kWorldStreamY) == 0 || std::isfinite(observer.position.y)) && - ((observer.axes & kWorldStreamZ) == 0 || std::isfinite(observer.position.z)); + return ((observer.axes & kWorldStreamX) == 0 || ::isfinite(observer.position.x)) && + ((observer.axes & kWorldStreamY) == 0 || ::isfinite(observer.position.y)) && + ((observer.axes & kWorldStreamZ) == 0 || ::isfinite(observer.position.z)); } bool SanitizeRegion(const WorldStreamRegion& source, WorldStreamRegion* result) { if (!IsFinite(source.minimum) || !IsFinite(source.maximum)) return false; *result = source; - result->minimum = {std::min(source.minimum.x, source.maximum.x), - std::min(source.minimum.y, source.maximum.y), - std::min(source.minimum.z, source.maximum.z)}; - result->maximum = {std::max(source.minimum.x, source.maximum.x), - std::max(source.minimum.y, source.maximum.y), - std::max(source.minimum.z, source.maximum.z)}; + result->minimum = {rx::Min(source.minimum.x, source.maximum.x), + rx::Min(source.minimum.y, source.maximum.y), + rx::Min(source.minimum.z, source.maximum.z)}; + result->maximum = {rx::Max(source.minimum.x, source.maximum.x), + rx::Max(source.minimum.y, source.maximum.y), + rx::Max(source.minimum.z, source.maximum.z)}; return true; } f32 SafeDistance(double distance) { - if (!std::isfinite(distance) || distance > std::numeric_limits::max()) { - return std::numeric_limits::infinity(); + if (!::isfinite(distance) || distance > FLT_MAX) { + return INFINITY; } - return static_cast(std::max(0.0, distance)); + return static_cast(rx::Max(0.0, distance)); } Vec3 PredictedPosition(const WorldStreamObservation& observer) { @@ -81,11 +82,11 @@ Vec3 PredictedPosition(const WorldStreamObservation& observer) { ? static_cast(observer.velocity.z) * observer.prediction_seconds : 0.0, }; - const double length = std::hypot(offset[0], offset[1], offset[2]); + const double length = ::hypot(offset[0], offset[1], offset[2]); if (length <= 0) return observer.position; const double scale = - std::min(1.0, static_cast(observer.maximum_prediction_distance) / length); - const f32 maximum = std::numeric_limits::max(); + rx::Min(1.0, static_cast(observer.maximum_prediction_distance) / length); + const f32 maximum = FLT_MAX; auto advance = [&](f32 position, double delta) { const double value = static_cast(position) + delta * scale; if (value >= maximum) return maximum; @@ -108,7 +109,7 @@ f32 PointBoundsDistance(const Vec3& point, const WorldStreamRegion& region, u8 a accumulate(kWorldStreamX, point.x, region.minimum.x, region.maximum.x); accumulate(kWorldStreamY, point.y, region.minimum.y, region.maximum.y); accumulate(kWorldStreamZ, point.z, region.minimum.z, region.maximum.z); - return SafeDistance(std::sqrt(distance_sq)); + return SafeDistance(::sqrt(distance_sq)); } double PointSegmentDistance(double point_x, double point_y, double point_z, Vec3 start, Vec3 end, @@ -124,12 +125,12 @@ double PointSegmentDistance(double point_x, double point_y, double point_z, Vec3 segment[0] * segment[0] + segment[1] * segment[1] + segment[2] * segment[2]; double t = 0; if (length_sq > 0) { - t = std::clamp( + t = rx::Clamp( (relative[0] * segment[0] + relative[1] * segment[1] + relative[2] * segment[2]) / length_sq, 0.0, 1.0); } - return std::hypot(relative[0] - segment[0] * t, relative[1] - segment[1] * t, + return ::hypot(relative[0] - segment[0] * t, relative[1] - segment[1] * t, relative[2] - segment[2] * t); } @@ -155,12 +156,12 @@ f32 SweptBoundsDistance(const Vec3& start, const Vec3& end, const WorldStreamReg (axes & kWorldStreamZ) ? (static_cast(region.maximum.z) - region.minimum.z) * 0.5 : 0.0, }; - const double radius = std::hypot(extent[0], extent[1], extent[2]); + const double radius = ::hypot(extent[0], extent[1], extent[2]); const double distance = PointSegmentDistance(center[0], center[1], center[2], start, end, axes); - if (!std::isfinite(distance) || !std::isfinite(radius)) { + if (!::isfinite(distance) || !::isfinite(radius)) { return PointBoundsDistance(start, region, axes); } - return SafeDistance(std::max(0.0, distance - radius)); + return SafeDistance(rx::Max(0.0, distance - radius)); } WorldStreamDemand EvaluateSanitizedDemand(const WorldStreamObservation& observer, @@ -168,14 +169,14 @@ WorldStreamDemand EvaluateSanitizedDemand(const WorldStreamObservation& observer const WorldStreamRegion& region) { const f32 current = PointBoundsDistance(observer.position, region, observer.axes); const f32 swept = SweptBoundsDistance(observer.position, predicted, region, observer.axes); - const f32 prediction = std::min(current, swept); + const f32 prediction = rx::Min(current, swept); const bool load = prediction <= observer.load_distance; return {load, prediction <= observer.retain_distance, load && current > observer.load_distance, current, prediction}; } u64 DistanceKey(f32 distance) { - if (std::isnan(distance) || distance <= 0) return 0; + if (::isnan(distance) || distance <= 0) return 0; // Keep millimetre-scale ordering at ordinary world distances. Above the // fixed-point range, positive IEEE-754 bits are monotonic, so the second // range preserves ordering through every finite f32 value and infinity. @@ -184,17 +185,17 @@ u64 DistanceKey(f32 distance) { if (distance < kLinearLimit) { return static_cast(distance * kDistanceQuantization + 0.5f); } - const u32 bits = std::bit_cast(distance); - const u32 limit_bits = std::bit_cast(kLinearLimit); + const u32 bits = rx::BitCast(distance); + const u32 limit_bits = rx::BitCast(kLinearLimit); return (u64{1} << 32) + static_cast(bits - limit_bits); } -WorldStreamDemand DemandFromObservers(std::span observers, - std::span predictions, +WorldStreamDemand DemandFromObservers(base::Span observers, + base::Span predictions, const WorldStreamRegion& region) { WorldStreamDemand combined; - combined.current_distance = std::numeric_limits::infinity(); - combined.predicted_distance = std::numeric_limits::infinity(); + combined.current_distance = INFINITY; + combined.predicted_distance = INFINITY; bool any_current_load = false; for (size_t i = 0; i < observers.size(); ++i) { const WorldStreamObservation& observer = observers[i]; @@ -203,15 +204,15 @@ WorldStreamDemand DemandFromObservers(std::span ob combined.load |= demand.load; combined.retain |= demand.retain; any_current_load |= demand.load && !demand.prediction_only; - combined.current_distance = std::min(combined.current_distance, demand.current_distance); - combined.predicted_distance = std::min(combined.predicted_distance, demand.predicted_distance); + combined.current_distance = rx::Min(combined.current_distance, demand.current_distance); + combined.predicted_distance = rx::Min(combined.predicted_distance, demand.predicted_distance); } combined.prediction_only = combined.load && !any_current_load; return combined; } f32 PriorityDistance(const WorldStreamDemand& demand) { - return std::min(demand.current_distance, demand.predicted_distance); + return rx::Min(demand.current_distance, demand.predicted_distance); } u8 Urgency(const WorldStreamDemand& demand) { @@ -246,7 +247,7 @@ WorldStreamAction ActionFor(WorldStreamActionKind kind, const WorldStreamRegion& WorldStreamPlan::TrackedRegion* WorldStreamPlan::FindRegion(u64 id) { if (regions_.empty()) return nullptr; - auto it = std::lower_bound( + auto it = base::LowerBound( regions_.begin(), regions_.end(), id, [](const TrackedRegion& region, u64 wanted) { return region.region.id < wanted; }); return it != regions_.end() && it->region.id == id ? it : nullptr; @@ -254,7 +255,7 @@ WorldStreamPlan::TrackedRegion* WorldStreamPlan::FindRegion(u64 id) { const WorldStreamPlan::TrackedRegion* WorldStreamPlan::FindRegion(u64 id) const { if (regions_.empty()) return nullptr; - auto it = std::lower_bound( + auto it = base::LowerBound( regions_.begin(), regions_.end(), id, [](const TrackedRegion& region, u64 wanted) { return region.region.id < wanted; }); return it != regions_.end() && it->region.id == id ? it : nullptr; @@ -308,8 +309,8 @@ WorldStreamDemand EvaluateWorldStreamDemand(const WorldStreamObservation& source WorldStreamRegion region; if (!HasFiniteActivePosition(observer) || !SanitizeRegion(source_region, ®ion) || (observer.channels & source_region.channels) == 0) { - return {false, false, false, std::numeric_limits::infinity(), - std::numeric_limits::infinity()}; + return {false, false, false, INFINITY, + INFINITY}; } const Vec3 predicted = PredictedPosition(observer); @@ -332,8 +333,8 @@ void ConfigureWorldStreaming(WorldStreamPlan& plan, const WorldStreamSettings& s plan.settings_ = settings; } -void AdvanceWorldStreaming(WorldStreamPlan& plan, std::span observers, - std::span candidates, +void AdvanceWorldStreaming(WorldStreamPlan& plan, base::Span observers, + base::Span candidates, const WorldStreamFrameBudget& budget, base::Vector* actions) { if (!actions) return; @@ -365,12 +366,15 @@ void AdvanceWorldStreaming(WorldStreamPlan& plan, std::span ScoredRegion* { if (scored.empty()) return nullptr; - auto it = std::lower_bound( + auto it = base::LowerBound( scored.begin(), scored.end(), id, [](const ScoredRegion& score, u64 wanted) { return score.region.id < wanted; }); return it != scored.end() && it->region.id == id ? it : nullptr; @@ -426,8 +430,8 @@ void AdvanceWorldStreaming(WorldStreamPlan& plan, std::span::infinity(); - demand.predicted_distance = std::numeric_limits::infinity(); + demand.current_distance = INFINITY; + demand.predicted_distance = INFINITY; if (current) demand = current->demand; if (tracked.phase == WorldStreamPlan::Phase::kPreparing || tracked.phase == WorldStreamPlan::Phase::kReady || @@ -514,7 +518,7 @@ void AdvanceWorldStreaming(WorldStreamPlan& plan, std::spanid != score.region.id) { @@ -527,11 +531,14 @@ void AdvanceWorldStreaming(WorldStreamPlan& plan, std::span= budget.maximum_unloads || pending >= budget.maximum_pending) break; @@ -549,7 +556,7 @@ void AdvanceWorldStreaming(WorldStreamPlan& plan, std::span b.region.priority; if (a.last_prepare_tick != b.last_prepare_tick) { @@ -570,11 +577,11 @@ void AdvanceWorldStreaming(WorldStreamPlan& plan, std::span b.region.priority; diff --git a/engine/scene/world_streaming.h b/engine/scene/world_streaming.h index 7c78bd4f..9066896d 100644 --- a/engine/scene/world_streaming.h +++ b/engine/scene/world_streaming.h @@ -3,8 +3,8 @@ #include -#include +#include "base/containers/span.h" #include "core/export.h" #include "core/math.h" @@ -198,8 +198,8 @@ class WorldStreamPlan { u64 TakeGeneration(); friend void ConfigureWorldStreaming(WorldStreamPlan&, const WorldStreamSettings&); - friend void AdvanceWorldStreaming(WorldStreamPlan&, std::span, - std::span, + friend void AdvanceWorldStreaming(WorldStreamPlan&, base::Span, + base::Span, const WorldStreamFrameBudget&, base::Vector*); friend bool ApplyWorldStreamPrepareResult(WorldStreamPlan&, WorldStreamTicket, @@ -224,8 +224,8 @@ RX_SCENE_EXPORT void ConfigureWorldStreaming(WorldStreamPlan& plan, // observers' retain-radius queries; omission means a region left the catalog. // Candidate order does not affect action order. RX_SCENE_EXPORT void AdvanceWorldStreaming(WorldStreamPlan& plan, - std::span observers, - std::span candidates, + base::Span observers, + base::Span candidates, const WorldStreamFrameBudget& budget, base::Vector* actions); diff --git a/engine/script/handler_context.h b/engine/script/handler_context.h index 70013ee4..a6238cfa 100644 --- a/engine/script/handler_context.h +++ b/engine/script/handler_context.h @@ -1,7 +1,7 @@ #ifndef RX_SCRIPT_HANDLER_CONTEXT_H_ #define RX_SCRIPT_HANDLER_CONTEXT_H_ -#include +#include #include "script/script_arena.h" #include "script/script_string.h" @@ -34,7 +34,7 @@ struct HandlerContext { // mid-call) or a returned ScriptStringView dangles. ScriptArena* scratch = nullptr; - // A portable log sink (function pointer + user data, no std::function) so the + // A portable log sink (function pointer + user data, no base::Function) so the // context stays a plain struct any runtime can populate. void (*log_sink)(void* user, ScriptStringView msg) = nullptr; void* log_user = nullptr; diff --git a/engine/script/handler_registry.cc b/engine/script/handler_registry.cc index 92c268ba..956f05b6 100644 --- a/engine/script/handler_registry.cc +++ b/engine/script/handler_registry.cc @@ -1,7 +1,8 @@ #include "script/handler_registry.h" -#include -#include +#include + +#include "base/algorithm.h" namespace rx::script { @@ -9,7 +10,7 @@ namespace { // Lower-bound the sorted table by key. Returns the first entry with key >= want. template auto LowerBound(Vec& table, u64 want) { - return std::lower_bound(table.begin(), table.end(), want, + return base::LowerBound(table.begin(), table.end(), want, [](const auto& e, u64 k) { return e.key < k; }); } } // namespace diff --git a/engine/script/handler_registry.h b/engine/script/handler_registry.h index 490db63f..5a7ca242 100644 --- a/engine/script/handler_registry.h +++ b/engine/script/handler_registry.h @@ -1,10 +1,11 @@ #ifndef RX_SCRIPT_HANDLER_REGISTRY_H_ #define RX_SCRIPT_HANDLER_REGISTRY_H_ -#include +#include #include -#include +#include "base/containers/vector.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/types.h" #include "script/script_string.h" @@ -15,12 +16,12 @@ namespace rx::script { struct HandlerContext; // the per-call service locator (handler_context.h) // A bound script handler: the file-private unpacking trampoline a category emits. -// A plain function pointer, not std::function: no capture, no allocation, and a +// A plain function pointer, not base::Function: no capture, no allocation, and a // stable calling convention every runtime and the wire path can share. using HandlerFn = ScriptValue (*)(HandlerContext&, ScriptArgs&); // Upper bound on a handler's parameter count. Script functions never approach -// this; keeping the params inline (no std::vector) means registration allocates +// this; keeping the params inline (no base::Vector) means registration allocates // nothing per command. inline constexpr u32 kMaxHandlerParams = 12; @@ -48,7 +49,7 @@ struct HandlerDesc { HandlerFn fn = nullptr; HandlerSig sig; // Points at the caller's name bytes. Command names are string literals (static - // storage), so this is a view, never a copied std::string; zero allocation. + // storage), so this is a view, never a copied base::String; zero allocation. ScriptStringView name; }; @@ -90,7 +91,7 @@ class HandlerRegistry { u64 key; // HashStr(name) HandlerDesc desc; }; - std::vector table_; // sorted by key + base::Vector table_; // sorted by key }; } // namespace rx::script diff --git a/engine/script/script_arena.cc b/engine/script/script_arena.cc index f9eca707..0298fc61 100644 --- a/engine/script/script_arena.cc +++ b/engine/script/script_arena.cc @@ -1,17 +1,18 @@ +#include "base/check.h" +#include "base/memory/mem_ops.h" +#include "base/memory/unique_pointer.h" +#include "base/numeric_limits.h" #include "script/script_arena.h" -#include -#include -#include -#include +#include +#include namespace rx::script { namespace { size_t AlignUp(size_t n, size_t align) { const size_t padding = align - 1; - if (n > std::numeric_limits::max() - padding) - throw std::bad_array_new_length(); + BASE_FATAL_CHECK(n <= SIZE_MAX - padding, "script arena request overflows size_t"); return (n + padding) & ~padding; } } // namespace @@ -19,7 +20,8 @@ size_t AlignUp(size_t n, size_t align) { ScriptArena::ScriptArena(size_t block_size) : block_size_(block_size) {} ScriptArena::~ScriptArena() { - for (Block* b : {head_, free_list_}) { + Block* const lists[] = {head_, free_list_}; + for (Block* b : lists) { while (b) { Block* next = b->next; delete[] b->base; @@ -43,22 +45,21 @@ ScriptArena::Block* ScriptArena::NewBlock(size_t min_bytes) { link = &(*link)->next; } size_t cap = min_bytes > block_size_ ? min_bytes : block_size_; - auto b = std::make_unique(); + Block* b = new Block(); b->base = new u8[cap]; b->cap = cap; b->used = 0; b->next = nullptr; - return b.release(); + return b; } void* ScriptArena::Alloc(size_t bytes, size_t align) { // Blocks come from new[], aligned to the default new alignment; requests up to // that (covers every script POD, incl. ScriptValue) get correct alignment // relative to the block base. Over-aligned requests are not supported. - if (align == 0 || (align & (align - 1)) != 0 || align > alignof(std::max_align_t)) - throw std::bad_array_new_length(); - if (bytes > std::numeric_limits::max() - used_) - throw std::bad_array_new_length(); + BASE_FATAL_CHECK(align != 0 && (align & (align - 1)) == 0 && align <= alignof(max_align_t), + "script arena alignment must be a power of two up to max_align_t"); + BASE_FATAL_CHECK(bytes <= SIZE_MAX - used_, "script arena request overflows size_t"); const size_t min_block_bytes = AlignUp(bytes, align); size_t off = head_ ? AlignUp(head_->used, align) : 0; @@ -87,11 +88,10 @@ void ScriptArena::Reset() { } ScriptStringView ArenaCopy(ScriptArena& arena, ScriptStringView s) { - if (s.size == std::numeric_limits::max()) - throw std::bad_array_new_length(); + BASE_FATAL_CHECK(s.size != base::MinMax::max(), "script string longer than u32"); char* dst = static_cast( arena.Alloc(static_cast(s.size) + 1, alignof(char))); - if (s.size) std::memcpy(dst, s.data, s.size); + if (s.size) base::MemCopy(dst, s.data, s.size); dst[s.size] = '\0'; return ScriptStringView(dst, s.size); } diff --git a/engine/script/script_arena.h b/engine/script/script_arena.h index d6234c57..6722e922 100644 --- a/engine/script/script_arena.h +++ b/engine/script/script_arena.h @@ -1,7 +1,7 @@ #ifndef RX_SCRIPT_SCRIPT_ARENA_H_ #define RX_SCRIPT_SCRIPT_ARENA_H_ -#include +#include #include "core/export.h" #include "core/types.h" @@ -28,10 +28,10 @@ class ScriptArena { ScriptArena& operator=(const ScriptArena&) = delete; // Bump-allocate `bytes` with a nonzero power-of-two `align` no greater than - // max_align_t. Never returns null; rejects invalid/overflowing requests with - // std::bad_array_new_length and propagates std::bad_alloc from the backing new. + // max_align_t. Never returns null; an invalid or overflowing request is a + // programmer error and ends the process (BASE_FATAL_CHECK). // Lifetime: until the next Reset(). - RX_SCRIPT_EXPORT void* Alloc(size_t bytes, size_t align = alignof(std::max_align_t)); + RX_SCRIPT_EXPORT void* Alloc(size_t bytes, size_t align = alignof(max_align_t)); // Reclaim everything at once. Blocks are retained for reuse, not freed. RX_SCRIPT_EXPORT void Reset(); diff --git a/engine/script/script_string.cc b/engine/script/script_string.cc index ebb9bf16..bc521eeb 100644 --- a/engine/script/script_string.cc +++ b/engine/script/script_string.cc @@ -1,24 +1,25 @@ +#include "base/algorithm.h" +#include "base/check.h" +#include "base/memory/mem_ops.h" +#include "base/numeric_limits.h" +#include "base/strings/string_ref.h" #include "script/script_string.h" -#include -#include -#include -#include +#include namespace rx::script { ScriptString::ScriptString(ScriptStringView s) { if (s.size == 0) return; - if (s.size == std::numeric_limits::max()) - throw std::bad_array_new_length(); + BASE_FATAL_CHECK(s.size != base::MinMax::max(), "script string longer than u32"); data_ = new char[static_cast(s.size) + 1]; - std::memcpy(data_, s.data, s.size); + base::MemCopy(data_, s.data, s.size); data_[s.size] = '\0'; size_ = s.size; } ScriptString::ScriptString(const char* s) - : ScriptString(ScriptStringView(std::string_view(s ? s : ""))) {} + : ScriptString(ScriptStringView(base::StringRef(s ? s : ""))) {} ScriptString::ScriptString(const ScriptString& o) : ScriptString(o.view()) {} @@ -31,12 +32,12 @@ ScriptString::ScriptString(ScriptString&& o) noexcept ScriptString::~ScriptString() { delete[] data_; } void ScriptString::swap(ScriptString& o) noexcept { - std::swap(data_, o.data_); - std::swap(size_, o.size_); + base::Swap(data_, o.data_); + base::Swap(size_, o.size_); } bool ScriptString::operator==(ScriptStringView o) const { - return size_ == o.size && std::memcmp(c_str(), o.data ? o.data : "", size_) == 0; + return size_ == o.size && base::MemCompare(c_str(), o.data ? o.data : "", size_) == 0; } } // namespace rx::script diff --git a/engine/script/script_string.h b/engine/script/script_string.h index c7dfd28b..fa94288b 100644 --- a/engine/script/script_string.h +++ b/engine/script/script_string.h @@ -1,18 +1,18 @@ #ifndef RX_SCRIPT_SCRIPT_STRING_H_ #define RX_SCRIPT_SCRIPT_STRING_H_ -#include -#include -#include - +#include "base/check.h" +#include "base/numeric_limits.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/types.h" namespace rx::script { -// The string type at the script-handler boundary. Deliberately NOT std::string: +// The string type at the script-handler boundary. Deliberately NOT base::String: // handlers are bound to several script runtimes (the Papyrus VM, the managed -// host, the RPC/net path, a console), and std::string's layout is compiler- and +// host, the RPC/net path, a console), and base::String's layout is compiler- and // stdlib-specific, so it cannot cross a runtime or DSO boundary safely. These // two types give a stable, self-owned representation instead, decoupled from any // one runtime and from any game (rx only, no Skyrim/recreation types leak in). @@ -31,33 +31,31 @@ struct ScriptStringView { : data(s), size(0) { size_t length = 0; while (s && s[length] != '\0') { - if (length == std::numeric_limits::max()) - throw std::bad_array_new_length(); + BASE_FATAL_CHECK(length < base::MinMax::max(), "script string longer than u32"); ++length; } size = static_cast(length); } - ScriptStringView(std::string_view s) // NOLINT(google-explicit-constructor) + ScriptStringView(base::StringRef s) // NOLINT(google-explicit-constructor) : data(s.data()) { - if (s.size() > std::numeric_limits::max()) - throw std::bad_array_new_length(); + BASE_FATAL_CHECK(s.size() <= base::MinMax::max(), "script string longer than u32"); size = static_cast(s.size()); } - constexpr std::string_view view() const { return {data, size}; } - constexpr operator std::string_view() const { return view(); } // NOLINT + constexpr base::StringRef view() const { return {data, size}; } + constexpr operator base::StringRef() const { return view(); } // NOLINT bool empty() const { return size == 0; } }; static_assert(sizeof(ScriptStringView) == 16, "ABI-boundary layout must stay fixed"); // The owning value: what a handler that RETURNS or STORES a string yields. Owns -// a null-terminated UTF-8 buffer with an explicit layout (not std::string), and +// a null-terminated UTF-8 buffer with an explicit layout (not base::String), and // serializes as a u32 length followed by its bytes. class ScriptString { public: ScriptString() = default; RX_SCRIPT_EXPORT ScriptString(ScriptStringView s); // NOLINT - ScriptString(std::string_view s) : ScriptString(ScriptStringView(s)) {} // NOLINT + ScriptString(base::StringRef s) : ScriptString(ScriptStringView(s)) {} // NOLINT RX_SCRIPT_EXPORT ScriptString(const char* s); // NOLINT RX_SCRIPT_EXPORT ScriptString(const ScriptString& o); RX_SCRIPT_EXPORT ScriptString(ScriptString&& o) noexcept; @@ -88,7 +86,7 @@ class ScriptString { // name once and dispatch by id. enum class StrId : u64 {}; -constexpr StrId HashStr(std::string_view s) { +constexpr StrId HashStr(base::StringRef s) { return static_cast(Fnv1a(s)); } diff --git a/engine/script/script_symbols.cc b/engine/script/script_symbols.cc index 057222e1..9db58a58 100644 --- a/engine/script/script_symbols.cc +++ b/engine/script/script_symbols.cc @@ -1,6 +1,6 @@ #include "script/script_symbols.h" -#include +#include "base/check.h" namespace rx::script { @@ -9,11 +9,10 @@ ScriptSymbols::ScriptSymbols() = default; StrId ScriptSymbols::Intern(ScriptStringView text) { const StrId id = HashStr(text); const u64 key = static_cast(id); - auto it = table_.find(key); - if (it != table_.end()) { + if (const ScriptStringView* known = table_.find(key)) { // Same id must mean same bytes; a mismatch is a hash collision we refuse to // silently alias. - assert(it->second.view() == text.view() && "StrId collision between symbols"); + BASE_DCHECK(known->view() == text.view(), "StrId collision between symbols"); return id; } // Store canonical, null-terminated bytes in the grow-only arena and map the id @@ -24,12 +23,12 @@ StrId ScriptSymbols::Intern(ScriptStringView text) { } ScriptStringView ScriptSymbols::Resolve(StrId id) const { - auto it = table_.find(static_cast(id)); - return it == table_.end() ? ScriptStringView{} : it->second; + const ScriptStringView* found = table_.find(static_cast(id)); + return found ? *found : ScriptStringView{}; } bool ScriptSymbols::Has(StrId id) const { - return table_.find(static_cast(id)) != table_.end(); + return table_.contains(static_cast(id)); } } // namespace rx::script diff --git a/engine/script/script_symbols.h b/engine/script/script_symbols.h index 47858f70..68161cbb 100644 --- a/engine/script/script_symbols.h +++ b/engine/script/script_symbols.h @@ -1,12 +1,12 @@ #ifndef RX_SCRIPT_SCRIPT_SYMBOLS_H_ #define RX_SCRIPT_SCRIPT_SYMBOLS_H_ -#include #include "core/export.h" #include "core/types.h" #include "script/script_arena.h" #include "script/script_string.h" +#include "base/containers/unordered_map.h" namespace rx::script { @@ -38,7 +38,7 @@ class ScriptSymbols { private: ScriptArena storage_; // grow-only canonical bytes (never Reset) - std::unordered_map table_; // StrId -> canonical view + base::UnorderedMap table_; // StrId -> canonical view }; } // namespace rx::script diff --git a/engine/script/script_value.h b/engine/script/script_value.h index e26e5b57..b81b3af4 100644 --- a/engine/script/script_value.h +++ b/engine/script/script_value.h @@ -1,9 +1,9 @@ #ifndef RX_SCRIPT_SCRIPT_VALUE_H_ #define RX_SCRIPT_SCRIPT_VALUE_H_ -#include -#include +#include "base/containers/vector.h" +#include "base/meta/traits.h" #include "core/export.h" #include "core/math.h" #include "core/types.h" @@ -86,10 +86,10 @@ class ScriptValue { }; // The point of the hand-rolled union: the value stack is a flat POD buffer. -static_assert(std::is_trivially_copyable_v, +static_assert(base::is_trivially_copyable_v, "ScriptValue must stay a POD so the argument stack is memcpy-able"); -using ScriptStack = std::vector; +using ScriptStack = base::Vector; // A thin typed reader over the argument stack. This is the surface the unpacking // trampolines use (a.Ent(0), a.Vec(1), a.Str(0)), so a handler's generated diff --git a/engine/terrain/terrain.cc b/engine/terrain/terrain.cc index 09fed12d..0e0f94c3 100644 --- a/engine/terrain/terrain.cc +++ b/engine/terrain/terrain.cc @@ -1,29 +1,33 @@ +#include "base/algorithm.h" +#include "base/containers/span.h" +#include "base/memory/move.h" +#include "base/numeric_limits.h" +#include "base/optional.h" +#include "core/scalar.h" #include "terrain/terrain.h" -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace rx::terrain { namespace { bool IsFinite(Vec3 value) { - return std::isfinite(value.x) && std::isfinite(value.y) && - std::isfinite(value.z); + return ::isfinite(value.x) && ::isfinite(value.y) && + ::isfinite(value.z); } bool CheckedSampleCount(u32 quads, size_t *count) { if (quads == 0 || quads > 65534) return false; const size_t side = static_cast(quads) + 1; - if (side > std::numeric_limits::max() / side) + if (side > SIZE_MAX / side) return false; *count = side * side; - return *count <= std::numeric_limits::max(); + return *count <= base::MinMax::max(); } bool KeyLess(TerrainTileKey a, TerrainTileKey b) { @@ -36,12 +40,21 @@ bool ChangeLess(const TerrainSampleChange &a, const TerrainSampleChange &b) { return a.sample < b.sample; } +template +bool IsSorted(const base::Vector &values, Less less) { + for (size_t i = 1; i < values.size(); ++i) { + if (less(values[i], values[i - 1])) + return false; + } + return true; +} + u32 ActiveLayerCount(const TerrainDesc &desc) { - return static_cast(std::min(desc.layers.size(), 4)); + return static_cast(rx::Min(desc.layers.size(), 4)); } TerrainWeights NormalizeWeights(TerrainWeights value, u32 layer_count) { - layer_count = std::clamp(layer_count, 1u, 4u); + layer_count = rx::Clamp(layer_count, 1u, 4u); u32 total = 0; for (u32 i = 0; i < layer_count; ++i) total += value.rgba[i]; @@ -64,10 +77,11 @@ TerrainWeights NormalizeWeights(TerrainWeights value, u32 layer_count) { assigned += normalized.rgba[i]; remainders[i] = {i, scaled % total}; } - std::sort(remainders, remainders + layer_count, - [](const Remainder &a, const Remainder &b) { - return a.value != b.value ? a.value > b.value : a.layer < b.layer; - }); + // Layers are distinct, so the order is total and any correct sort agrees. + base::Sort(remainders, remainders + layer_count, + [](const Remainder &a, const Remainder &b) { + return a.value != b.value ? a.value > b.value : a.layer < b.layer; + }); for (u32 i = 0; assigned < 255; ++i, ++assigned) { ++normalized.rgba[remainders[i % layer_count].layer]; } @@ -89,10 +103,10 @@ bool IsNormalized(TerrainWeights value, u32 layer_count) { TerrainWeights PaintWeights(TerrainWeights old, u32 target, f32 amount, u32 layer_count) { - amount = std::clamp(amount, 0.0f, 1.0f); + amount = rx::Clamp(amount, 0.0f, 1.0f); const u32 old_target = old.rgba[target]; - const u32 new_target = static_cast(std::clamp( - std::lround(old_target + (255.0f - old_target) * amount), 0l, 255l)); + const u32 new_target = static_cast(rx::Clamp( + ::lround(old_target + (255.0f - old_target) * amount), 0l, 255l)); const u32 old_others = 255 - old_target; const u32 new_others = 255 - new_target; @@ -122,7 +136,7 @@ TerrainWeights PaintWeights(TerrainWeights old, u32 target, f32 amount, }; for (u32 i = 0; i < remainder_count; ++i) { for (u32 j = i + 1; j < remainder_count; ++j) { - if (better(remainders[j], remainders[i])) std::swap(remainders[i], remainders[j]); + if (better(remainders[j], remainders[i])) base::Swap(remainders[i], remainders[j]); } } for (u32 i = 0; assigned < new_others; ++i, ++assigned) { @@ -184,7 +198,7 @@ bool RayBounds(Vec3 origin, Vec3 direction, const f32 minima[3] = {bounds.minimum.x, bounds.minimum.y, bounds.minimum.z}; const f32 maxima[3] = {bounds.maximum.x, bounds.maximum.y, bounds.maximum.z}; for (u32 axis = 0; axis < 3; ++axis) { - if (std::abs(directions[axis]) < 1e-12f) { + if (::abs(directions[axis]) < 1e-12f) { if (origins[axis] < minima[axis] || origins[axis] > maxima[axis]) return false; continue; @@ -194,9 +208,9 @@ bool RayBounds(Vec3 origin, Vec3 direction, double b = (static_cast(maxima[axis]) - origins[axis]) / directions[axis]; if (a > b) - std::swap(a, b); - minimum_t = std::max(minimum_t, a); - maximum_t = std::min(maximum_t, b); + base::Swap(a, b); + minimum_t = rx::Max(minimum_t, a); + maximum_t = rx::Min(maximum_t, b); if (minimum_t > maximum_t) return false; } @@ -210,19 +224,19 @@ bool RayTriangle(Vec3 origin, Vec3 direction, Vec3 a, Vec3 b, Vec3 c, const Vec3 edge_b = c - a; const Vec3 p = Cross(direction, edge_b); const f32 determinant = Dot(edge_a, p); - if (!std::isfinite(determinant) || std::abs(determinant) < 1e-8f) + if (!::isfinite(determinant) || ::abs(determinant) < 1e-8f) return false; const f32 inverse = 1.0f / determinant; const Vec3 relative = origin - a; const f32 u = Dot(relative, p) * inverse; - if (!std::isfinite(u) || u < 0 || u > 1) + if (!::isfinite(u) || u < 0 || u > 1) return false; const Vec3 q = Cross(relative, edge_a); const f32 v = Dot(direction, q) * inverse; - if (!std::isfinite(v) || v < 0 || u + v > 1) + if (!::isfinite(v) || v < 0 || u + v > 1) return false; const f32 hit = Dot(edge_b, q) * inverse; - if (!std::isfinite(hit) || hit < 0 || hit > maximum_distance) + if (!::isfinite(hit) || hit < 0 || hit > maximum_distance) return false; *distance = hit; *normal = Normalize(Cross(edge_a, edge_b)); @@ -235,17 +249,17 @@ bool RayTriangle(Vec3 origin, Vec3 direction, Vec3 a, Vec3 b, Vec3 c, Terrain::Terrain() : Terrain(TerrainDesc{}) {} -Terrain::Terrain(TerrainDesc desc) : desc_(std::move(desc)) { +Terrain::Terrain(TerrainDesc desc) : desc_(base::move(desc)) { if (desc_.tile_quads == 0) desc_.tile_quads = 32; - if (!std::isfinite(desc_.sample_spacing) || desc_.sample_spacing <= 0) { + if (!::isfinite(desc_.sample_spacing) || desc_.sample_spacing <= 0) { desc_.sample_spacing = 1.0f; } - if (!std::isfinite(desc_.origin.x)) + if (!::isfinite(desc_.origin.x)) desc_.origin.x = 0; - if (!std::isfinite(desc_.origin.y)) + if (!::isfinite(desc_.origin.y)) desc_.origin.y = 0; - if (!std::isfinite(desc_.origin.z)) + if (!::isfinite(desc_.origin.z)) desc_.origin.z = 0; if (desc_.layers.empty()) desc_.layers.push_back(TerrainLayer{"Layer 0"}); @@ -255,7 +269,7 @@ Terrain::Terrain(TerrainDesc desc) : desc_(std::move(desc)) { const TerrainTile *Terrain::FindTile(TerrainTileKey key) const { const auto found = - std::lower_bound(tiles_.begin(), tiles_.end(), key, + base::LowerBound(tiles_.begin(), tiles_.end(), key, [](const TerrainTile &tile, TerrainTileKey wanted) { return KeyLess(tile.key, wanted); }); @@ -264,7 +278,7 @@ const TerrainTile *Terrain::FindTile(TerrainTileKey key) const { TerrainTile *Terrain::FindTileMutable(TerrainTileKey key) { auto found = - std::lower_bound(tiles_.begin(), tiles_.end(), key, + base::LowerBound(tiles_.begin(), tiles_.end(), key, [](const TerrainTile &tile, TerrainTileKey wanted) { return KeyLess(tile.key, wanted); }); @@ -274,23 +288,31 @@ TerrainTile *Terrain::FindTileMutable(TerrainTileKey key) { void Terrain::RecalculateBounds(TerrainTile *tile) { if (!tile || tile->heights.empty()) return; - const auto [minimum, maximum] = - std::minmax_element(tile->heights.begin(), tile->heights.end()); + // std::minmax_element's picks: the first smallest and the last largest. + const f32 *minimum = tile->heights.begin(); + const f32 *maximum = tile->heights.begin(); + for (const f32 *it = tile->heights.begin() + 1; it != tile->heights.end(); + ++it) { + if (*it < *minimum) + minimum = it; + if (!(*it < *maximum)) + maximum = it; + } tile->minimum_height = *minimum; tile->maximum_height = *maximum; } -bool Terrain::AddOrReplaceTile(TerrainTileKey key, std::span heights, - std::span weights) { +bool Terrain::AddOrReplaceTile(TerrainTileKey key, base::Span heights, + base::Span weights) { size_t sample_count = 0; if (!CheckedSampleCount(desc_.tile_quads, &sample_count) || heights.size() != sample_count || (!weights.empty() && weights.size() != sample_count) || - !std::isfinite(desc_.tile_quads * desc_.sample_spacing)) { + !::isfinite(desc_.tile_quads * desc_.sample_spacing)) { return false; } for (f32 height : heights) { - if (!std::isfinite(height)) + if (!::isfinite(height)) return false; } @@ -305,20 +327,20 @@ bool Terrain::AddOrReplaceTile(TerrainTileKey key, std::span heights, } } RecalculateBounds(&replacement); - if (!std::isfinite(replacement.maximum_height - replacement.minimum_height)) + if (!::isfinite(replacement.maximum_height - replacement.minimum_height)) return false; auto insertion = - std::lower_bound(tiles_.begin(), tiles_.end(), key, + base::LowerBound(tiles_.begin(), tiles_.end(), key, [](const TerrainTile &tile, TerrainTileKey wanted) { return KeyLess(tile.key, wanted); }); if (insertion != tiles_.end() && insertion->key == key) { replacement.revision = insertion->revision + 1; - *insertion = std::move(replacement); + *insertion = base::move(replacement); } else { replacement.revision = 1; - insertion = tiles_.insert(insertion, std::move(replacement)); + insertion = tiles_.insert(insertion, base::move(replacement)); } TerrainTile *source = &*insertion; @@ -352,10 +374,10 @@ bool Terrain::AddOrReplaceTile(TerrainTileKey key, std::span heights, bool normal_dependent, auto copy_samples) { const i64 neighbor_x = static_cast(key.x) + offset_x; const i64 neighbor_z = static_cast(key.z) + offset_z; - if (neighbor_x < std::numeric_limits::min() || - neighbor_x > std::numeric_limits::max() || - neighbor_z < std::numeric_limits::min() || - neighbor_z > std::numeric_limits::max()) { + if (neighbor_x < base::MinMax::min() || + neighbor_x > base::MinMax::max() || + neighbor_z < base::MinMax::min() || + neighbor_z > base::MinMax::max()) { return; } synchronize({static_cast(neighbor_x), static_cast(neighbor_z)}, @@ -392,7 +414,7 @@ bool Terrain::AddOrReplaceTile(TerrainTileKey key, std::span heights, return true; } -std::optional Terrain::GridHeight(i64 grid_x, i64 grid_z) const { +base::Optional Terrain::GridHeight(i64 grid_x, i64 grid_z) const { const i64 quads = desc_.tile_quads; const i64 base_x = FloorDiv(grid_x, quads); const i64 base_z = FloorDiv(grid_z, quads); @@ -405,10 +427,10 @@ std::optional Terrain::GridHeight(i64 grid_x, i64 grid_z) const { const u32 side = desc_.tile_quads + 1; for (u32 z = 0; z < count_z; ++z) { for (u32 x = 0; x < count_x; ++x) { - if (candidate_x[x] < std::numeric_limits::min() || - candidate_x[x] > std::numeric_limits::max() || - candidate_z[z] < std::numeric_limits::min() || - candidate_z[z] > std::numeric_limits::max()) { + if (candidate_x[x] < base::MinMax::min() || + candidate_x[x] > base::MinMax::max() || + candidate_z[z] < base::MinMax::min() || + candidate_z[z] > base::MinMax::max()) { continue; } const TerrainTile *tile = FindTile( @@ -422,28 +444,28 @@ std::optional Terrain::GridHeight(i64 grid_x, i64 grid_z) const { return tile->heights[SampleIndex(side, local_x, local_z)]; } } - return std::nullopt; + return base::nullopt; } -std::optional Terrain::SampleHeight(f32 world_x, f32 world_z) const { - if (!std::isfinite(world_x) || !std::isfinite(world_z) || tiles_.empty()) - return std::nullopt; +base::Optional Terrain::SampleHeight(f32 world_x, f32 world_z) const { + if (!::isfinite(world_x) || !::isfinite(world_z) || tiles_.empty()) + return base::nullopt; const double grid_x = (static_cast(world_x) - desc_.origin.x) / desc_.sample_spacing; const double grid_z = (static_cast(world_z) - desc_.origin.z) / desc_.sample_spacing; const double quads = desc_.tile_quads; - const double tile_floor_x = std::floor(grid_x / quads); - const double tile_floor_z = std::floor(grid_z / quads); + const double tile_floor_x = ::floor(grid_x / quads); + const double tile_floor_z = ::floor(grid_z / quads); if (tile_floor_x <= - static_cast(std::numeric_limits::min()) + 1 || + static_cast(base::MinMax::min()) + 1 || tile_floor_x >= - static_cast(std::numeric_limits::max()) - 1 || + static_cast(base::MinMax::max()) - 1 || tile_floor_z <= - static_cast(std::numeric_limits::min()) + 1 || + static_cast(base::MinMax::min()) + 1 || tile_floor_z >= - static_cast(std::numeric_limits::max()) - 1) { - return std::nullopt; + static_cast(base::MinMax::max()) - 1) { + return base::nullopt; } const i64 base_x = static_cast(tile_floor_x); const i64 base_z = static_cast(tile_floor_z); @@ -452,18 +474,18 @@ std::optional Terrain::SampleHeight(f32 world_x, f32 world_z) const { constexpr double kBoundaryEpsilon = 1e-6; const i64 candidate_x[2] = {base_x, base_x - 1}; const i64 candidate_z[2] = {base_z, base_z - 1}; - const u32 count_x = std::abs(edge_x) <= kBoundaryEpsilon ? 2 : 1; - const u32 count_z = std::abs(edge_z) <= kBoundaryEpsilon ? 2 : 1; + const u32 count_x = ::abs(edge_x) <= kBoundaryEpsilon ? 2 : 1; + const u32 count_z = ::abs(edge_z) <= kBoundaryEpsilon ? 2 : 1; const u32 side = desc_.tile_quads + 1; for (u32 cz = 0; cz < count_z; ++cz) { for (u32 cx = 0; cx < count_x; ++cx) { const i64 tile_x = candidate_x[cx]; const i64 tile_z = candidate_z[cz]; - if (tile_x < std::numeric_limits::min() || - tile_x > std::numeric_limits::max() || - tile_z < std::numeric_limits::min() || - tile_z > std::numeric_limits::max()) { + if (tile_x < base::MinMax::min() || + tile_x > base::MinMax::max() || + tile_z < base::MinMax::min() || + tile_z > base::MinMax::max()) { continue; } const TerrainTile *tile = @@ -476,12 +498,12 @@ std::optional Terrain::SampleHeight(f32 world_x, f32 world_z) const { local_z < -kBoundaryEpsilon || local_z > quads + kBoundaryEpsilon) { continue; } - local_x = std::clamp(local_x, 0.0, quads); - local_z = std::clamp(local_z, 0.0, quads); + local_x = rx::Clamp(local_x, 0.0, quads); + local_z = rx::Clamp(local_z, 0.0, quads); const u32 cell_x = - std::min(static_cast(std::floor(local_x)), desc_.tile_quads - 1); + rx::Min(static_cast(::floor(local_x)), desc_.tile_quads - 1); const u32 cell_z = - std::min(static_cast(std::floor(local_z)), desc_.tile_quads - 1); + rx::Min(static_cast(::floor(local_z)), desc_.tile_quads - 1); const f32 fraction_x = static_cast(local_x - cell_x); const f32 fraction_z = static_cast(local_z - cell_z); const f32 a = tile->heights[SampleIndex(side, cell_x, cell_z)]; @@ -493,14 +515,14 @@ std::optional Terrain::SampleHeight(f32 world_x, f32 world_z) const { : d + (1.0f - fraction_x) * (c - d) + (1.0f - fraction_z) * (b - d); const double world_height = static_cast(desc_.origin.y) + height; - if (!std::isfinite(world_height) || - std::abs(world_height) > std::numeric_limits::max()) { - return std::nullopt; + if (!::isfinite(world_height) || + ::abs(world_height) > FLT_MAX) { + return base::nullopt; } return static_cast(world_height); } } - return std::nullopt; + return base::nullopt; } asset::AssetId Terrain::TileAssetId(TerrainTileKey key) const { @@ -510,17 +532,17 @@ asset::AssetId Terrain::TileAssetId(TerrainTileKey key) const { for (char c : tag) HashByte(&hash, static_cast(c)); HashU64(&hash, desc_.id.hash); - HashU32(&hash, std::bit_cast(key.x)); - HashU32(&hash, std::bit_cast(key.z)); + HashU32(&hash, rx::BitCast(key.x)); + HashU32(&hash, rx::BitCast(key.z)); return asset::AssetId{hash == 0 ? 1 : hash}; } -std::optional +base::Optional Terrain::TileRegion(TerrainTileKey key, u32 channels, i32 priority) const { - if (!desc_.id) return std::nullopt; + if (!desc_.id) return base::nullopt; const TerrainTile *tile = FindTile(key); if (!tile) - return std::nullopt; + return base::nullopt; const double width = static_cast(desc_.tile_quads) * desc_.sample_spacing; const double minimum_x = desc_.origin.x + static_cast(key.x) * width; @@ -531,13 +553,13 @@ Terrain::TileRegion(TerrainTileKey key, u32 channels, i32 priority) const { static_cast(desc_.origin.y) + tile->minimum_height; const double maximum_y = static_cast(desc_.origin.y) + tile->maximum_height; - const double maximum_float = std::numeric_limits::max(); + const double maximum_float = FLT_MAX; const double values[] = {minimum_x, minimum_y, minimum_z, maximum_x, maximum_y, maximum_z}; for (double value : values) { - if (!std::isfinite(value) || value < -maximum_float || + if (!::isfinite(value) || value < -maximum_float || value > maximum_float) { - return std::nullopt; + return base::nullopt; } } return scene::WorldStreamRegion{ @@ -557,7 +579,7 @@ void Terrain::GatherStreamRegions( if (!regions) return; regions->clear(); - if ((query.channels & channels) == 0 || !std::isfinite(query.radius) || + if ((query.channels & channels) == 0 || !::isfinite(query.radius) || query.radius < 0) return; u8 axes = query.axes & scene::kWorldStreamXYZ; @@ -565,7 +587,7 @@ void Terrain::GatherStreamRegions( axes = scene::kWorldStreamXYZ; auto valid_axis = [&](u8 axis, f32 origin, f32 predicted) { return (axes & axis) == 0 || - (std::isfinite(origin) && std::isfinite(predicted)); + (::isfinite(origin) && ::isfinite(predicted)); }; if (!valid_axis(scene::kWorldStreamX, query.origin.x, query.predicted.x) || !valid_axis(scene::kWorldStreamY, query.origin.y, query.predicted.y) || @@ -578,13 +600,13 @@ void Terrain::GatherStreamRegions( if ((axes & axis) == 0) return true; const double query_minimum = - std::min(start, end) - static_cast(query.radius); + rx::Min(start, end) - static_cast(query.radius); const double query_maximum = - std::max(start, end) + static_cast(query.radius); + rx::Max(start, end) + static_cast(query.radius); return maximum >= query_minimum && minimum <= query_maximum; }; for (const TerrainTile &tile : tiles_) { - const std::optional region = + const base::Optional region = TileRegion(tile.key, channels, priority); if (!region) continue; @@ -599,16 +621,16 @@ void Terrain::GatherStreamRegions( } } -std::optional +base::Optional Terrain::BuildTileMesh(TerrainTileKey key, asset::AssetId material) const { const TerrainTile *tile = FindTile(key); if (!tile) - return std::nullopt; + return base::nullopt; const u32 quads = desc_.tile_quads; const u32 side = quads + 1; const f32 width = static_cast(quads) * desc_.sample_spacing; - if (!std::isfinite(width)) - return std::nullopt; + if (!::isfinite(width)) + return base::nullopt; asset::Mesh mesh; mesh.id = TileAssetId(key); @@ -623,10 +645,10 @@ Terrain::BuildTileMesh(TerrainTileKey key, asset::AssetId material) const { const f32 center = tile->heights[index]; const i64 grid_x = static_cast(key.x) * quads + x; const i64 grid_z = static_cast(key.z) * quads + z; - const std::optional left = GridHeight(grid_x - 1, grid_z); - const std::optional right = GridHeight(grid_x + 1, grid_z); - const std::optional down = GridHeight(grid_x, grid_z - 1); - const std::optional up = GridHeight(grid_x, grid_z + 1); + const base::Optional left = GridHeight(grid_x - 1, grid_z); + const base::Optional right = GridHeight(grid_x + 1, grid_z); + const base::Optional down = GridHeight(grid_x, grid_z - 1); + const base::Optional up = GridHeight(grid_x, grid_z + 1); const f32 dx = left && right ? (*right - *left) / (2 * desc_.sample_spacing) : right ? (*right - center) / desc_.sample_spacing @@ -674,27 +696,27 @@ Terrain::BuildTileMesh(TerrainTileKey key, asset::AssetId material) const { (tile->maximum_height - tile->minimum_height) * 0.5f; mesh.bounds_center[2] = width * 0.5f; const f32 half_height = (tile->maximum_height - tile->minimum_height) * 0.5f; - mesh.bounds_radius = std::hypot(width * 0.5f, width * 0.5f, half_height); + mesh.bounds_radius = ::hypot(width * 0.5f, width * 0.5f, half_height); return mesh; } -std::optional Terrain::Raycast(Vec3 origin, Vec3 direction, +base::Optional Terrain::Raycast(Vec3 origin, Vec3 direction, f32 maximum_distance) const { if (!IsFinite(origin) || !IsFinite(direction) || - std::isnan(maximum_distance) || maximum_distance < 0) { - return std::nullopt; + ::isnan(maximum_distance) || maximum_distance < 0) { + return base::nullopt; } - const double length = std::hypot(direction.x, direction.y, direction.z); - if (!std::isfinite(length) || length <= 0) - return std::nullopt; + const double length = ::hypot(direction.x, direction.y, direction.z); + if (!::isfinite(length) || length <= 0) + return base::nullopt; direction = {static_cast(static_cast(direction.x) / length), static_cast(static_cast(direction.y) / length), static_cast(static_cast(direction.z) / length)}; if (!IsFinite(direction)) - return std::nullopt; + return base::nullopt; f32 nearest = maximum_distance; - std::optional result; + base::Optional result; const u32 quads = desc_.tile_quads; const u32 side = quads + 1; for (const TerrainTile &tile : tiles_) { @@ -711,8 +733,8 @@ std::optional Terrain::Raycast(Vec3 origin, Vec3 direction, minimum_x + width, maximum_y, minimum_z + width}; bool valid_bounds = true; for (double value : values) { - if (!std::isfinite(value) || - std::abs(value) > std::numeric_limits::max()) { + if (!::isfinite(value) || + ::abs(value) > FLT_MAX) { valid_bounds = false; break; } @@ -764,12 +786,12 @@ std::optional Terrain::Raycast(Vec3 origin, Vec3 direction, TerrainChange Terrain::ApplyBrush(const TerrainBrush &brush) { TerrainChange change; change.terrain = desc_.id; - if (!std::isfinite(brush.center_x) || !std::isfinite(brush.center_z) || - !std::isfinite(brush.radius) || brush.radius <= 0 || - !std::isfinite(brush.strength) || brush.strength <= 0 || - !std::isfinite(brush.falloff) || brush.falloff < 0 || + if (!::isfinite(brush.center_x) || !::isfinite(brush.center_z) || + !::isfinite(brush.radius) || brush.radius <= 0 || + !::isfinite(brush.strength) || brush.strength <= 0 || + !::isfinite(brush.falloff) || brush.falloff < 0 || (brush.mode == TerrainBrushMode::kFlatten && - !std::isfinite(brush.flatten_target)) || + !::isfinite(brush.flatten_target)) || (brush.mode == TerrainBrushMode::kPaintLayer && brush.layer >= ActiveLayerCount(desc_))) { return change; @@ -799,12 +821,12 @@ TerrainChange Terrain::ApplyBrush(const TerrainBrush &brush) { const double world_z = desc_.origin.z + static_cast(grid_z) * desc_.sample_spacing; const f32 distance = static_cast( - std::hypot(world_x - brush.center_x, world_z - brush.center_z)); + ::hypot(world_x - brush.center_x, world_z - brush.center_z)); if (distance > brush.radius) continue; - const f32 radial = std::max(0.0f, 1.0f - distance / brush.radius); + const f32 radial = rx::Max(0.0f, 1.0f - distance / brush.radius); const f32 influence = - brush.falloff == 0 ? 1.0f : std::pow(radial, brush.falloff); + brush.falloff == 0 ? 1.0f : ::pow(radial, brush.falloff); if (influence <= 0) continue; @@ -819,12 +841,12 @@ TerrainChange Terrain::ApplyBrush(const TerrainBrush &brush) { new_value.height -= brush.strength * influence; break; case TerrainBrushMode::kSmooth: { - const std::optional neighbors[] = { + const base::Optional neighbors[] = { GridHeight(grid_x - 1, grid_z), GridHeight(grid_x + 1, grid_z), GridHeight(grid_x, grid_z - 1), GridHeight(grid_x, grid_z + 1)}; f32 total = 0; u32 count = 0; - for (std::optional neighbor : neighbors) { + for (base::Optional neighbor : neighbors) { if (neighbor) { total += *neighbor; ++count; @@ -832,13 +854,13 @@ TerrainChange Terrain::ApplyBrush(const TerrainBrush &brush) { } if (count > 0) { const f32 amount = - std::clamp(brush.strength * influence, 0.0f, 1.0f); + rx::Clamp(brush.strength * influence, 0.0f, 1.0f); new_value.height += (total / count - old_value.height) * amount; } break; } case TerrainBrushMode::kFlatten: { - const f32 amount = std::clamp(brush.strength * influence, 0.0f, 1.0f); + const f32 amount = rx::Clamp(brush.strength * influence, 0.0f, 1.0f); const f32 target = brush.flatten_target - desc_.origin.y; new_value.height += (target - old_value.height) * amount; break; @@ -846,11 +868,11 @@ TerrainChange Terrain::ApplyBrush(const TerrainBrush &brush) { case TerrainBrushMode::kPaintLayer: new_value.weights = PaintWeights(old_value.weights, brush.layer, - std::clamp(brush.strength * influence, 0.0f, 1.0f), + rx::Clamp(brush.strength * influence, 0.0f, 1.0f), ActiveLayerCount(desc_)); break; } - if (!std::isfinite(new_value.height) || new_value == old_value) + if (!::isfinite(new_value.height) || new_value == old_value) continue; change.samples.push_back({tile.key, index, old_value, new_value}); } @@ -863,24 +885,24 @@ TerrainChange Terrain::ApplyBrush(const TerrainBrush &brush) { // even though none of their authored samples changed. base::Vector normal_dependents; auto neighbor = [&](TerrainTileKey key, i32 dx, - i32 dz) -> std::optional { + i32 dz) -> base::Optional { const i64 x = static_cast(key.x) + dx; const i64 z = static_cast(key.z) + dz; - if (x < std::numeric_limits::min() || - x > std::numeric_limits::max() || - z < std::numeric_limits::min() || - z > std::numeric_limits::max()) { - return std::nullopt; + if (x < base::MinMax::min() || + x > base::MinMax::max() || + z < base::MinMax::min() || + z > base::MinMax::max()) { + return base::nullopt; } return TerrainTileKey{static_cast(x), static_cast(z)}; }; for (const TerrainSampleChange& sample : change.samples) { const u32 local_x = sample.sample % side; const u32 local_z = sample.sample / side; - const std::optional west = neighbor(sample.tile, -1, 0); - const std::optional east = neighbor(sample.tile, 1, 0); - const std::optional south = neighbor(sample.tile, 0, -1); - const std::optional north = neighbor(sample.tile, 0, 1); + const base::Optional west = neighbor(sample.tile, -1, 0); + const base::Optional east = neighbor(sample.tile, 1, 0); + const base::Optional south = neighbor(sample.tile, 0, -1); + const base::Optional north = neighbor(sample.tile, 0, 1); if (local_x == 1 && west && FindTile(*west)) normal_dependents.push_back(*west); if (local_x + 1 == quads && east && FindTile(*east)) normal_dependents.push_back(*east); if (local_z == 1 && south && FindTile(*south)) normal_dependents.push_back(*south); @@ -890,10 +912,10 @@ TerrainChange Terrain::ApplyBrush(const TerrainBrush &brush) { // border copy reaches the diagonal through the pushes above, but in sparse // layouts (corner tile present, edge tile missing) that indirection breaks, // so mark the diagonal directly. - const std::optional southwest = neighbor(sample.tile, -1, -1); - const std::optional southeast = neighbor(sample.tile, 1, -1); - const std::optional northwest = neighbor(sample.tile, -1, 1); - const std::optional northeast = neighbor(sample.tile, 1, 1); + const base::Optional southwest = neighbor(sample.tile, -1, -1); + const base::Optional southeast = neighbor(sample.tile, 1, -1); + const base::Optional northwest = neighbor(sample.tile, -1, 1); + const base::Optional northeast = neighbor(sample.tile, 1, 1); if (((local_x == 1 && local_z == 0) || (local_x == 0 && local_z == 1)) && southwest && FindTile(*southwest)) normal_dependents.push_back(*southwest); @@ -911,9 +933,11 @@ TerrainChange Terrain::ApplyBrush(const TerrainBrush &brush) { normal_dependents.push_back(*northeast); } for (TerrainTileKey key : normal_dependents) change.dirty_tiles.push_back(key); - std::sort(change.dirty_tiles.begin(), change.dirty_tiles.end(), KeyLess); + // A key is all its value, so equal keys are identical and any correct sort + // agrees. + base::Sort(change.dirty_tiles.begin(), change.dirty_tiles.end(), KeyLess); change.dirty_tiles.erase( - std::unique(change.dirty_tiles.begin(), change.dirty_tiles.end()), + base::Unique(change.dirty_tiles.begin(), change.dirty_tiles.end()), change.dirty_tiles.end()); if (!change.empty() && !ApplyChange(change)) return TerrainChange{}; @@ -938,16 +962,18 @@ bool Terrain::SetChangeState(const TerrainChange &change, bool use_new) { previous = sample; has_previous = true; TerrainTile *tile = FindTileMutable(sample.tile); + const TerrainTileKey *dirty = + base::LowerBound(change.dirty_tiles.begin(), change.dirty_tiles.end(), + sample.tile, KeyLess); if (!tile || sample.sample >= tile->heights.size() || - !std::binary_search(change.dirty_tiles.begin(), - change.dirty_tiles.end(), sample.tile, KeyLess)) { + dirty == change.dirty_tiles.end() || KeyLess(sample.tile, *dirty)) { return false; } const TerrainSampleState &wanted = use_new ? sample.new_value : sample.old_value; const TerrainSampleState &expected = use_new ? sample.old_value : sample.new_value; - if (!std::isfinite(wanted.height) || + if (!::isfinite(wanted.height) || !IsNormalized(wanted.weights, layer_count) || tile->heights[sample.sample] != expected.height || tile->weights[sample.sample] != expected.weights) { @@ -988,13 +1014,10 @@ bool MergeTerrainChanges(TerrainChange *stroke, const TerrainChange &dab) { return true; } if (stroke->terrain != dab.terrain || - !std::is_sorted(stroke->samples.begin(), stroke->samples.end(), - ChangeLess) || - !std::is_sorted(dab.samples.begin(), dab.samples.end(), ChangeLess) || - !std::is_sorted(stroke->dirty_tiles.begin(), stroke->dirty_tiles.end(), - KeyLess) || - !std::is_sorted(dab.dirty_tiles.begin(), dab.dirty_tiles.end(), - KeyLess)) { + !IsSorted(stroke->samples, ChangeLess) || + !IsSorted(dab.samples, ChangeLess) || + !IsSorted(stroke->dirty_tiles, KeyLess) || + !IsSorted(dab.dirty_tiles, KeyLess)) { return false; } @@ -1023,10 +1046,24 @@ bool MergeTerrainChanges(TerrainChange *stroke, const TerrainChange &dab) { merged.dirty_tiles.reserve(stroke->dirty_tiles.size() + dab.dirty_tiles.size()); - std::set_union(stroke->dirty_tiles.begin(), stroke->dirty_tiles.end(), - dab.dirty_tiles.begin(), dab.dirty_tiles.end(), - std::back_inserter(merged.dirty_tiles), KeyLess); - *stroke = std::move(merged); + // std::set_union: on equal keys the stroke's copy is kept. + size_t old_tile = 0; + size_t dab_tile = 0; + while (old_tile < stroke->dirty_tiles.size()) { + if (dab_tile == dab.dirty_tiles.size()) { + merged.dirty_tiles.push_back(stroke->dirty_tiles[old_tile++]); + } else if (KeyLess(dab.dirty_tiles[dab_tile], + stroke->dirty_tiles[old_tile])) { + merged.dirty_tiles.push_back(dab.dirty_tiles[dab_tile++]); + } else { + if (!KeyLess(stroke->dirty_tiles[old_tile], dab.dirty_tiles[dab_tile])) + ++dab_tile; + merged.dirty_tiles.push_back(stroke->dirty_tiles[old_tile++]); + } + } + while (dab_tile < dab.dirty_tiles.size()) + merged.dirty_tiles.push_back(dab.dirty_tiles[dab_tile++]); + *stroke = base::move(merged); return true; } diff --git a/engine/terrain/terrain.h b/engine/terrain/terrain.h index 8508f24f..622aa86d 100644 --- a/engine/terrain/terrain.h +++ b/engine/terrain/terrain.h @@ -1,17 +1,16 @@ #ifndef RX_TERRAIN_TERRAIN_H_ #define RX_TERRAIN_TERRAIN_H_ -#include -#include -#include -#include -#include -#include +#include #include #include "asset/asset_id.h" #include "asset/mesh.h" +#include "base/containers/array.h" +#include "base/containers/span.h" +#include "base/optional.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/math.h" #include "scene/world_streaming.h" @@ -23,20 +22,19 @@ struct TerrainTileKey { i32 z = 0; bool operator==(const TerrainTileKey &) const = default; - auto operator<=>(const TerrainTileKey &) const = default; }; struct TerrainWeights { - std::array rgba = {255, 0, 0, 0}; + base::Array rgba = {255, 0, 0, 0}; bool operator==(const TerrainWeights &) const = default; }; struct TerrainLayer { - std::string name; + base::String name; asset::AssetId albedo; asset::AssetId normal; - std::array debug_rgba = {96, 128, 64, 255}; + base::Array debug_rgba = {96, 128, 64, 255}; bool operator==(const TerrainLayer &) const = default; }; @@ -114,8 +112,8 @@ class RX_TERRAIN_EXPORT Terrain { explicit Terrain(TerrainDesc desc); const TerrainDesc &desc() const { return desc_; } - std::span tiles() const { - return {tiles_.data(), tiles_.size()}; + base::Span tiles() const { + return base::Span(tiles_.data(), tiles_.size()); } u32 samples_per_side() const { return desc_.tile_quads + 1; } @@ -123,12 +121,12 @@ class RX_TERRAIN_EXPORT Terrain { // Replaces a tile and makes its supplied border authoritative over any // existing cardinal or diagonal neighbors. Empty weights select layer zero. - bool AddOrReplaceTile(TerrainTileKey key, std::span heights, - std::span weights = {}); + bool AddOrReplaceTile(TerrainTileKey key, base::Span heights, + base::Span weights = {}); - std::optional SampleHeight(f32 world_x, f32 world_z) const; + base::Optional SampleHeight(f32 world_x, f32 world_z) const; asset::AssetId TileAssetId(TerrainTileKey key) const; - std::optional TileRegion(TerrainTileKey key, + base::Optional TileRegion(TerrainTileKey key, u32 channels = ~u32{0}, i32 priority = 0) const; @@ -138,11 +136,11 @@ class RX_TERRAIN_EXPORT Terrain { base::Vector *regions, u32 channels = ~u32{0}, i32 priority = 0) const; - std::optional BuildTileMesh(TerrainTileKey key, + base::Optional BuildTileMesh(TerrainTileKey key, asset::AssetId material) const; - std::optional + base::Optional Raycast(Vec3 origin, Vec3 direction, - f32 maximum_distance = std::numeric_limits::infinity()) const; + f32 maximum_distance = INFINITY) const; TerrainChange ApplyBrush(const TerrainBrush &brush); bool ApplyChange(const TerrainChange &change); @@ -150,14 +148,14 @@ class RX_TERRAIN_EXPORT Terrain { private: TerrainTile *FindTileMutable(TerrainTileKey key); - std::optional GridHeight(i64 grid_x, i64 grid_z) const; + base::Optional GridHeight(i64 grid_x, i64 grid_z) const; void RecalculateBounds(TerrainTile *tile); bool SetChangeState(const TerrainChange &change, bool use_new); TerrainDesc desc_; base::Vector tiles_; - friend bool LoadTerrain(const std::string &, Terrain *, std::string *); + friend bool LoadTerrain(const base::String &, Terrain *, base::String *); }; // Appends a later, already-applied dab to a stroke. Repeated samples retain the @@ -166,11 +164,11 @@ RX_TERRAIN_EXPORT bool MergeTerrainChanges(TerrainChange *stroke, const TerrainChange &dab); RX_TERRAIN_EXPORT bool SaveTerrain(const Terrain &terrain, - const std::string &file_path, - std::string *error = nullptr); -RX_TERRAIN_EXPORT bool LoadTerrain(const std::string &file_path, + const base::String &file_path, + base::String *error = nullptr); +RX_TERRAIN_EXPORT bool LoadTerrain(const base::String &file_path, Terrain *terrain, - std::string *error = nullptr); + base::String *error = nullptr); } // namespace rx::terrain diff --git a/engine/terrain/terrain_io.cc b/engine/terrain/terrain_io.cc index 84b27d0b..312510b3 100644 --- a/engine/terrain/terrain_io.cc +++ b/engine/terrain/terrain_io.cc @@ -1,20 +1,14 @@ +#include "base/containers/span.h" +#include "base/memory/move.h" +#include "base/numeric_limits.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/scalar.h" #include "terrain/terrain.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(_WIN32) -#define WIN32_LEAN_AND_MEAN -#include -#endif +#include + +#include "core/file_system.h" namespace rx::terrain { namespace { @@ -28,9 +22,9 @@ constexpr u32 kMaximumTiles = 1'000'000; constexpr u64 kMaximumTotalSamples = 64'000'000; constexpr u64 kMaximumFileBytes = 2ull * 1024 * 1024 * 1024; -void SetError(std::string *error, std::string message) { +void SetError(base::String *error, base::String message) { if (error) - *error = std::move(message); + *error = base::move(message); } void AppendU8(base::Vector *bytes, u8 value) { bytes->push_back(value); } @@ -51,15 +45,15 @@ void AppendU64(base::Vector *bytes, u64 value) { } void AppendF32(base::Vector *bytes, f32 value) { - AppendU32(bytes, std::bit_cast(value)); + AppendU32(bytes, rx::BitCast(value)); } -void AppendString(base::Vector *bytes, std::string_view value) { +void AppendString(base::Vector *bytes, base::StringRef value) { AppendU32(bytes, static_cast(value.size())); bytes->insert(bytes->end(), value.begin(), value.end()); } -u64 Checksum(std::span bytes) { +u64 Checksum(base::Span bytes) { u64 hash = 0xcbf29ce484222325ull; for (u8 byte : bytes) { hash ^= byte; @@ -88,24 +82,24 @@ bool GetSampleCount(u32 quads, u32 *count) { return false; const u64 side = static_cast(quads) + 1; const u64 samples = side * side; - if (samples > std::numeric_limits::max()) + if (samples > base::MinMax::max()) return false; *count = static_cast(samples); return true; } bool ValidateTerrain(const Terrain &terrain, u32 *sample_count, - std::string *error) { + base::String *error) { const TerrainDesc &desc = terrain.desc(); - if (!desc.id || !std::isfinite(desc.origin.x) || !std::isfinite(desc.origin.y) || - !std::isfinite(desc.origin.z) || !std::isfinite(desc.sample_spacing) || + if (!desc.id || !::isfinite(desc.origin.x) || !::isfinite(desc.origin.y) || + !::isfinite(desc.origin.z) || !::isfinite(desc.sample_spacing) || desc.sample_spacing <= 0 || !GetSampleCount(desc.tile_quads, sample_count)) { SetError(error, "terrain has invalid dimensions or non-finite metadata"); return false; } const f32 tile_width = desc.tile_quads * desc.sample_spacing; - if (!std::isfinite(tile_width)) { + if (!::isfinite(tile_width)) { SetError(error, "terrain tile width is not finite"); return false; } @@ -138,7 +132,7 @@ bool ValidateTerrain(const Terrain &terrain, u32 *sample_count, previous = tile.key; has_previous = true; for (u32 i = 0; i < *sample_count; ++i) { - if (!std::isfinite(tile.heights[i]) || + if (!::isfinite(tile.heights[i]) || !IsNormalized(tile.weights[i], static_cast(desc.layers.size()))) { SetError(error, "terrain tile contains invalid height or weight data"); @@ -151,7 +145,7 @@ bool ValidateTerrain(const Terrain &terrain, u32 *sample_count, class Reader { public: - explicit Reader(std::span bytes) : bytes_(bytes) {} + explicit Reader(base::Span bytes) : bytes_(bytes) {} bool ReadU8(u8 *value) { if (remaining() < 1) @@ -193,7 +187,7 @@ class Reader { u32 bits = 0; if (!ReadU32(&bits)) return false; - *value = std::bit_cast(bits); + *value = rx::BitCast(bits); return true; } @@ -201,11 +195,11 @@ class Reader { u32 bits = 0; if (!ReadU32(&bits)) return false; - *value = std::bit_cast(bits); + *value = rx::BitCast(bits); return true; } - bool ReadString(u32 maximum_size, std::string *value) { + bool ReadString(u32 maximum_size, base::String *value) { u32 size = 0; if (!ReadU32(&size) || size > maximum_size || remaining() < size) return false; @@ -218,52 +212,36 @@ class Reader { size_t remaining() const { return bytes_.size() - offset_; } private: - std::span bytes_; + base::Span bytes_; size_t offset_ = 0; }; -bool ReadFile(const std::string &file_path, base::Vector *bytes, - std::string *error) { - std::ifstream input(file_path, std::ios::binary | std::ios::ate); - if (!input) { +bool ReadFile(const base::String &file_path, base::Vector *bytes, + base::String *error) { + base::File input = + fs::OpenFile(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ); + if (!input.IsValid()) { SetError(error, "cannot open terrain file for reading"); return false; } - const std::streamoff size = input.tellg(); - if (size < 0 || static_cast(size) > kMaximumFileBytes) { + base::File::Info info; + if (!input.GetInfo(&info) || info.size < 0 || + static_cast(info.size) > kMaximumFileBytes) { SetError(error, "terrain file size is invalid"); return false; } - bytes->resize(static_cast(size)); - input.seekg(0); - if (!bytes->empty()) { - input.read(reinterpret_cast(bytes->data()), - static_cast(bytes->size())); - } - if (!input) { + bytes->resize(static_cast(info.size)); + if (!fs::ReadAt(input, 0, base::Span(bytes->data(), bytes->size()))) { SetError(error, "terrain file is truncated"); return false; } return true; } -bool ReplaceFile(const std::filesystem::path& temporary, - const std::filesystem::path& target, - std::error_code* error) { - std::filesystem::rename(temporary, target, *error); -#if defined(_WIN32) - if (*error && MoveFileExW(temporary.c_str(), target.c_str(), - MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { - error->clear(); - } -#endif - return !*error; -} - } // namespace -bool SaveTerrain(const Terrain &terrain, const std::string &file_path, - std::string *error) { +bool SaveTerrain(const Terrain &terrain, const base::String &file_path, + base::String *error) { if (error) error->clear(); u32 sample_count = 0; @@ -271,7 +249,7 @@ bool SaveTerrain(const Terrain &terrain, const std::string &file_path, return false; base::Vector bytes; - bytes.insert(bytes.end(), std::begin(kMagic), std::end(kMagic)); + bytes.insert(bytes.end(), kMagic, kMagic + sizeof(kMagic)); AppendU32(&bytes, kVersion); const TerrainDesc &desc = terrain.desc(); AppendU64(&bytes, desc.id.hash); @@ -291,14 +269,19 @@ bool SaveTerrain(const Terrain &terrain, const std::string &file_path, } for (const TerrainTile &tile : terrain.tiles()) { - AppendU32(&bytes, std::bit_cast(tile.key.x)); - AppendU32(&bytes, std::bit_cast(tile.key.z)); + AppendU32(&bytes, rx::BitCast(tile.key.x)); + AppendU32(&bytes, rx::BitCast(tile.key.z)); AppendU64(&bytes, tile.revision); - const auto [minimum_it, maximum_it] = - std::minmax_element(tile.heights.begin(), tile.heights.end()); + // std::minmax_element's picks: the first smallest, the last largest. + const f32* minimum_it = tile.heights.begin(); + const f32* maximum_it = tile.heights.begin(); + for (const f32* it = tile.heights.begin(); it != tile.heights.end(); ++it) { + if (*it < *minimum_it) minimum_it = it; + if (!(*it < *maximum_it)) maximum_it = it; + } const f32 minimum = *minimum_it; const f32 range = *maximum_it - minimum; - if (!std::isfinite(range)) { + if (!::isfinite(range)) { SetError(error, "terrain tile height range is not finite"); return false; } @@ -312,7 +295,7 @@ bool SaveTerrain(const Terrain &terrain, const std::string &file_path, const double normalized = (static_cast(height) - minimum) / static_cast(range); quantized = static_cast( - std::clamp(std::llround(normalized * 65535.0), 0ll, 65535ll)); + rx::Clamp(::llround(normalized * 65535.0), 0ll, 65535ll)); } AppendU16(&bytes, quantized); } @@ -323,37 +306,33 @@ bool SaveTerrain(const Terrain &terrain, const std::string &file_path, } AppendU64(&bytes, Checksum(bytes)); - const std::filesystem::path target(file_path); - std::filesystem::path temporary = target; - temporary += ".tmp"; - std::error_code filesystem_error; - std::filesystem::remove(temporary, filesystem_error); - std::ofstream output(temporary, std::ios::binary | std::ios::trunc); - if (!output) { + const base::String temporary = file_path + ".tmp"; + fs::Remove(temporary); + base::File output = + fs::OpenFile(temporary, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); + if (!output.IsValid()) { SetError(error, "cannot open terrain temporary file for writing"); return false; } - output.write(reinterpret_cast(bytes.data()), - static_cast(bytes.size())); - output.flush(); - const bool wrote = static_cast(output); - output.close(); + const bool wrote = fs::WriteAll(output, base::Span(bytes.data(), bytes.size())) && + output.Flush(); + output.Close(); if (!wrote) { - std::filesystem::remove(temporary, filesystem_error); + fs::Remove(temporary); SetError(error, "failed writing terrain temporary file"); return false; } - if (!ReplaceFile(temporary, target, &filesystem_error)) { - std::filesystem::remove(temporary, filesystem_error); + if (!fs::Rename(temporary, file_path)) { + fs::Remove(temporary); SetError(error, "failed renaming terrain temporary file"); return false; } return true; } -bool LoadTerrain(const std::string &file_path, Terrain *terrain, - std::string *error) { +bool LoadTerrain(const base::String &file_path, Terrain *terrain, + base::String *error) { if (error) error->clear(); if (!terrain) { @@ -374,12 +353,12 @@ bool LoadTerrain(const std::string &file_path, Terrain *terrain, << shift; } if (stored_checksum != - Checksum(std::span(bytes.data(), payload_size))) { + Checksum(base::Span(bytes.data(), payload_size))) { SetError(error, "terrain file checksum mismatch"); return false; } - Reader reader(std::span(bytes.data(), payload_size)); + Reader reader(base::Span(bytes.data(), payload_size)); for (u8 expected : kMagic) { u8 actual = 0; if (!reader.ReadU8(&actual) || actual != expected) { @@ -405,12 +384,12 @@ bool LoadTerrain(const std::string &file_path, Terrain *terrain, return false; } u32 sample_count = 0; - if (!desc.id || !std::isfinite(desc.origin.x) || - !std::isfinite(desc.origin.y) || - !std::isfinite(desc.origin.z) || !std::isfinite(desc.sample_spacing) || + if (!desc.id || !::isfinite(desc.origin.x) || + !::isfinite(desc.origin.y) || + !::isfinite(desc.origin.z) || !::isfinite(desc.sample_spacing) || desc.sample_spacing <= 0 || !GetSampleCount(desc.tile_quads, &sample_count) || - !std::isfinite(desc.tile_quads * desc.sample_spacing) || + !::isfinite(desc.tile_quads * desc.sample_spacing) || layer_count == 0 || layer_count > kMaximumLayers || tile_count > kMaximumTiles || static_cast(tile_count) * sample_count > kMaximumTotalSamples) { @@ -434,7 +413,7 @@ bool LoadTerrain(const std::string &file_path, Terrain *terrain, } } - Terrain decoded(std::move(desc)); + Terrain decoded(base::move(desc)); base::Vector revisions; revisions.reserve(tile_count); TerrainTileKey previous; @@ -453,8 +432,8 @@ bool LoadTerrain(const std::string &file_path, Terrain *terrain, SetError(error, "terrain tile header is truncated"); return false; } - if ((has_previous && !KeyLess(previous, key)) || !std::isfinite(minimum) || - !std::isfinite(range) || range < 0 || !std::isfinite(minimum + range) || + if ((has_previous && !KeyLess(previous, key)) || !::isfinite(minimum) || + !::isfinite(range) || range < 0 || !::isfinite(minimum + range) || stored_samples != sample_count || stored_weight_bytes != sample_count * 4 || reader.remaining() < static_cast(sample_count) * 6) { @@ -472,7 +451,7 @@ bool LoadTerrain(const std::string &file_path, Terrain *terrain, return false; } height = minimum + range * (static_cast(quantized) / 65535.0f); - if (!std::isfinite(height)) { + if (!::isfinite(height)) { SetError(error, "terrain height data is not finite"); return false; } @@ -502,7 +481,7 @@ bool LoadTerrain(const std::string &file_path, Terrain *terrain, } for (u32 i = 0; i < tile_count; ++i) decoded.tiles_[i].revision = revisions[i]; - *terrain = std::move(decoded); + *terrain = base::move(decoded); return true; } diff --git a/engine/ui/CMakeLists.txt b/engine/ui/CMakeLists.txt index f2e022ce..e812dda9 100644 --- a/engine/ui/CMakeLists.txt +++ b/engine/ui/CMakeLists.txt @@ -25,6 +25,9 @@ if(NOT TARGET ultragui) set(ULTRAGUI_VIDEO OFF CACHE BOOL "" FORCE) set(ULTRAGUI_PLATFORM_GLFW OFF CACHE BOOL "" FORCE) set(ULTRAGUI_BUILD_SHADERS OFF CACHE BOOL "" FORCE) + # No STL, no exceptions: ugui's containers are base's (ugui::String is + # base::String), built against rx's own equilibrium::base. + set(ULTRAGUI_USE_BASE ON CACHE BOOL "" FORCE) set(ULTRAGUI_PLATFORM_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/ugui_platform.cc" CACHE FILEPATH "" FORCE) set(ULTRAGUI_RHI_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/ugui_rhi.cc" diff --git a/engine/ui/splash.cc b/engine/ui/splash.cc index cbdd986a..26eaefb4 100644 --- a/engine/ui/splash.cc +++ b/engine/ui/splash.cc @@ -1,9 +1,8 @@ #include "ui/splash.h" -#include -#include -#include -#include +#include +#include +#include #include #include @@ -12,6 +11,10 @@ #include #include +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/optional.h" +#include "core/file_system.h" #include "core/log.h" #include "render/rhi/vulkan_interop.h" #include "ui/brand/rx_engine_svg.h" @@ -21,8 +24,6 @@ namespace rx::ui { namespace { -namespace fs = std::filesystem; - // Fractions of the window the logo is allowed to take. Height leads (the plate // reads as a band across the middle); the width clamp keeps the wordmark off // the edges of a wide or ultrawide window. @@ -44,8 +45,7 @@ const char* SystemFont() { "/run/current-system/sw/share/X11/fonts/DejaVuSans.ttf", }; for (const char* candidate : kCandidates) { - std::error_code ec; - if (fs::exists(candidate, ec)) return candidate; + if (rx::fs::Exists(candidate)) return candidate; } return nullptr; } @@ -80,9 +80,9 @@ void TrimTransparentBorder(ugui::SvgImage& image) { for (u32 y = 0; y < out_h; ++y) { const u8* src = &image.pixels[((y + min_y) * w + min_x) * 4]; u8* dst = &image.pixels[y * out_w * 4]; - std::memmove(dst, src, static_cast(out_w) * 4); + base::MemMove(dst, src, static_cast(out_w) * 4); } - image.pixels.resize(static_cast(out_w) * out_h * 4); + image.pixels.resize(static_cast(out_w) * out_h * 4); image.width = out_w; image.height = out_h; } @@ -153,7 +153,7 @@ bool Splash::Initialize(Window& window, render::Renderer& renderer, asset::Vfs& } bool Splash::LoadFont(asset::Vfs& vfs) { - if (std::optional> bytes = vfs.Read(kFontAsset)) { + if (base::Optional> bytes = vfs.Read(kFontAsset)) { const ugui::FontHandle font = ui_->LoadFontMemory( reinterpret_cast(bytes->data()), bytes->size()); if (font != ugui::kInvalidFont) { @@ -266,7 +266,7 @@ void Splash::BuildDocument() { // box has to hold the aspect of the upload. Fractional px, because rounding // the two sides apart skews it. flex-shrink: 0, because a column short on // room takes the height back and leaves the width alone. - std::snprintf(doc, sizeof(doc), + ::snprintf(doc, sizeof(doc), "panel splash_root {\n" " width: %d; height: %d; margin: %d 0 0 %d;\n" " background: #f4f4f5;\n" @@ -338,7 +338,7 @@ void Splash::Draw(render::FrameView& view) { // underneath still records: drop it and the fade dissolves to the bare scene // rather than to the application's first screen, which on a game is a world // that has not finished streaming. - view.hud_draw = [this, under = std::move(view.hud_draw)](render::CommandList& cmd) { + view.hud_draw = [this, under = base::move(view.hud_draw)](render::CommandList& cmd) { if (under) under(cmd); if (draw_data_) backend_.Render(*draw_data_, render::GetVkCommandBuffer(cmd)); }; diff --git a/engine/ui/splash.h b/engine/ui/splash.h index 0db2cc23..2fa7d647 100644 --- a/engine/ui/splash.h +++ b/engine/ui/splash.h @@ -1,11 +1,11 @@ #ifndef RX_UI_SPLASH_H_ #define RX_UI_SPLASH_H_ -#include #include #include "asset/vfs.h" +#include "base/optional.h" #include "core/export.h" #include "core/types.h" #include "core/window.h" @@ -90,8 +90,8 @@ class RX_UI_EXPORT Splash { // the thread's active WidgetRegistry, and `park_` hands that slot straight // back to whatever held it (see the note in Initialize). Declared in this // order so park_ unwinds before the context that displaced it. - std::optional ui_; - std::optional park_; + base::Optional ui_; + base::Optional park_; GuiRenderBackend backend_; UguiHostState host_state_; const ugui::DrawData* draw_data_ = nullptr; diff --git a/engine/ui/ugui_backend.cc b/engine/ui/ugui_backend.cc index 6b0dcc05..43f0218c 100644 --- a/engine/ui/ugui_backend.cc +++ b/engine/ui/ugui_backend.cc @@ -1,10 +1,11 @@ #include "ui/ugui_backend.h" -#include -#include +#include #include +#include "base/memory/mem_ops.h" +#include "core/scalar.h" #include "render/util/shader_util.h" #include "shaders/ugui_frost_ps_hlsl.h" #include "shaders/ugui_frost_vs_hlsl.h" @@ -78,13 +79,13 @@ void GuiRenderBackend::UploadBuffer(GpuBuffer& b, VkBufferUsageFlags usage, cons if (bytes == 0) return; if (b.capacity < bytes) { DestroyBuffer(b); - VkDeviceSize cap = std::max(bytes, 4096); + VkDeviceSize cap = rx::Max(bytes, 4096); CreateBuffer(cap, usage, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, b); } void* dst = nullptr; vkMapMemory(info_.device, b.memory, 0, bytes, 0, &dst); - std::memcpy(dst, src, static_cast(bytes)); + base::MemCopy(dst, src, static_cast(bytes)); vkUnmapMemory(info_.device, b.memory); } @@ -224,7 +225,7 @@ GuiRenderBackend::Texture GuiRenderBackend::MakeTexture(uint32_t w, uint32_t h, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, staging); void* data = nullptr; vkMapMemory(info_.device, staging.memory, 0, size, 0, &data); - std::memcpy(data, pixels, static_cast(size)); + base::MemCopy(data, pixels, static_cast(size)); vkUnmapMemory(info_.device, staging.memory); VkImageCreateInfo ici{.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; @@ -406,7 +407,8 @@ void GuiRenderBackend::Shutdown() { DestroyBuffer(f.text_idx); } frames_.clear(); - for (auto& kv : user_textures_) FreeTexture(kv.second.tex); + // Teardown only: the order textures are freed in has no effect. + for (auto kv : user_textures_) FreeTexture(kv.value.tex); user_textures_.clear(); FreeTexture(font_); FreeTexture(white_); @@ -502,21 +504,21 @@ void GuiRenderBackend::Render(const ugui::DrawData& dd, VkCommandBuffer cmd) { } else if (c.texture_id == ugui::kNullTextureId) { set = white_.set; } else { - auto it = user_textures_.find(c.texture_id); - set = it != user_textures_.end() ? it->second.tex.set : white_.set; + const UserTexture* user = user_textures_.find(c.texture_id); + set = user ? user->tex.set : white_.set; } if (set == VK_NULL_HANDLE) set = white_.set; vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout_, 0, 1, &set, 0, nullptr); VkRect2D scissor{}; - float x0 = std::max(0.0f, c.clip_rect.x) * sx; - float y0 = std::max(0.0f, c.clip_rect.y) * sy; - float x1 = std::max(0.0f, c.clip_rect.x + c.clip_rect.w) * sx; - float y1 = std::max(0.0f, c.clip_rect.y + c.clip_rect.h) * sy; + float x0 = rx::Max(0.0f, c.clip_rect.x) * sx; + float y0 = rx::Max(0.0f, c.clip_rect.y) * sy; + float x1 = rx::Max(0.0f, c.clip_rect.x + c.clip_rect.w) * sx; + float y1 = rx::Max(0.0f, c.clip_rect.y + c.clip_rect.h) * sy; scissor.offset = {static_cast(x0), static_cast(y0)}; - scissor.extent = {static_cast(std::max(0.0f, x1 - x0)), - static_cast(std::max(0.0f, y1 - y0))}; + scissor.extent = {static_cast(rx::Max(0.0f, x1 - x0)), + static_cast(rx::Max(0.0f, y1 - y0))}; vkCmdSetScissor(cmd, 0, 1, &scissor); GpuBuffer& vb = c.is_text ? fb.text_vtx : fb.quad_vtx; @@ -546,20 +548,20 @@ ugui::TextureId GuiRenderBackend::CreateTexture(uint32_t width, uint32_t height, } void GuiRenderBackend::UpdateTexture(ugui::TextureId id, const void* pixels) { - auto it = user_textures_.find(id); - if (it == user_textures_.end() || !pixels) return; - UserTexture& ut = it->second; + UserTexture* found = user_textures_.find(id); + if (!found || !pixels) return; + UserTexture& ut = *found; vkDeviceWaitIdle(info_.device); FreeTexture(ut.tex); ut.tex = MakeTexture(ut.width, ut.height, ut.fmt, ut.pixel_size, pixels, ut.sampler); } void GuiRenderBackend::DestroyTexture(ugui::TextureId id) { - auto it = user_textures_.find(id); - if (it == user_textures_.end()) return; + UserTexture* found = user_textures_.find(id); + if (!found) return; vkDeviceWaitIdle(info_.device); - FreeTexture(it->second.tex); - user_textures_.erase(it); + FreeTexture(found->tex); + user_textures_.erase(id); } } // namespace rx::ui diff --git a/engine/ui/ugui_backend.h b/engine/ui/ugui_backend.h index 4b8f5198..37af72cb 100644 --- a/engine/ui/ugui_backend.h +++ b/engine/ui/ugui_backend.h @@ -3,13 +3,13 @@ #include -#include -#include -#include +#include #include #include #include +#include "base/containers/unordered_map.h" +#include "base/containers/vector.h" namespace rx::ui { @@ -109,16 +109,16 @@ class GuiRenderBackend final : public ugui::TextureBackend { VkDescriptorPool descriptor_pool_ = VK_NULL_HANDLE; Texture white_{}; Texture font_{}; - std::unordered_map user_textures_; + base::UnorderedMap user_textures_; ugui::TextureId next_user_id_ = 1; - std::vector frames_; + base::Vector frames_; uint32_t frame_index_ = 0; // Backdrop-blur (frosted glass): the renderer hands a blurred copy of what is // behind the UI each frame; frost commands sample it via a per-frame set. VkImageView backdrop_view_ = VK_NULL_HANDLE; VkSampler backdrop_sampler_ = VK_NULL_HANDLE; - std::vector frost_sets_; + base::Vector frost_sets_; }; } // namespace rx::ui diff --git a/engine/ui/ugui_platform.cc b/engine/ui/ugui_platform.cc index 61fe3121..0e34e059 100644 --- a/engine/ui/ugui_platform.cc +++ b/engine/ui/ugui_platform.cc @@ -6,7 +6,7 @@ #include -#include +#include "base/time/time.h" // Quoted relative, not "ui/...": this file is compiled into the ultragui // library, which does not see rx's engine include root. @@ -17,7 +17,7 @@ namespace ugui { struct Platform::Impl { rx::ui::UguiHostState* host = nullptr; InputQueue queue; - std::chrono::steady_clock::time_point start{}; + base::TimeTicks start; }; Platform::Platform() : impl_(new Impl()) {} @@ -25,7 +25,7 @@ Platform::~Platform() { delete impl_; } bool Platform::Init(const WindowConfig& config) { impl_->host = static_cast(config.external_window); - impl_->start = std::chrono::steady_clock::now(); + impl_->start = base::TimeTicks::Now(); return true; } @@ -49,8 +49,7 @@ Vec2 Platform::framebuffer_size() const { return window_size(); } f32 Platform::dpi_scale() const { return impl_->host ? impl_->host->dpi_scale : 1.0f; } f64 Platform::time() const { - auto now = std::chrono::steady_clock::now(); - return std::chrono::duration(now - impl_->start).count(); + return (base::TimeTicks::Now() - impl_->start).InSecondsF(); } void* Platform::native_handle() const { return impl_->host; } diff --git a/engine/weather/weather.cc b/engine/weather/weather.cc index db8a7c5f..ac1c1edb 100644 --- a/engine/weather/weather.cc +++ b/engine/weather/weather.cc @@ -1,9 +1,10 @@ #include "weather/weather.h" -#include -#include -#include +#include +#include "base/algorithm.h" +#include "base/memory/move.h" +#include "core/scalar.h" #include "render/atmosphere/lightning_envelope.h" namespace rx::weather { @@ -38,12 +39,12 @@ constexpr f32 kStrikeMinRange = 100.0f; constexpr f32 kStrikeMaxRange = 300.0f; f32 Clamp01(f32 v) { - if (!std::isfinite(v)) return 0.0f; + if (!::isfinite(v)) return 0.0f; return v < 0.0f ? 0.0f : (v > 1.0f ? 1.0f : v); } f32 Lerpf(f32 a, f32 b, f32 t) { return a + (b - a) * t; } f32 LerpAngle(f32 a, f32 b, f32 t) { - f32 delta = std::fmod(b - a + kPi, kTwoPi); + f32 delta = ::fmod(b - a + kPi, kTwoPi); if (delta < 0.0f) delta += kTwoPi; return a + (delta - kPi) * t; @@ -87,62 +88,62 @@ WeatherSystem::WeatherSystem(u32 rng_seed) : rng_(rng_seed ? rng_seed : 1u) {} u32 WeatherSystem::AddState(const WeatherState &state) { WeatherState clean = state; auto finite_or = [](f32 value, f32 fallback) { - return std::isfinite(value) ? value : fallback; + return ::isfinite(value) ? value : fallback; }; clean.coverage = Clamp01(clean.coverage); clean.cloud_type = Clamp01(clean.cloud_type); clean.precipitation = Clamp01(clean.precipitation); clean.storminess = Clamp01(clean.storminess); clean.darkness = Clamp01(clean.darkness); - clean.density = std::clamp(finite_or(clean.density, 1.0f), 0.0f, 10.0f); + clean.density = rx::Clamp(finite_or(clean.density, 1.0f), 0.0f, 10.0f); clean.base_altitude = - std::clamp(finite_or(clean.base_altitude, 1800.0f), -100000.0f, 100000.0f); + rx::Clamp(finite_or(clean.base_altitude, 1800.0f), -100000.0f, 100000.0f); clean.top_altitude = - std::clamp(finite_or(clean.top_altitude, clean.base_altitude + 1.0f), + rx::Clamp(finite_or(clean.top_altitude, clean.base_altitude + 1.0f), clean.base_altitude + 1.0f, 200000.0f); - clean.wind_yaw = std::remainder(finite_or(clean.wind_yaw, 0.0f), kTwoPi); - clean.wind_speed = std::clamp(finite_or(clean.wind_speed, 0.0f), 0.0f, 200.0f); + clean.wind_yaw = ::remainder(finite_or(clean.wind_yaw, 0.0f), kTwoPi); + clean.wind_speed = rx::Clamp(finite_or(clean.wind_speed, 0.0f), 0.0f, 200.0f); clean.vertical_skew = - std::clamp(finite_or(clean.vertical_skew, 0.0f), -100000.0f, 100000.0f); - clean.turbulence = std::clamp(finite_or(clean.turbulence, 0.0f), 0.0f, 10.0f); + rx::Clamp(finite_or(clean.vertical_skew, 0.0f), -100000.0f, 100000.0f); + clean.turbulence = rx::Clamp(finite_or(clean.turbulence, 0.0f), 0.0f, 10.0f); clean.fog_density = Clamp01(clean.fog_density); - clean.fog_height = std::clamp(finite_or(clean.fog_height, 90.0f), 0.1f, 100000.0f); + clean.fog_height = rx::Clamp(finite_or(clean.fog_height, 90.0f), 0.1f, 100000.0f); clean.fog_churn = Clamp01(clean.fog_churn); clean.tornado_prone = Clamp01(clean.tornado_prone); clean.strike_min_range = - std::clamp(finite_or(clean.strike_min_range, kStrikeMinRange), 0.0f, 1000000.0f); + rx::Clamp(finite_or(clean.strike_min_range, kStrikeMinRange), 0.0f, 1000000.0f); clean.strike_max_range = - std::clamp(finite_or(clean.strike_max_range, kStrikeMaxRange), + rx::Clamp(finite_or(clean.strike_max_range, kStrikeMaxRange), clean.strike_min_range, 1000000.0f); clean.transition_seconds = - std::clamp(finite_or(clean.transition_seconds, 0.0f), 0.0f, 86400.0f); - clean.min_dwell = std::clamp(finite_or(clean.min_dwell, 0.01f), 0.01f, 86400.0f); + rx::Clamp(finite_or(clean.transition_seconds, 0.0f), 0.0f, 86400.0f); + clean.min_dwell = rx::Clamp(finite_or(clean.min_dwell, 0.01f), 0.01f, 86400.0f); clean.max_dwell = - std::clamp(finite_or(clean.max_dwell, clean.min_dwell), clean.min_dwell, 86400.0f); - clean.weight = std::clamp(finite_or(clean.weight, 0.0f), 0.0f, 1000000.0f); - clean.day_weight = std::clamp(finite_or(clean.day_weight, 0.0f), 0.0f, 1000000.0f); - clean.night_weight = std::clamp(finite_or(clean.night_weight, 0.0f), 0.0f, 1000000.0f); + rx::Clamp(finite_or(clean.max_dwell, clean.min_dwell), clean.min_dwell, 86400.0f); + clean.weight = rx::Clamp(finite_or(clean.weight, 0.0f), 0.0f, 1000000.0f); + clean.day_weight = rx::Clamp(finite_or(clean.day_weight, 0.0f), 0.0f, 1000000.0f); + clean.night_weight = rx::Clamp(finite_or(clean.night_weight, 0.0f), 0.0f, 1000000.0f); states_.push_back(clean); return static_cast(states_.size() - 1); } u32 WeatherSystem::AddRegion(const WeatherRegion ®ion) { WeatherRegion clean = region; - if (!std::isfinite(clean.min_xz.x) || !std::isfinite(clean.min_xz.y) || - !std::isfinite(clean.max_xz.x) || !std::isfinite(clean.max_xz.y)) { + if (!::isfinite(clean.min_xz.x) || !::isfinite(clean.min_xz.y) || + !::isfinite(clean.max_xz.x) || !::isfinite(clean.max_xz.y)) { clean.min_xz = {}; clean.max_xz = {}; } for (f32 &weight : clean.weights) { - weight = std::isfinite(weight) ? std::clamp(weight, 0.0f, 1000000.0f) + weight = ::isfinite(weight) ? rx::Clamp(weight, 0.0f, 1000000.0f) : 0.0f; } - regions_.push_back(std::move(clean)); + regions_.push_back(base::move(clean)); return static_cast(regions_.size() - 1); } void WeatherSystem::SetGroundHeight(GroundHeightFn fn) { - ground_ = fn ? std::move(fn) : GroundHeightFn([](f32, f32) { return 0.0f; }); + ground_ = fn ? base::move(fn) : GroundHeightFn([](f32, f32) { return 0.0f; }); } void WeatherSystem::ForceState(u32 state_index, f32 transition_seconds) { @@ -151,7 +152,7 @@ void WeatherSystem::ForceState(u32 state_index, f32 transition_seconds) { if (state_index >= states_.size()) { state_index = static_cast(states_.size() - 1); } - if (!std::isfinite(transition_seconds)) + if (!::isfinite(transition_seconds)) transition_seconds = 0.0f; forced_ = true; started_ = true; // a forced state suppresses the first-frame region auto-pick @@ -242,10 +243,10 @@ u32 WeatherSystem::PickNext(const Vec3 &player_pos, f32 time_of_day01) { // Day phase in [0,1]: 0 at midnight (fully night), 1 at noon (fully day), // smoothly through dawn/dusk. Biases each candidate's weight toward its // day_weight or night_weight. - f32 dayness = 0.5f - 0.5f * std::cos(kTwoPi * time_of_day01); + f32 dayness = 0.5f - 0.5f * ::cos(kTwoPi * time_of_day01); auto effective_weight = [&](u32 idx, f32 base) -> f32 { - if (!std::isfinite(base) || base <= 0.0f) + if (!::isfinite(base) || base <= 0.0f) return 0.0f; const WeatherState &s = states_[idx]; f32 mult = Lerpf(s.night_weight, s.day_weight, dayness); @@ -329,7 +330,7 @@ render::CloudscapeMapState WeatherSystem::MapOf(const WeatherState &s) const { // not the same thing as WeatherSettings::precipitation ("rain falling on the // player"). A stormy state whose rain stays kilometres away still needs its // storm cells in the deck, so storminess floors the map channel. - m.precipitation = std::max(s.precipitation, s.storminess * 0.7f); + m.precipitation = rx::Max(s.precipitation, s.storminess * 0.7f); return m; } @@ -357,7 +358,7 @@ void WeatherSystem::Compose() { f32 authored_fog = Lerpf(a.fog_density, b.fog_density, s); f32 precip_now = Lerpf(a.precipitation, b.precipitation, s); f32 mist = wetness_ * 0.35f * Clamp01(1.0f - precip_now * 2.0f); - cloudscape_.fog_density = Clamp01(std::max(authored_fog, mist)); + cloudscape_.fog_density = Clamp01(rx::Max(authored_fog, mist)); cloudscape_.fog_height = Lerpf(a.fog_height, b.fog_height, s); cloudscape_.fog_churn = Lerpf(a.fog_churn, b.fog_churn, s); // The shell tracks the class each state represents (a stratus ceiling is @@ -405,7 +406,7 @@ void WeatherSystem::IntegrateTornado(f32 dt, const Vec3 &player_pos, if (tornado_active_) { tornado_age_ += dt; if (anvil < 0.4f) - tornado_dur_ = std::min(tornado_dur_, tornado_age_ + 9.0f); + tornado_dur_ = rx::Min(tornado_dur_, tornado_age_ + 9.0f); // Envelope: touchdown ramp, sustained wander, rope-out. The funnel // travels downwind a little slower than the deck, with a lazy sideways // wobble so its track snakes instead of ruling a straight line. @@ -413,8 +414,8 @@ void WeatherSystem::IntegrateTornado(f32 dt, const Vec3 &player_pos, f32 out = 1.0f - Smoothstep((tornado_age_ - tornado_dur_ + 9.0f) / 9.0f); cloudscape_.tornado_strength = ramp * out; f32 speed = cloudscape_.wind_speed * 0.55f; - f32 wob = std::sin(tornado_age_ * 0.31f) * 0.6f; - Vec2 dir{std::cos(cloudscape_.wind_yaw), std::sin(cloudscape_.wind_yaw)}; + f32 wob = ::sin(tornado_age_ * 0.31f) * 0.6f; + Vec2 dir{::cos(cloudscape_.wind_yaw), ::sin(cloudscape_.wind_yaw)}; tornado_pos_.x += (dir.x - dir.y * wob) * speed * dt; tornado_pos_.y += (dir.y + dir.x * wob) * speed * dt; cloudscape_.tornado_pos = tornado_pos_; @@ -424,7 +425,7 @@ void WeatherSystem::IntegrateTornado(f32 dt, const Vec3 &player_pos, f32 u = NextF32(); if (u < 1e-4f) u = 1e-4f; - tornado_timer_ = -kTornadoMeanInterval * std::log(u); + tornado_timer_ = -kTornadoMeanInterval * ::log(u); } return; } @@ -436,7 +437,7 @@ void WeatherSystem::IntegrateTornado(f32 dt, const Vec3 &player_pos, if (tornado_timer_ > 0.0f) return; // Touch down upwind of the player so the track carries the funnel past. - Vec2 dir{std::cos(cloudscape_.wind_yaw), std::sin(cloudscape_.wind_yaw)}; + Vec2 dir{::cos(cloudscape_.wind_yaw), ::sin(cloudscape_.wind_yaw)}; f32 upwind = RandomRange(400.0f, 1100.0f); f32 crosswind = RandomRange(-350.0f, 350.0f); tornado_pos_ = Vec2{player_pos.x - dir.x * upwind - dir.y * crosswind, @@ -479,14 +480,14 @@ bool WeatherSystem::IntegrateLightning(f32 dt, const Vec3 &player_pos, f32 min_r = Lerpf(sa.strike_min_range, sb.strike_min_range, s); f32 max_r = Lerpf(sa.strike_max_range, sb.strike_max_range, s); if (max_r < min_r) - std::swap(min_r, max_r); + base::Swap(min_r, max_r); f32 ang = RandomRange(0.0f, kTwoPi); f32 range = RandomRange(min_r, max_r); - f32 sx = player_pos.x + std::cos(ang) * range; - f32 sz = player_pos.z + std::sin(ang) * range; + f32 sx = player_pos.x + ::cos(ang) * range; + f32 sz = player_pos.z + ::sin(ang) * range; f32 strike_ground = ground_(sx, sz); weather_.strike_pos = - Vec3{sx, std::isfinite(strike_ground) ? strike_ground : 0.0f, sz}; + Vec3{sx, ::isfinite(strike_ground) ? strike_ground : 0.0f, sz}; weather_.strike_seed = NextU32(); weather_.strike_energy = RandomRange(0.6f, 1.0f); weather_.strike_age = 0.0f; @@ -494,7 +495,7 @@ bool WeatherSystem::IntegrateLightning(f32 dt, const Vec3 &player_pos, f32 u = NextF32(); if (u < 1e-4f) u = 1e-4f; - strike_timer_ = -kStrikeMeanInterval * std::log(u); + strike_timer_ = -kStrikeMeanInterval * ::log(u); } } @@ -507,7 +508,7 @@ bool WeatherSystem::IntegrateLightning(f32 dt, const Vec3 &player_pos, if (weather_.strike_age >= 0.0f) { f32 dx = weather_.strike_pos.x - player_pos.x; f32 dz = weather_.strike_pos.z - player_pos.z; - f32 dist = std::sqrt(dx * dx + dz * dz); + f32 dist = ::sqrt(dx * dx + dz * dz); f32 near_w = 600.0f / (600.0f + dist); f32 falloff = 0.08f + 0.92f * near_w * near_w; weather_.lightning = render::LightningEnvelope( @@ -522,14 +523,14 @@ bool WeatherSystem::IntegrateLightning(f32 dt, const Vec3 &player_pos, void WeatherSystem::Update(f32 dt, const Vec3 &player_pos, f32 time_of_day01) { if (states_.empty()) return; // no states: leave the default outputs untouched - if (!std::isfinite(dt) || dt < 0.0f) + if (!::isfinite(dt) || dt < 0.0f) return; - if (!std::isfinite(player_pos.x) || !std::isfinite(player_pos.y) || - !std::isfinite(player_pos.z)) + if (!::isfinite(player_pos.x) || !::isfinite(player_pos.y) || + !::isfinite(player_pos.z)) return; - if (!std::isfinite(time_of_day01)) + if (!::isfinite(time_of_day01)) time_of_day01 = 0.0f; - dt = std::min(dt, 60.0f); + dt = rx::Min(dt, 60.0f); // First frame settles onto a region-valid state (unless a script forced one). if (!started_) { @@ -551,7 +552,7 @@ void WeatherSystem::Update(f32 dt, const Vec3 &player_pos, f32 time_of_day01) { bool first_slice = true; bool strike_spawned = false; do { - f32 slice = std::min(remaining, 0.1f); + f32 slice = rx::Min(remaining, 0.1f); f32 scheduler_left = slice; u32 boundaries = 0; while (scheduler_left > 0.0f && boundaries++ < 1024u) { @@ -560,12 +561,12 @@ void WeatherSystem::Update(f32 dt, const Vec3 &player_pos, f32 time_of_day01) { Settle(); continue; } - f32 left = std::max((1.0f - blend_) * blend_dur_, 0.0f); + f32 left = rx::Max((1.0f - blend_) * blend_dur_, 0.0f); if (left <= 1e-6f) { Settle(); continue; } - f32 step = std::min(scheduler_left, left); + f32 step = rx::Min(scheduler_left, left); blend_ += step / blend_dur_; scheduler_left -= step; if (blend_ >= 1.0f - 1e-6f) @@ -575,7 +576,7 @@ void WeatherSystem::Update(f32 dt, const Vec3 &player_pos, f32 time_of_day01) { if (forced_) break; if (dwell_left_ > 0.0f) { - f32 step = std::min(scheduler_left, dwell_left_); + f32 step = rx::Min(scheduler_left, dwell_left_); dwell_left_ -= step; scheduler_left -= step; if (scheduler_left <= 0.0f) @@ -592,7 +593,7 @@ void WeatherSystem::Update(f32 dt, const Vec3 &player_pos, f32 time_of_day01) { if (slice > 0.0f) { f32 yaw = cloudscape_.wind_yaw; f32 speed = cloudscape_.wind_speed; - map_offset_ += Vec2{std::cos(yaw), std::sin(yaw)} * (speed * slice); + map_offset_ += Vec2{::cos(yaw), ::sin(yaw)} * (speed * slice); cloudscape_.map_offset = map_offset_; IntegrateSurface(slice, weather_.precipitation, weather_.snow); @@ -609,7 +610,7 @@ void WeatherSystem::Update(f32 dt, const Vec3 &player_pos, f32 time_of_day01) { IntegrateTornado(slice, player_pos, cloudscape_.anvil); } f32 local_ground = ground_(player_pos.x, player_pos.z); - cloudscape_.fog_ground = std::isfinite(local_ground) ? local_ground : 0.0f; + cloudscape_.fog_ground = ::isfinite(local_ground) ? local_ground : 0.0f; remaining -= slice; first_slice = false; diff --git a/engine/weather/weather.h b/engine/weather/weather.h index 3cb078fc..02169091 100644 --- a/engine/weather/weather.h +++ b/engine/weather/weather.h @@ -18,8 +18,8 @@ #include -#include +#include "base/functional/function.h" #include "core/export.h" #include "core/math.h" // Vec2 (weather map XZ offset), Vec3 #include "core/types.h" @@ -124,7 +124,7 @@ class RX_WEATHER_EXPORT WeatherSystem { public: // Optional ground-height sampler: lightning strikes ask it for the ground y // at a world XZ. Defaults to the y=0 plane. - using GroundHeightFn = std::function; + using GroundHeightFn = base::Function; explicit WeatherSystem(u32 rng_seed = 1); diff --git a/engine/world/world_bake.cc b/engine/world/world_bake.cc index fe9437cb..56cd3a32 100644 --- a/engine/world/world_bake.cc +++ b/engine/world/world_bake.cc @@ -1,20 +1,28 @@ #include "world/world_bake.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include #include "asset/asset_database.h" #include "asset/asset_id.h" #include "asset/pack.h" #include "asset/vfs.h" +#include "base/algorithm.h" +#include "base/containers/span.h" +#include "base/memory/move.h" +#include "base/optional.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/format.h" +#include "core/scalar.h" #include "edit/reflect.h" #include "edit/scene_io.h" #include "scene/components.h" #include "world/world_format.h" #include "world/world_stream.h" +#include "core/sort.h" namespace rx::world { namespace { @@ -24,8 +32,8 @@ namespace { // than fail. Far beyond any real world: at 64 m cells this is 2^29 * 64 m. constexpr i64 kMaximumCellCoord = i64{1} << 29; -void SetError(std::string* error, std::string message) { - if (error) *error = std::move(message); +void SetError(base::String* error, base::String message) { + if (error) *error = base::move(message); } // One entity as the cook sees it: where it goes, and what it is made of. @@ -43,7 +51,7 @@ struct Authored { // ever classify as static decoration; the stable id replaces it, and carrying // both would mean the instance page's rows each needed an ECS row to hold one. bool IdentityOnly(const edit::ComponentDesc& desc) { - return std::strcmp(desc.name, "Guid") == 0; + return ::strcmp(desc.name, "Guid") == 0; } // The grid. Cells are ids on a 2D lattice over XZ (vertical extent is the @@ -63,7 +71,7 @@ u64 CellIdFor(i64 x, i64 z) { // entities binned into it - computing the two separately in different // precisions puts an AABB one square away from its own contents. i64 CellCoord(f32 value, f32 size) { - return static_cast(std::floor(static_cast(value) / size)); + return static_cast(::floor(static_cast(value) / size)); } f32 CellOrigin(i64 coord, f32 size) { return static_cast(static_cast(coord) * size); } @@ -75,11 +83,11 @@ f32 CellOrigin(i64 coord, f32 size) { return static_cast(static_cast(transform.position[axis]) / cell_size; - if (!(std::abs(quotient) < static_cast(kMaximumCellCoord))) return false; + if (!(::abs(quotient) < static_cast(kMaximumCellCoord))) return false; } return true; } @@ -110,7 +118,7 @@ u64 HashBytes(u64 hash, const void* data, size_t size) { // it makes the hash ambiguous: {"A", "BC"} and {"AB", "C"} feed it the same // sequence, so two cooks that cut the world differently would claim the same // bake id and each accept the other's saves. -u64 HashString(u64 hash, std::string_view value) { +u64 HashString(u64 hash, base::StringRef value) { const u64 size = value.size(); hash = HashBytes(hash, &size, sizeof(size)); return HashBytes(hash, value.data(), value.size()); @@ -125,28 +133,28 @@ u64 HashString(u64 hash, std::string_view value) { // actually writes. Only those: folding in the whole registry would move every // world's bake id whenever an unrelated component was added anywhere in the // engine, invalidating the saves the id exists to protect. -u64 HashCook(const std::string& scene_path, const WorldBakeOptions& options, - const base::Vector& schema) { - std::FILE* file = std::fopen(scene_path.c_str(), "rb"); +u64 HashCook(const base::String& scene_path, const WorldBakeOptions& options, + const base::Vector& schema) { + FILE* file = ::fopen(scene_path.c_str(), "rb"); if (!file) return 0; u64 hash = 0xcbf29ce484222325ull; u8 buffer[4096]; size_t read = 0; - while ((read = std::fread(buffer, 1, sizeof(buffer), file)) > 0) { + while ((read = ::fread(buffer, 1, sizeof(buffer), file)) > 0) { hash = HashBytes(hash, buffer, read); } - std::fclose(file); + ::fclose(file); hash = HashString(hash, options.name); hash = HashBytes(hash, &options.cell_size, sizeof(options.cell_size)); hash = HashBytes(hash, &options.skip_unknown, sizeof(options.skip_unknown)); const u64 instance_count = options.instance_components.size(); hash = HashBytes(hash, &instance_count, sizeof(instance_count)); - for (const std::string& component : options.instance_components) { + for (const base::String& component : options.instance_components) { hash = HashString(hash, component); } const u64 schema_count = schema.size(); hash = HashBytes(hash, &schema_count, sizeof(schema_count)); - for (const std::string& entry : schema) hash = HashString(hash, entry); + for (const base::String& entry : schema) hash = HashString(hash, entry); return hash == 0 ? 1 : hash; // 0 means "unreadable" } @@ -159,9 +167,9 @@ bool SameComponents(const Authored& a, const Authored& b) { } // The instance set the options ask for, with the default filled in. -base::Vector InstanceComponents(const WorldBakeOptions& options) { +base::Vector InstanceComponents(const WorldBakeOptions& options) { if (!options.instance_components.empty()) return options.instance_components; - base::Vector defaults; + base::Vector defaults; defaults.push_back("Transform"); defaults.push_back("Renderable"); return defaults; @@ -169,13 +177,13 @@ base::Vector InstanceComponents(const WorldBakeOptions& options) { } // namespace -std::string WorldNameForArchive(std::string_view archive_path) { +base::String WorldNameForArchive(base::StringRef archive_path) { const size_t slash = archive_path.find_last_of("/\\"); - const std::string_view file = - slash == std::string_view::npos ? archive_path : archive_path.substr(slash + 1); + const base::StringRef file = + slash == base::StringRef::npos ? archive_path : archive_path.substr(slash + 1); const size_t dot = file.find_last_of('.'); // A leading dot is the whole name of a dotfile, not an extension. - return std::string(dot == std::string_view::npos || dot == 0 ? file : file.substr(0, dot)); + return base::String(dot == base::StringRef::npos || dot == 0 ? file : file.substr(0, dot)); } const char* BakeRoleName(BakeRole role) { @@ -190,7 +198,7 @@ const char* BakeRoleName(BakeRole role) { BakeVerdict ClassifyForBake(ecs::World& world, ecs::Entity entity, const WorldBakeOptions& options) { BakeVerdict verdict; - const base::Vector instance_set = InstanceComponents(options); + const base::Vector instance_set = InstanceComponents(options); const scene::Transform* transform = world.Get(entity); if (!transform) { @@ -203,7 +211,7 @@ BakeVerdict ClassifyForBake(ecs::World& world, ecs::Entity entity, verdict.refusal = "has a Parent, which is an ecs handle no baked cell can carry"; return verdict; } - const f32 cell_size = options.cell_size > 0 && std::isfinite(options.cell_size) + const f32 cell_size = options.cell_size > 0 && ::isfinite(options.cell_size) ? options.cell_size : 64.0f; if (!InLattice(*transform, cell_size)) { @@ -230,7 +238,7 @@ BakeVerdict ClassifyForBake(ecs::World& world, ecs::Entity entity, if (!IdentityOnly(*desc)) { ++authored_count; bool named = false; - for (const std::string& want : instance_set) named |= want == desc->name; + for (const base::String& want : instance_set) named |= want == desc->name; only_instance_components &= named; } if (!Bakeable(*desc)) verdict.dropped.push_back(desc->name); @@ -241,13 +249,13 @@ BakeVerdict ClassifyForBake(ecs::World& world, ecs::Entity entity, return verdict; } -bool BakeWorld(const std::string& scene_path, const WorldBakeOptions& input_options, - const std::string& archive_path, WorldBakeResult* result, std::string* error) { +bool BakeWorld(const base::String& scene_path, const WorldBakeOptions& input_options, + const base::String& archive_path, WorldBakeResult* result, base::String* error) { if (!result) return false; *result = WorldBakeResult{}; WorldBakeOptions options = input_options; - if (!(options.cell_size > 0) || !std::isfinite(options.cell_size)) { + if (!(options.cell_size > 0) || !::isfinite(options.cell_size)) { SetError(error, "cell size must be a positive, finite number"); return false; } @@ -256,7 +264,7 @@ bool BakeWorld(const std::string& scene_path, const WorldBakeOptions& input_opti // The name becomes a directory inside the archive and half of every payload // path, so a name that is not a single path segment would silently scatter // the world somewhere the index's own prefix convention cannot find it again. - if (options.name.empty() || options.name.find_first_of("/\\") != std::string::npos) { + if (options.name.empty() || options.name.find_first_of("/\\") != base::String::npos) { SetError(error, "world name must be one path segment; '" + options.name + "' is not"); return false; } @@ -267,7 +275,7 @@ bool BakeWorld(const std::string& scene_path, const WorldBakeOptions& input_opti // entity indices are the file's order, so two cooks of one file replay the // same creation sequence and hand out the same ids. ecs::World source; - std::string load_error; + base::String load_error; // Strict by default: a component this build does not register is dropped // silently otherwise, and the cell that needed it bakes without the thing it // was authored to place. @@ -307,37 +315,38 @@ bool BakeWorld(const std::string& scene_path, const WorldBakeOptions& input_opti if (!IdentityOnly(*desc)) { ++authored_count; bool named = false; - for (const std::string& want : options.instance_components) named |= want == desc->name; + for (const base::String& want : options.instance_components) named |= want == desc->name; only_instance_components &= named; } if (!Bakeable(*desc)) { bool seen = false; - for (const std::string& name : result->dropped) seen |= name == desc->name; + for (const base::String& name : result->dropped) seen |= name == desc->name; if (!seen) result->dropped.push_back(desc->name); continue; } record.components.push_back(desc); } - std::sort(record.components.begin(), record.components.end(), - [](const edit::ComponentDesc* a, const edit::ComponentDesc* b) { - return std::strcmp(a->name, b->name) < 0; - }); + // Stable: registration does not force unique names. + rx::StableSort(record.components.data(), record.components.data() + record.components.size(), + [](const edit::ComponentDesc* a, const edit::ComponentDesc* b) { + return ::strcmp(a->name, b->name) < 0; + }); record.instance = only_instance_components && authored_count == options.instance_components.size(); - authored.push_back(std::move(record)); + authored.push_back(base::move(record)); }); // A Parent link is an ecs::Entity handle, and a handle cannot survive a bake, // let alone a streaming boundary. Refusing is the honest answer: silently // dropping the link would move the child into world space. if (parented != 0) { - SetError(error, std::to_string(parented) + + SetError(error, rx::ToString(parented) + " entities have a Parent; flatten the hierarchy before baking (a parent " "link is an ecs handle, which no baked cell can carry)"); return false; } if (off_lattice != 0) { - SetError(error, std::to_string(off_lattice) + + SetError(error, rx::ToString(off_lattice) + " entities have a Transform.position that is not finite, or so far out " "that its cell would alias onto another; either way the archive would " "cook and be wrong"); @@ -349,17 +358,17 @@ bool BakeWorld(const std::string& scene_path, const WorldBakeOptions& input_opti } // Group by cell, then by component set. Sorting once gives both. - std::sort(authored.begin(), authored.end(), [](const Authored& a, const Authored& b) { + base::Sort(authored.begin(), authored.end(), [](const Authored& a, const Authored& b) { if (a.cell != b.cell) return a.cell < b.cell; if (a.instance != b.instance) return a.instance < b.instance; if (a.components.size() != b.components.size()) { return a.components.size() < b.components.size(); } for (size_t i = 0; i < a.components.size(); ++i) { - const int order = std::strcmp(a.components[i]->name, b.components[i]->name); + const int order = ::strcmp(a.components[i]->name, b.components[i]->name); if (order != 0) return order < 0; } - // A total order, so std::sort's instability cannot decide which entity gets + // A total order, so the sort's instability cannot decide which entity gets // which stable id. The index is deterministic because the cook loaded this // file into a fresh world, so index order is file order; cooking a lived-in // world would order ties by that session's history instead, which is why @@ -378,20 +387,20 @@ bool BakeWorld(const std::string& scene_path, const WorldBakeOptions& input_opti if (!seen) written.push_back(desc); } } - std::sort(written.begin(), written.end(), - [](const edit::ComponentDesc* a, const edit::ComponentDesc* b) { - return std::strcmp(a->name, b->name) < 0; - }); - base::Vector schema; + rx::StableSort(written.data(), written.data() + written.size(), + [](const edit::ComponentDesc* a, const edit::ComponentDesc* b) { + return ::strcmp(a->name, b->name) < 0; + }); + base::Vector schema; schema.reserve(written.size()); for (const edit::ComponentDesc* desc : written) { u32 stride = 0; u64 layout = 0; if (!RuntimeComponentLayout(desc->name, &stride, &layout)) { - SetError(error, std::string("component '") + desc->name + "' has no reflected layout"); + SetError(error, base::String("component '") + desc->name + "' has no reflected layout"); return false; } - schema.push_back(std::string(desc->name) + " " + std::to_string(layout)); + schema.push_back(base::String(desc->name) + " " + rx::ToString(layout)); } if (options.bake_id == 0) options.bake_id = HashCook(scene_path, options, schema); @@ -405,10 +414,10 @@ bool BakeWorld(const std::string& scene_path, const WorldBakeOptions& input_opti index.set_bake_id(options.bake_id); index.set_grid(options.cell_size, {0, 0, 0}); asset::PackWriter pack; - const std::string prefix = options.name; + const base::String prefix = options.name; u64 next_stable_id = 1; // 0 is reserved for "no id" - std::string encode_error; + base::String encode_error; for (size_t cell_begin = 0; cell_begin < authored.size();) { const u64 cell = authored[cell_begin].cell; @@ -429,8 +438,8 @@ bool BakeWorld(const std::string& scene_path, const WorldBakeOptions& input_opti for (size_t i = cell_begin; i < cell_end; ++i) { const scene::Transform* transform = source.Get(authored[i].entity); if (!transform) continue; - low_y = first_y ? transform->position[1] : std::min(low_y, transform->position[1]); - high_y = first_y ? transform->position[1] : std::max(high_y, transform->position[1]); + low_y = first_y ? transform->position[1] : rx::Min(low_y, transform->position[1]); + high_y = first_y ? transform->position[1] : rx::Max(high_y, transform->position[1]); first_y = false; } const f32 base_x = CellOrigin(authored[cell_begin].cell_x, options.cell_size); @@ -475,14 +484,14 @@ bool BakeWorld(const std::string& scene_path, const WorldBakeOptions& input_opti "is; --instance must name a set that includes Renderable"); return false; } - const std::optional mesh_path = asset::LookupAssetPath(renderable->mesh); + const base::Optional mesh_path = asset::LookupAssetPath(renderable->mesh); if (!mesh_path) { - SetError(error, "Renderable.mesh " + std::to_string(renderable->mesh.hash) + + SetError(error, "Renderable.mesh " + rx::ToString(renderable->mesh.hash) + " resolves to no asset path, so the instance page would name a " "prototype no host can look up; author the mesh by path"); return false; } - const std::string& prototype = *mesh_path; + const base::String& prototype = *mesh_path; instances.AddInstance( next_stable_id + (i - cell_begin), instances.AddPrototype(prototype), {transform->position[0], transform->position[1], transform->position[2]}, @@ -501,13 +510,13 @@ bool BakeWorld(const std::string& scene_path, const WorldBakeOptions& input_opti for (size_t i = group_begin; i < group_end; ++i) { ids.push_back(next_stable_id + (i - cell_begin)); } - entities.SetStableIds(archetype, std::span(ids.data(), ids.size())); + entities.SetStableIds(archetype, base::Span(ids.data(), ids.size())); for (const edit::ComponentDesc* desc : authored[group_begin].components) { u32 stride = 0; u64 layout = 0; if (!RuntimeComponentLayout(desc->name, &stride, &layout)) { - SetError(error, std::string("component '") + desc->name + "' has no reflected layout"); + SetError(error, base::String("component '") + desc->name + "' has no reflected layout"); return false; } base::Vector column; @@ -515,14 +524,14 @@ bool BakeWorld(const std::string& scene_path, const WorldBakeOptions& input_opti for (size_t i = group_begin; i < group_end; ++i) { const void* value = source.GetRaw(authored[i].entity, desc->id); if (!value) { - SetError(error, std::string("component '") + desc->name + "' vanished mid-bake"); + SetError(error, base::String("component '") + desc->name + "' vanished mid-bake"); return false; } const u8* bytes = static_cast(value); column.insert(column.end(), bytes, bytes + stride); } entities.AddColumn(archetype, desc->name, stride, layout, - std::span(column.data(), column.size())); + base::Span(column.data(), column.size())); cell_entity_bytes += column.size(); } // What the rows cost once they are ECS rows, which is the number the @@ -540,7 +549,7 @@ bool BakeWorld(const std::string& scene_path, const WorldBakeOptions& input_opti return false; } index.AddPayload(cell, Domain::kGameplay, Tier::kStandard, cell_entity_bytes, cell_entities); - pack.Add(CellPayloadPath(prefix, cell, Domain::kGameplay, Tier::kStandard), std::move(bytes)); + pack.Add(CellPayloadPath(prefix, cell, Domain::kGameplay, Tier::kStandard), base::move(bytes)); result->entities += cell_entities; } if (cell_instances != 0) { @@ -552,7 +561,7 @@ bool BakeWorld(const std::string& scene_path, const WorldBakeOptions& input_opti index.AddPayload(cell, Domain::kRepresentation, Tier::kFull, static_cast(cell_instances) * sizeof(ResidentInstance), cell_instances); pack.Add(CellPayloadPath(prefix, cell, Domain::kRepresentation, Tier::kFull), - std::move(bytes)); + base::move(bytes)); result->instances += cell_instances; } @@ -565,7 +574,7 @@ bool BakeWorld(const std::string& scene_path, const WorldBakeOptions& input_opti SetError(error, encode_error); return false; } - pack.Add(prefix + "/" + options.name + ".rxworld", std::move(index_bytes)); + pack.Add(prefix + "/" + options.name + ".rxworld", base::move(index_bytes)); if (!pack.WriteTo(archive_path)) { SetError(error, archive_path + ": cannot be written"); diff --git a/engine/world/world_bake.h b/engine/world/world_bake.h index aa6d09c0..d7b92df5 100644 --- a/engine/world/world_bake.h +++ b/engine/world/world_bake.h @@ -1,11 +1,11 @@ #ifndef RX_WORLD_WORLD_BAKE_H_ #define RX_WORLD_WORLD_BAKE_H_ -#include -#include #include +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/math.h" #include "core/types.h" @@ -35,12 +35,12 @@ namespace rx::world { // constant default would have them agree only by accident. It also keeps two // worlds from colliding when they are merged into one archive, which naming // every world the same thing would guarantee. -RX_WORLD_EXPORT std::string WorldNameForArchive(std::string_view archive_path); +RX_WORLD_EXPORT base::String WorldNameForArchive(base::StringRef archive_path); struct WorldBakeOptions { // Names the world, and the directory its index and payloads sit in inside the // archive: /.rxworld. Empty means WorldNameForArchive. - std::string name; + base::String name; f32 cell_size = 64.0f; // 0 hashes the cook: the scene's bytes, these settings, and the reflected // layout of every component actually written. An explicit value overrides it, @@ -52,7 +52,7 @@ struct WorldBakeOptions { bool skip_unknown = false; // An entity whose authored component set is exactly this becomes an instance // page row rather than an ECS entity. Empty means Transform + Renderable. - base::Vector instance_components; + base::Vector instance_components; }; // What the cook makes of one entity. @@ -79,9 +79,9 @@ struct BakeVerdict { Vec3 cell_minimum; Vec3 cell_maximum; // Reflected components that cannot be baked and are dropped from it. - base::Vector dropped; + base::Vector dropped; // Why, when role is kRefused. - std::string refusal; + base::String refusal; }; RX_WORLD_EXPORT BakeVerdict ClassifyForBake(ecs::World& world, ecs::Entity entity, @@ -92,23 +92,23 @@ struct WorldBakeResult { // The name the world was actually cooked under, which is what `--world-name` // has to be given to open it. Reported rather than left to the caller to // recompute, because an empty options.name means the cook decided it. - std::string name; + base::String name; u32 cells = 0; u32 entities = 0; u32 instances = 0; // Components dropped because they cannot be restored by copying bytes, once // each. Not an error - a cook can legitimately carry them - but the author // has to be told, because every entity that had one no longer does. - base::Vector dropped; + base::Vector dropped; }; // Cooks `scene_path` into a .rxp at `archive_path`. False, with `error` set to // something naming the scene and the problem, on anything the cook refuses: a // Parent link, an entity off the lattice, a component this build cannot bake or // does not know, an empty scene, or a file it cannot read or write. -RX_WORLD_EXPORT bool BakeWorld(const std::string& scene_path, const WorldBakeOptions& options, - const std::string& archive_path, WorldBakeResult* result, - std::string* error); +RX_WORLD_EXPORT bool BakeWorld(const base::String& scene_path, const WorldBakeOptions& options, + const base::String& archive_path, WorldBakeResult* result, + base::String* error); } // namespace rx::world diff --git a/engine/world/world_claim.cc b/engine/world/world_claim.cc index 0f8a03e1..69acb977 100644 --- a/engine/world/world_claim.cc +++ b/engine/world/world_claim.cc @@ -1,6 +1,7 @@ +#include "core/scalar.h" #include "world/world_claim.h" +#include "core/sort.h" -#include namespace rx::world { @@ -78,7 +79,7 @@ i32 ClaimSet::Priority(u64 cell, u32 domains) const { const i32 candidate = entry.claim.kind == ClaimKind::kHard ? 3 : entry.claim.kind == ClaimKind::kSoft ? 2 : 1; - priority = std::max(priority, candidate); + priority = rx::Max(priority, candidate); } return priority; } @@ -91,10 +92,12 @@ void ClaimSet::Explain(u64 cell, u32 domains, base::Vector* out) if (!Honors(entry.claim)) continue; out->push_back(entry.claim); } - std::sort(out->begin(), out->end(), [](const ResidencyClaim& a, const ResidencyClaim& b) { - if (a.kind != b.kind) return a.kind < b.kind; - return a.owner < b.owner; - }); + // Stable: one owner may hold several claims of a kind on a cell. + rx::StableSort(out->data(), out->data() + out->size(), + [](const ResidencyClaim& a, const ResidencyClaim& b) { + if (a.kind != b.kind) return a.kind < b.kind; + return a.owner < b.owner; + }); } } // namespace rx::world diff --git a/engine/world/world_claim.h b/engine/world/world_claim.h index f6bc81f1..6e49efdf 100644 --- a/engine/world/world_claim.h +++ b/engine/world/world_claim.h @@ -1,10 +1,10 @@ #ifndef RX_WORLD_WORLD_CLAIM_H_ #define RX_WORLD_WORLD_CLAIM_H_ -#include #include +#include "base/containers/span.h" #include "core/export.h" #include "core/types.h" #include "world/world_format.h" @@ -84,8 +84,8 @@ class RX_WORLD_EXPORT ClaimSet { bool Honors(const ResidencyClaim& claim) const { return claim.kind <= weakest_honored_; } - std::span entries() const { - return std::span(entries_.data(), entries_.size()); + base::Span entries() const { + return base::Span(entries_.data(), entries_.size()); } size_t size() const { return entries_.size(); } bool empty() const { return entries_.empty(); } diff --git a/engine/world/world_format.cc b/engine/world/world_format.cc index e53b0ea3..ad56bc12 100644 --- a/engine/world/world_format.cc +++ b/engine/world/world_format.cc @@ -1,10 +1,16 @@ +#include "base/algorithm.h" +#include "base/containers/span.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/format.h" +#include "core/scalar.h" #include "world/world_format.h" +#include "core/sort.h" -#include -#include -#include -#include -#include +#include +#include namespace rx::world { namespace { @@ -38,11 +44,11 @@ constexpr u32 kColumnBytes = 32; constexpr u32 kPrototypeBytes = 4; constexpr u32 kInstanceBytes = 44; -void SetError(std::string* error, std::string message) { - if (error) *error = std::move(message); +void SetError(base::String* error, base::String message) { + if (error) *error = base::move(message); } -u64 Checksum(u64 hash, std::span bytes) { +u64 Checksum(u64 hash, base::Span bytes) { for (u8 byte : bytes) { hash ^= byte; hash *= 0x100000001b3ull; @@ -54,7 +60,7 @@ u64 Checksum(u64 hash, std::span bytes) { // cell, the domain, the tier and every table count were guarded only by the // cross-checks that read them, and a zeroed field passes a cross-check by // looking like "unset". -u64 Checksum(std::span header, std::span body) { +u64 Checksum(base::Span header, base::Span body) { return Checksum(Checksum(0xcbf29ce484222325ull, header), body); } @@ -82,7 +88,7 @@ void AppendU64(base::Vector* bytes, u64 value) { for (u32 shift = 0; shift < 64; shift += 8) AppendU8(bytes, static_cast(value >> shift)); } -void AppendF32(base::Vector* bytes, f32 value) { AppendU32(bytes, std::bit_cast(value)); } +void AppendF32(base::Vector* bytes, f32 value) { AppendU32(bytes, rx::BitCast(value)); } void AppendVec3(base::Vector* bytes, const Vec3& value) { AppendF32(bytes, value.x); @@ -94,7 +100,7 @@ void AppendVec3(base::Vector* bytes, const Vec3& value) { // goes false it stays false, so a caller may read a whole record and test once. class Cursor { public: - explicit Cursor(std::span bytes) : bytes_(bytes) {} + explicit Cursor(base::Span bytes) : bytes_(bytes) {} bool ok() const { return ok_; } size_t offset() const { return offset_; } @@ -132,7 +138,7 @@ class Cursor { return value; } - f32 F32() { return std::bit_cast(U32()); } + f32 F32() { return rx::BitCast(U32()); } Vec3 ReadVec3() { Vec3 value; @@ -143,7 +149,7 @@ class Cursor { } private: - std::span bytes_; + base::Span bytes_; size_t offset_ = 0; bool ok_ = true; }; @@ -156,12 +162,12 @@ class StringTable { public: StringTable() { bytes_.push_back('\0'); } - u32 Intern(std::string_view value) { + u32 Intern(base::StringRef value) { if (value.empty()) return 0; for (u32 offset : offsets_) { const char* existing = bytes_.data() + offset; - if (value.size() == std::char_traits::length(existing) && - std::memcmp(existing, value.data(), value.size()) == 0) { + if (value.size() == ::strlen(existing) && + base::MemCompare(existing, value.data(), value.size()) == 0) { return offset; } } @@ -179,12 +185,12 @@ class StringTable { base::Vector offsets_; }; -std::string_view StringAt(const base::Vector& table, u32 offset) { +base::StringRef StringAt(const base::Vector& table, u32 offset) { if (offset >= table.size()) return {}; const char* begin = table.data() + offset; const size_t limit = table.size() - offset; - const size_t length = std::char_traits::length(begin); - return length < limit ? std::string_view(begin, length) : std::string_view(); + const size_t length = ::strlen(begin); + return length < limit ? base::StringRef(begin, length) : base::StringRef(); } // A string table is only usable if its last byte terminates; otherwise a view @@ -194,13 +200,13 @@ bool StringTableTerminated(const base::Vector& table) { } bool IsFinite(const Vec3& value) { - return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z); + return ::isfinite(value.x) && ::isfinite(value.y) && ::isfinite(value.z); } bool ValidDomain(u8 value) { return value < kDomainCount; } bool ValidTier(u8 value) { return value < kTierCount; } -void AppendHex64(std::string* out, u64 value) { +void AppendHex64(base::String* out, u64 value) { static const char kDigits[] = "0123456789abcdef"; for (int shift = 60; shift >= 0; shift -= 4) out->push_back(kDigits[(value >> shift) & 0xf]); } @@ -229,8 +235,8 @@ const char* TierName(Tier tier) { return "unknown"; } -std::string CellPayloadPath(std::string_view prefix, u64 cell, Domain domain, Tier tier) { - std::string path(prefix); +base::String CellPayloadPath(base::StringRef prefix, u64 cell, Domain domain, Tier tier) { + base::String path(prefix); if (!path.empty() && path.back() != '/') path.push_back('/'); AppendHex64(&path, cell); path.push_back('.'); @@ -241,14 +247,14 @@ std::string CellPayloadPath(std::string_view prefix, u64 cell, Domain domain, Ti return path; } -u64 HashComponentLayout(std::string_view component, u32 stride, - std::span field_names, - std::span field_types, std::span field_offsets) { +u64 HashComponentLayout(base::StringRef component, u32 stride, + base::Span field_names, + base::Span field_types, base::Span field_offsets) { u64 hash = 0xcbf29ce484222325ull; hash = HashBytes(hash, component.data(), component.size()); hash = HashBytes(hash, &stride, sizeof(stride)); const size_t count = - std::min(field_names.size(), std::min(field_types.size(), field_offsets.size())); + rx::Min(field_names.size(), rx::Min(field_types.size(), field_offsets.size())); const u32 field_count = static_cast(count); hash = HashBytes(hash, &field_count, sizeof(field_count)); for (size_t i = 0; i < count; ++i) { @@ -260,7 +266,7 @@ u64 HashComponentLayout(std::string_view component, u32 stride, } const WorldCellRecord* WorldIndexData::FindCell(u64 id) const { - auto it = std::lower_bound(cells.begin(), cells.end(), id, + auto it = base::LowerBound(cells.begin(), cells.end(), id, [](const WorldCellRecord& cell, u64 wanted) { return cell.id < wanted; }); return it != cells.end() && it->id == id ? it : nullptr; } @@ -268,7 +274,7 @@ const WorldCellRecord* WorldIndexData::FindCell(u64 id) const { const WorldCellRecord* WorldIndexData::FindCellByStableId(u64 stable_id) const { // Ranges never overlap (the writer refuses a world where they do), so the // last range starting at or below the id is the only candidate. - auto it = std::upper_bound(stable_id_order.begin(), stable_id_order.end(), stable_id, + auto it = base::UpperBound(stable_id_order.begin(), stable_id_order.end(), stable_id, [this](u64 wanted, u32 index) { return wanted < cells[index].stable_id_first; }); @@ -310,10 +316,10 @@ WorldIndexWriter::PendingCell* WorldIndexWriter::Find(u64 id) { void WorldIndexWriter::AddCell(u64 id, Vec3 minimum, Vec3 maximum, u32 zone, u64 stable_id_first, u32 stable_id_count) { const bool finite = IsFinite(minimum) && IsFinite(maximum); - const Vec3 low{std::min(minimum.x, maximum.x), std::min(minimum.y, maximum.y), - std::min(minimum.z, maximum.z)}; - const Vec3 high{std::max(minimum.x, maximum.x), std::max(minimum.y, maximum.y), - std::max(minimum.z, maximum.z)}; + const Vec3 low{rx::Min(minimum.x, maximum.x), rx::Min(minimum.y, maximum.y), + rx::Min(minimum.z, maximum.z)}; + const Vec3 high{rx::Max(minimum.x, maximum.x), rx::Max(minimum.y, maximum.y), + rx::Max(minimum.z, maximum.z)}; if (PendingCell* existing = Find(id)) { // Every field, flags included. Re-adding replaces the record, so what a // cell ends up with must not depend on whether SetCellFlags ran before or @@ -356,7 +362,7 @@ void WorldIndexWriter::AddPayload(u64 cell, Domain domain, Tier tier, u64 reside payloads_.push_back({cell, resident_bytes, row_count, domain, tier}); } -bool WorldIndexWriter::Encode(base::Vector* out, std::string* error) const { +bool WorldIndexWriter::Encode(base::Vector* out, base::String* error) const { if (!out) return false; if (cells_.size() > kMaximumCells) { SetError(error, "world index: too many cells"); @@ -371,7 +377,7 @@ bool WorldIndexWriter::Encode(base::Vector* out, std::string* error) const { // same things here is what keeps the cook from succeeding on an archive its // own loader will not open: a bake that fails at the end of a cook is a bad // afternoon, one that fails at the start of a play session is a bug report. - if (!std::isfinite(cell_size_) || cell_size_ < 0) { + if (!::isfinite(cell_size_) || cell_size_ < 0) { SetError(error, "world index: grid cell size is not a finite, non-negative number"); return false; } @@ -382,16 +388,18 @@ bool WorldIndexWriter::Encode(base::Vector* out, std::string* error) const { for (const PendingCell& cell : cells_) { if (cell.finite_bounds && IsFinite(cell.minimum) && IsFinite(cell.maximum)) continue; SetError(error, - "world index: cell " + std::to_string(cell.id) + " has non-finite bounds"); + "world index: cell " + rx::ToString(cell.id) + " has non-finite bounds"); return false; } base::Vector cells(cells_); - std::sort(cells.begin(), cells.end(), - [](const PendingCell& a, const PendingCell& b) { return a.id < b.id; }); + // Tied ids are refused just below with a message naming only the id, so any + // correct sort agrees. + base::Sort(cells.begin(), cells.end(), + [](const PendingCell& a, const PendingCell& b) { return a.id < b.id; }); for (size_t i = 1; i < cells.size(); ++i) { if (cells[i].id == cells[i - 1].id) { - SetError(error, "world index: duplicate cell id " + std::to_string(cells[i].id)); + SetError(error, "world index: duplicate cell id " + rx::ToString(cells[i].id)); return false; } } @@ -404,13 +412,15 @@ bool WorldIndexWriter::Encode(base::Vector* out, std::string* error) const { for (const PendingCell& cell : cells) { if (cell.stable_id_count != 0) ranged.push_back(&cell); } - std::sort(ranged.begin(), ranged.end(), [](const PendingCell* a, const PendingCell* b) { + // Cell ids are unique by now, so the order is total and any correct sort + // agrees. + base::Sort(ranged.begin(), ranged.end(), [](const PendingCell* a, const PendingCell* b) { if (a->stable_id_first != b->stable_id_first) return a->stable_id_first < b->stable_id_first; return a->id < b->id; }); for (const PendingCell* cell : ranged) { if (cell->stable_id_first > ~u64{0} - cell->stable_id_count) { - SetError(error, "world index: cell " + std::to_string(cell->id) + + SetError(error, "world index: cell " + rx::ToString(cell->id) + " stable-id range wraps past the end of the id space"); return false; } @@ -419,15 +429,15 @@ bool WorldIndexWriter::Encode(base::Vector* out, std::string* error) const { const PendingCell& previous = *ranged[i - 1]; const PendingCell& current = *ranged[i]; if (previous.stable_id_first + previous.stable_id_count > current.stable_id_first) { - SetError(error, "world index: cells " + std::to_string(previous.id) + " and " + - std::to_string(current.id) + " have overlapping stable-id ranges"); + SetError(error, "world index: cells " + rx::ToString(previous.id) + " and " + + rx::ToString(current.id) + " have overlapping stable-id ranges"); return false; } } auto known_cell = [&](u64 id) { auto it = - std::lower_bound(cells.begin(), cells.end(), id, + base::LowerBound(cells.begin(), cells.end(), id, [](const PendingCell& cell, u64 wanted) { return cell.id < wanted; }); return it != cells.end() && it->id == id; }; @@ -435,20 +445,23 @@ bool WorldIndexWriter::Encode(base::Vector* out, std::string* error) const { base::Vector payloads(payloads_); for (const PendingPayload& payload : payloads) { if (!known_cell(payload.cell)) { - SetError(error, "world index: payload names unknown cell " + std::to_string(payload.cell)); + SetError(error, "world index: payload names unknown cell " + rx::ToString(payload.cell)); return false; } if (payload.tier == Tier::kAbsent) { - SetError(error, "world index: cell " + std::to_string(payload.cell) + " " + + SetError(error, "world index: cell " + rx::ToString(payload.cell) + " " + DomainName(payload.domain) + " payload is baked at tier absent"); return false; } } - std::sort(payloads.begin(), payloads.end(), [](const PendingPayload& a, const PendingPayload& b) { - if (a.cell != b.cell) return a.cell < b.cell; - if (a.domain != b.domain) return a.domain < b.domain; - return a.tier < b.tier; - }); + // AddPayload keeps one entry per cell, domain and tier, so the order is total + // and any correct sort agrees. + base::Sort(payloads.begin(), payloads.end(), + [](const PendingPayload& a, const PendingPayload& b) { + if (a.cell != b.cell) return a.cell < b.cell; + if (a.domain != b.domain) return a.domain < b.domain; + return a.tier < b.tier; + }); base::Vector body; size_t payload_cursor = 0; @@ -477,7 +490,7 @@ bool WorldIndexWriter::Encode(base::Vector* out, std::string* error) const { } out->clear(); - out->insert(out->end(), std::begin(kIndexMagic), std::end(kIndexMagic)); + out->insert(out->end(), kIndexMagic, kIndexMagic + sizeof(kIndexMagic)); AppendU32(out, kIndexVersion); AppendU32(out, 0); // flags, reserved AppendU64(out, world_id_); @@ -486,25 +499,25 @@ bool WorldIndexWriter::Encode(base::Vector* out, std::string* error) const { AppendVec3(out, grid_origin_); AppendU32(out, static_cast(cells.size())); AppendU32(out, static_cast(payloads.size())); - AppendU64(out, Checksum(std::span(out->data(), out->size()), - std::span(body.data(), body.size()))); + AppendU64(out, Checksum(base::Span(out->data(), out->size()), + base::Span(body.data(), body.size()))); out->insert(out->end(), body.begin(), body.end()); return true; } -bool DecodeWorldIndex(std::span bytes, WorldIndexData* out, std::string* error) { +bool DecodeWorldIndex(base::Span bytes, WorldIndexData* out, base::String* error) { if (!out) return false; Cursor header(bytes); const u8* magic = nullptr; if (!header.Take(sizeof(kIndexMagic), &magic) || - std::memcmp(magic, kIndexMagic, sizeof(kIndexMagic)) != 0) { + base::MemCompare(magic, kIndexMagic, sizeof(kIndexMagic)) != 0) { SetError(error, "world index: not an RXWORLDI file"); return false; } const u32 version = header.U32(); if (version != kIndexVersion) { - SetError(error, "world index: version " + std::to_string(version) + ", expected " + - std::to_string(kIndexVersion)); + SetError(error, "world index: version " + rx::ToString(version) + ", expected " + + rx::ToString(kIndexVersion)); return false; } header.U32(); // flags, reserved @@ -528,11 +541,11 @@ bool DecodeWorldIndex(std::span bytes, WorldIndexData* out, std::strin const u64 expected_body = static_cast(cell_count) * kIndexCellBytes + static_cast(payload_count) * kIndexPayloadBytes; if (bytes.size() - body_offset != expected_body) { - SetError(error, "world index: body is " + std::to_string(bytes.size() - body_offset) + - " bytes, header describes " + std::to_string(expected_body)); + SetError(error, "world index: body is " + rx::ToString(bytes.size() - body_offset) + + " bytes, header describes " + rx::ToString(expected_body)); return false; } - const std::span body = bytes.subspan(body_offset); + const base::Span body = bytes.subspan(body_offset); if (Checksum(bytes.first(body_offset - sizeof(u64)), body) != checksum) { SetError(error, "world index: checksum mismatch"); return false; @@ -542,7 +555,7 @@ bool DecodeWorldIndex(std::span bytes, WorldIndexData* out, std::strin // is not a finite, non-negative number is a corrupt header, and substituting // zero for it would read as that legitimate answer. Both fields are inside // the checksum, so reaching here with either wrong means a wrong cook. - if (!std::isfinite(cell_size) || cell_size < 0) { + if (!::isfinite(cell_size) || cell_size < 0) { SetError(error, "world index: grid cell size is not a finite, non-negative number"); return false; } @@ -576,25 +589,25 @@ bool DecodeWorldIndex(std::span bytes, WorldIndexData* out, std::strin cell.payload_first = cursor.U32(); cell.payload_count = cursor.U32(); if (!IsFinite(cell.minimum) || !IsFinite(cell.maximum)) { - SetError(error, "world index: cell " + std::to_string(cell.id) + " has non-finite bounds"); + SetError(error, "world index: cell " + rx::ToString(cell.id) + " has non-finite bounds"); return false; } if (cell.minimum.x > cell.maximum.x || cell.minimum.y > cell.maximum.y || cell.minimum.z > cell.maximum.z) { - SetError(error, "world index: cell " + std::to_string(cell.id) + " has inverted bounds"); + SetError(error, "world index: cell " + rx::ToString(cell.id) + " has inverted bounds"); return false; } if (i > 0 && cell.id <= decoded.cells[i - 1].id) { - SetError(error, "world index: cells are not sorted by id at " + std::to_string(i)); + SetError(error, "world index: cells are not sorted by id at " + rx::ToString(i)); return false; } if (static_cast(cell.payload_first) + cell.payload_count > payload_count) { - SetError(error, "world index: cell " + std::to_string(cell.id) + + SetError(error, "world index: cell " + rx::ToString(cell.id) + " spans past the payload table"); return false; } if (cell.stable_id_count != 0 && cell.stable_id_first > ~u64{0} - cell.stable_id_count) { - SetError(error, "world index: cell " + std::to_string(cell.id) + + SetError(error, "world index: cell " + rx::ToString(cell.id) + " stable-id range wraps past the end of the id space"); return false; } @@ -608,16 +621,18 @@ bool DecodeWorldIndex(std::span bytes, WorldIndexData* out, std::strin for (u32 i = 0; i < decoded.cells.size(); ++i) { if (decoded.cells[i].stable_id_count != 0) decoded.stable_id_order.push_back(i); } - std::sort(decoded.stable_id_order.begin(), decoded.stable_id_order.end(), - [&](u32 a, u32 b) { - return decoded.cells[a].stable_id_first < decoded.cells[b].stable_id_first; - }); + // Stable: ties only on an overlap, and then cell order picks the ids named. + rx::StableSort(decoded.stable_id_order.data(), + decoded.stable_id_order.data() + decoded.stable_id_order.size(), + [&](u32 a, u32 b) { + return decoded.cells[a].stable_id_first < decoded.cells[b].stable_id_first; + }); for (size_t i = 1; i < decoded.stable_id_order.size(); ++i) { const WorldCellRecord& previous = decoded.cells[decoded.stable_id_order[i - 1]]; const WorldCellRecord& current = decoded.cells[decoded.stable_id_order[i]]; if (previous.stable_id_first + previous.stable_id_count > current.stable_id_first) { - SetError(error, "world index: cells " + std::to_string(previous.id) + " and " + - std::to_string(current.id) + " have overlapping stable-id ranges"); + SetError(error, "world index: cells " + rx::ToString(previous.id) + " and " + + rx::ToString(current.id) + " have overlapping stable-id ranges"); return false; } } @@ -631,7 +646,7 @@ bool DecodeWorldIndex(std::span bytes, WorldIndexData* out, std::strin const u8 tier = cursor.U8(); cursor.U16(); if (!ValidDomain(domain) || !ValidTier(tier) || tier == static_cast(Tier::kAbsent)) { - SetError(error, "world index: payload " + std::to_string(i) + " has an unknown domain/tier"); + SetError(error, "world index: payload " + rx::ToString(i) + " has an unknown domain/tier"); return false; } payload.domain = static_cast(domain); @@ -642,7 +657,7 @@ bool DecodeWorldIndex(std::span bytes, WorldIndexData* out, std::strin SetError(error, "world index: truncated body"); return false; } - *out = std::move(decoded); + *out = base::move(decoded); return true; } @@ -654,29 +669,29 @@ u32 CellPayloadWriter::BeginArchetype(u32 row_count) { return static_cast(archetypes_.size() - 1); } -void CellPayloadWriter::AddColumn(u32 archetype, std::string_view component, u32 stride, - u64 layout_hash, std::span bytes) { +void CellPayloadWriter::AddColumn(u32 archetype, base::StringRef component, u32 stride, + u64 layout_hash, base::Span bytes) { PendingColumn column; column.archetype = archetype; column.component.assign(component); column.stride = stride; column.layout_hash = layout_hash; column.bytes.insert(column.bytes.end(), bytes.begin(), bytes.end()); - columns_.push_back(std::move(column)); + columns_.push_back(base::move(column)); } -void CellPayloadWriter::SetStableIds(u32 archetype, std::span ids) { +void CellPayloadWriter::SetStableIds(u32 archetype, base::Span ids) { if (archetype >= archetypes_.size()) return; base::Vector& target = archetypes_[archetype].stable_ids; target.clear(); target.insert(target.end(), ids.begin(), ids.end()); } -u32 CellPayloadWriter::AddPrototype(std::string_view name) { +u32 CellPayloadWriter::AddPrototype(base::StringRef name) { for (size_t i = 0; i < prototypes_.size(); ++i) { if (prototypes_[i] == name) return static_cast(i); } - prototypes_.push_back(std::string(name)); + prototypes_.push_back(base::String(name)); return static_cast(prototypes_.size() - 1); } @@ -685,7 +700,7 @@ void CellPayloadWriter::AddInstance(u64 stable_id, u32 prototype, Vec3 position, instances_.push_back({stable_id, prototype, position, rotation, scale}); } -bool CellPayloadWriter::Encode(base::Vector* out, std::string* error) const { +bool CellPayloadWriter::Encode(base::Vector* out, base::String* error) const { if (!out) return false; const bool has_entities = !archetypes_.empty() || !columns_.empty(); @@ -705,7 +720,7 @@ bool CellPayloadWriter::Encode(base::Vector* out, std::string* error) const for (const PendingColumn& column : columns_) { if (column.archetype >= archetypes_.size()) { SetError(error, "cell payload: column '" + column.component + "' names unknown archetype " + - std::to_string(column.archetype)); + rx::ToString(column.archetype)); return false; } if (column.stride == 0) { @@ -715,16 +730,16 @@ bool CellPayloadWriter::Encode(base::Vector* out, std::string* error) const const u64 expected = static_cast(column.stride) * archetypes_[column.archetype].row_count; if (column.bytes.size() != expected) { SetError(error, "cell payload: column '" + column.component + "' has " + - std::to_string(column.bytes.size()) + " bytes, expected " + - std::to_string(expected)); + rx::ToString(column.bytes.size()) + " bytes, expected " + + rx::ToString(expected)); return false; } } for (size_t a = 0; a < archetypes_.size(); ++a) { if (archetypes_[a].stable_ids.size() != archetypes_[a].row_count) { - SetError(error, "cell payload: archetype " + std::to_string(a) + " has " + - std::to_string(archetypes_[a].stable_ids.size()) + " stable ids for " + - std::to_string(archetypes_[a].row_count) + " rows"); + SetError(error, "cell payload: archetype " + rx::ToString(a) + " has " + + rx::ToString(archetypes_[a].stable_ids.size()) + " stable ids for " + + rx::ToString(archetypes_[a].row_count) + " rows"); return false; } } @@ -736,26 +751,26 @@ bool CellPayloadWriter::Encode(base::Vector* out, std::string* error) const seen_ids.insert(seen_ids.end(), archetype.stable_ids.begin(), archetype.stable_ids.end()); } for (const WorldInstanceRecord& instance : instances_) seen_ids.push_back(instance.stable_id); - std::sort(seen_ids.begin(), seen_ids.end()); + base::Sort(seen_ids.begin(), seen_ids.end()); for (size_t i = 1; i < seen_ids.size(); ++i) { if (seen_ids[i] == seen_ids[i - 1]) { - SetError(error, "cell payload: stable id " + std::to_string(seen_ids[i]) + " appears twice"); + SetError(error, "cell payload: stable id " + rx::ToString(seen_ids[i]) + " appears twice"); return false; } } for (const WorldInstanceRecord& instance : instances_) { if (instance.prototype >= prototypes_.size()) { - SetError(error, "cell payload: instance " + std::to_string(instance.stable_id) + - " names unknown prototype " + std::to_string(instance.prototype)); + SetError(error, "cell payload: instance " + rx::ToString(instance.stable_id) + + " names unknown prototype " + rx::ToString(instance.prototype)); return false; } // A transform nothing can draw, refused where it was produced rather than // on the machine that tries to load the cell. - if (!IsFinite(instance.position) || !std::isfinite(instance.rotation.x) || - !std::isfinite(instance.rotation.y) || !std::isfinite(instance.rotation.z) || - !std::isfinite(instance.rotation.w) || !std::isfinite(instance.scale)) { - SetError(error, "cell payload: instance " + std::to_string(instance.stable_id) + + if (!IsFinite(instance.position) || !::isfinite(instance.rotation.x) || + !::isfinite(instance.rotation.y) || !::isfinite(instance.rotation.z) || + !::isfinite(instance.rotation.w) || !::isfinite(instance.scale)) { + SetError(error, "cell payload: instance " + rx::ToString(instance.stable_id) + " has a non-finite transform"); return false; } @@ -770,7 +785,7 @@ bool CellPayloadWriter::Encode(base::Vector* out, std::string* error) const if (column.archetype != a) continue; for (size_t seen = first; seen < ordered.size(); ++seen) { if (ordered[seen]->component == column.component) { - SetError(error, "cell payload: archetype " + std::to_string(a) + " lists component '" + + SetError(error, "cell payload: archetype " + rx::ToString(a) + " lists component '" + column.component + "' twice"); return false; } @@ -817,7 +832,7 @@ bool CellPayloadWriter::Encode(base::Vector* out, std::string* error) const base::Vector prototype_names; prototype_names.reserve(prototypes_.size()); - for (const std::string& name : prototypes_) prototype_names.push_back(strings.Intern(name)); + for (const base::String& name : prototypes_) prototype_names.push_back(strings.Intern(name)); if (strings.bytes().size() > kMaximumStringBytes) { SetError(error, "cell payload: string table too large"); @@ -857,7 +872,7 @@ bool CellPayloadWriter::Encode(base::Vector* out, std::string* error) const body.insert(body.end(), data.begin(), data.end()); out->clear(); - out->insert(out->end(), std::begin(kPayloadMagic), std::end(kPayloadMagic)); + out->insert(out->end(), kPayloadMagic, kPayloadMagic + sizeof(kPayloadMagic)); AppendU32(out, kPayloadVersion); AppendU32(out, static_cast(kind)); AppendU64(out, cell_id_); @@ -871,28 +886,28 @@ bool CellPayloadWriter::Encode(base::Vector* out, std::string* error) const AppendU32(out, static_cast(instances_.size())); AppendU32(out, static_cast(strings.bytes().size())); AppendU64(out, data.size()); - AppendU64(out, Checksum(std::span(out->data(), out->size()), - std::span(body.data(), body.size()))); + AppendU64(out, Checksum(base::Span(out->data(), out->size()), + base::Span(body.data(), body.size()))); out->insert(out->end(), body.begin(), body.end()); return true; } -std::string_view WorldCellPayload::String(u32 offset) const { return StringAt(strings, offset); } +base::StringRef WorldCellPayload::String(u32 offset) const { return StringAt(strings, offset); } -std::span WorldCellPayload::ColumnBytes(const WorldColumnRecord& column) const { +base::Span WorldCellPayload::ColumnBytes(const WorldColumnRecord& column) const { if (column.data_offset > data.size() || data.size() - column.data_offset < column.data_bytes) { return {}; } - return std::span(data.data() + column.data_offset, + return base::Span(data.data() + column.data_offset, static_cast(column.data_bytes)); } -std::span WorldCellPayload::StableIds(const WorldArchetypeRecord& archetype) const { +base::Span WorldCellPayload::StableIds(const WorldArchetypeRecord& archetype) const { if (archetype.stable_id_index > stable_ids.size() || stable_ids.size() - archetype.stable_id_index < archetype.row_count) { return {}; } - return std::span(stable_ids.data() + archetype.stable_id_index, archetype.row_count); + return base::Span(stable_ids.data() + archetype.stable_id_index, archetype.row_count); } u32 WorldCellPayload::total_row_count() const { @@ -901,24 +916,24 @@ u32 WorldCellPayload::total_row_count() const { return total; } -bool DecodeCellPayload(std::span bytes, WorldCellPayload* out, std::string* error) { +bool DecodeCellPayload(base::Span bytes, WorldCellPayload* out, base::String* error) { if (!out) return false; Cursor header(bytes); const u8* magic = nullptr; if (!header.Take(sizeof(kPayloadMagic), &magic) || - std::memcmp(magic, kPayloadMagic, sizeof(kPayloadMagic)) != 0) { + base::MemCompare(magic, kPayloadMagic, sizeof(kPayloadMagic)) != 0) { SetError(error, "cell payload: not an RXCELLPL file"); return false; } const u32 version = header.U32(); if (version != kPayloadVersion) { - SetError(error, "cell payload: version " + std::to_string(version) + ", expected " + - std::to_string(kPayloadVersion)); + SetError(error, "cell payload: version " + rx::ToString(version) + ", expected " + + rx::ToString(kPayloadVersion)); return false; } const u32 kind = header.U32(); if (kind > static_cast(PayloadKind::kInstances)) { - SetError(error, "cell payload: unknown kind " + std::to_string(kind)); + SetError(error, "cell payload: unknown kind " + rx::ToString(kind)); return false; } const u64 cell_id = header.U64(); @@ -965,11 +980,11 @@ bool DecodeCellPayload(std::span bytes, WorldCellPayload* out, std::st static_cast(instance_count) * kInstanceBytes + string_bytes + data_bytes; if (bytes.size() - body_offset != expected_body) { - SetError(error, "cell payload: body is " + std::to_string(bytes.size() - body_offset) + - " bytes, header describes " + std::to_string(expected_body)); + SetError(error, "cell payload: body is " + rx::ToString(bytes.size() - body_offset) + + " bytes, header describes " + rx::ToString(expected_body)); return false; } - const std::span body = bytes.subspan(body_offset); + const base::Span body = bytes.subspan(body_offset); if (Checksum(bytes.first(body_offset - sizeof(u64)), body) != checksum) { SetError(error, "cell payload: checksum mismatch"); return false; @@ -995,13 +1010,13 @@ bool DecodeCellPayload(std::span bytes, WorldCellPayload* out, std::st record.column_count = cursor.U32(); record.stable_id_offset = cursor.U64(); if (static_cast(record.column_first) + record.column_count > column_count) { - SetError(error, "cell payload: archetype " + std::to_string(i) + " spans past the columns"); + SetError(error, "cell payload: archetype " + rx::ToString(i) + " spans past the columns"); return false; } const u64 stable_bytes = static_cast(record.row_count) * sizeof(u64); if (record.stable_id_offset > data_bytes || data_bytes - record.stable_id_offset < stable_bytes) { - SetError(error, "cell payload: archetype " + std::to_string(i) + + SetError(error, "cell payload: archetype " + rx::ToString(i) + " stable ids fall outside the data section"); return false; } @@ -1017,15 +1032,15 @@ bool DecodeCellPayload(std::span bytes, WorldCellPayload* out, std::st record.data_offset = cursor.U64(); record.data_bytes = cursor.U64(); if (record.name >= string_bytes) { - SetError(error, "cell payload: column " + std::to_string(i) + " name is out of range"); + SetError(error, "cell payload: column " + rx::ToString(i) + " name is out of range"); return false; } if (record.stride == 0) { - SetError(error, "cell payload: column " + std::to_string(i) + " has a zero stride"); + SetError(error, "cell payload: column " + rx::ToString(i) + " has a zero stride"); return false; } if (record.data_offset > data_bytes || data_bytes - record.data_offset < record.data_bytes) { - SetError(error, "cell payload: column " + std::to_string(i) + + SetError(error, "cell payload: column " + rx::ToString(i) + " falls outside the data section"); return false; } @@ -1036,8 +1051,8 @@ bool DecodeCellPayload(std::span bytes, WorldCellPayload* out, std::st for (u32 c = 0; c < archetype.column_count; ++c) { const WorldColumnRecord& column = decoded.columns[archetype.column_first + c]; if (column.data_bytes != static_cast(column.stride) * archetype.row_count) { - SetError(error, "cell payload: archetype " + std::to_string(a) + " column " + - std::to_string(c) + " byte count disagrees with its row count"); + SetError(error, "cell payload: archetype " + rx::ToString(a) + " column " + + rx::ToString(c) + " byte count disagrees with its row count"); return false; } } @@ -1048,7 +1063,7 @@ bool DecodeCellPayload(std::span bytes, WorldCellPayload* out, std::st WorldPrototypeRecord record; record.name = cursor.U32(); if (record.name >= string_bytes) { - SetError(error, "cell payload: prototype " + std::to_string(i) + " name is out of range"); + SetError(error, "cell payload: prototype " + rx::ToString(i) + " name is out of range"); return false; } decoded.prototypes.push_back(record); @@ -1066,14 +1081,14 @@ bool DecodeCellPayload(std::span bytes, WorldCellPayload* out, std::st record.rotation.w = cursor.F32(); record.scale = cursor.F32(); if (record.prototype >= prototype_count) { - SetError(error, "cell payload: instance " + std::to_string(i) + " names unknown prototype " + - std::to_string(record.prototype)); + SetError(error, "cell payload: instance " + rx::ToString(i) + " names unknown prototype " + + rx::ToString(record.prototype)); return false; } - if (!IsFinite(record.position) || !std::isfinite(record.rotation.x) || - !std::isfinite(record.rotation.y) || !std::isfinite(record.rotation.z) || - !std::isfinite(record.rotation.w) || !std::isfinite(record.scale)) { - SetError(error, "cell payload: instance " + std::to_string(i) + + if (!IsFinite(record.position) || !::isfinite(record.rotation.x) || + !::isfinite(record.rotation.y) || !::isfinite(record.rotation.z) || + !::isfinite(record.rotation.w) || !::isfinite(record.scale)) { + SetError(error, "cell payload: instance " + rx::ToString(i) + " has a non-finite transform"); return false; } @@ -1108,14 +1123,14 @@ bool DecodeCellPayload(std::span bytes, WorldCellPayload* out, std::st u64 total_ids = 0; for (const WorldArchetypeRecord& archetype : decoded.archetypes) total_ids += archetype.row_count; if (total_ids > kMaximumTotalRows) { - SetError(error, "cell payload: " + std::to_string(total_ids) + + SetError(error, "cell payload: " + rx::ToString(total_ids) + " rows across its archetypes, more than a cell may hold"); return false; } decoded.stable_ids.reserve(static_cast(total_ids)); for (WorldArchetypeRecord& archetype : decoded.archetypes) { archetype.stable_id_index = static_cast(decoded.stable_ids.size()); - Cursor ids(std::span(decoded.data.data(), decoded.data.size())); + Cursor ids(base::Span(decoded.data.data(), decoded.data.size())); const u8* skipped = nullptr; ids.Take(static_cast(archetype.stable_id_offset), &skipped); for (u32 row = 0; row < archetype.row_count; ++row) decoded.stable_ids.push_back(ids.U64()); @@ -1132,13 +1147,13 @@ bool DecodeCellPayload(std::span bytes, WorldCellPayload* out, std::st seen.reserve(decoded.stable_ids.size() + decoded.instances.size()); seen.insert(seen.end(), decoded.stable_ids.begin(), decoded.stable_ids.end()); for (const WorldInstanceRecord& instance : decoded.instances) seen.push_back(instance.stable_id); - std::sort(seen.begin(), seen.end()); + base::Sort(seen.begin(), seen.end()); for (size_t i = 1; i < seen.size(); ++i) { if (seen[i] != seen[i - 1]) continue; - SetError(error, "cell payload: stable id " + std::to_string(seen[i]) + " appears twice"); + SetError(error, "cell payload: stable id " + rx::ToString(seen[i]) + " appears twice"); return false; } - *out = std::move(decoded); + *out = base::move(decoded); return true; } diff --git a/engine/world/world_format.h b/engine/world/world_format.h index 46f8c927..dd757944 100644 --- a/engine/world/world_format.h +++ b/engine/world/world_format.h @@ -1,15 +1,16 @@ #ifndef RX_WORLD_WORLD_FORMAT_H_ #define RX_WORLD_WORLD_FORMAT_H_ -#include -#include -#include #include #include +#include "base/containers/span.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/math.h" +#include "core/scalar.h" #include "core/types.h" // The baked map formats. Two files, two jobs: @@ -69,7 +70,7 @@ RX_WORLD_EXPORT const char* TierName(Tier tier); // Where one (domain, tier) of one cell lives, and what it costs resident. The // path is not stored: it is this, and only this. // /...rxcell -RX_WORLD_EXPORT std::string CellPayloadPath(std::string_view prefix, u64 cell, Domain domain, +RX_WORLD_EXPORT base::String CellPayloadPath(base::StringRef prefix, u64 cell, Domain domain, Tier tier); struct WorldPayloadRecord { @@ -135,8 +136,8 @@ struct WorldIndexData { Tier BestTier(const WorldCellRecord& cell, Domain domain, Tier ceiling) const; }; -RX_WORLD_EXPORT bool DecodeWorldIndex(std::span bytes, WorldIndexData* out, - std::string* error); +RX_WORLD_EXPORT bool DecodeWorldIndex(base::Span bytes, WorldIndexData* out, + base::String* error); // Cook side. Cells and payloads arrive in any order; Encode sorts and flattens. // Re-adding the same cell, or the same (cell, domain, tier), replaces the @@ -160,7 +161,7 @@ class RX_WORLD_EXPORT WorldIndexWriter { // id, or two cells whose stable-id ranges overlap. Catching an overlapping // range here is the whole point: at runtime it would resolve a stable id to // the wrong cell, silently, forever. - bool Encode(base::Vector* out, std::string* error) const; + bool Encode(base::Vector* out, base::String* error) const; private: struct PendingCell { @@ -173,7 +174,7 @@ class RX_WORLD_EXPORT WorldIndexWriter { u32 stable_id_count = 0; // Whether AddCell was handed finite bounds. Recorded rather than inferred // from the stored values, because the min/max canonicalization below throws - // a NaN away - std::min(0, NaN) is 0 - so by the time the bounds are stored + // a NaN away - rx::Min(0, NaN) is 0 - so by the time the bounds are stored // the only trace of one is an extent that silently collapsed. bool finite_bounds = true; }; @@ -264,14 +265,14 @@ struct WorldCellPayload { base::Vector data; // column bytes and stable-id arrays base::Vector stable_ids; // every archetype's ids, in archetype order - std::string_view String(u32 offset) const; - std::span ColumnBytes(const WorldColumnRecord& column) const; - std::span StableIds(const WorldArchetypeRecord& archetype) const; + base::StringRef String(u32 offset) const; + base::Span ColumnBytes(const WorldColumnRecord& column) const; + base::Span StableIds(const WorldArchetypeRecord& archetype) const; u32 total_row_count() const; }; -RX_WORLD_EXPORT bool DecodeCellPayload(std::span bytes, WorldCellPayload* out, - std::string* error); +RX_WORLD_EXPORT bool DecodeCellPayload(base::Span bytes, WorldCellPayload* out, + base::String* error); // Cook side for one payload. The writer owns the data section, so callers never // compute an offset. @@ -284,24 +285,24 @@ class RX_WORLD_EXPORT CellPayloadWriter { // Entity payloads: open an archetype, add one column per component holding // the full row_count worth of bytes, then close it with the stable ids. u32 BeginArchetype(u32 row_count); - void AddColumn(u32 archetype, std::string_view component, u32 stride, u64 layout_hash, - std::span bytes); - void SetStableIds(u32 archetype, std::span ids); + void AddColumn(u32 archetype, base::StringRef component, u32 stride, u64 layout_hash, + base::Span bytes); + void SetStableIds(u32 archetype, base::Span ids); // Instance payloads. - u32 AddPrototype(std::string_view name); + u32 AddPrototype(base::StringRef name); void AddInstance(u64 stable_id, u32 prototype, Vec3 position, Quat rotation, f32 scale); // False, with `error` set, when the payload is internally inconsistent: a // column whose bytes are not stride * row_count, an archetype missing stable // ids, a duplicate component in one archetype, a duplicate stable id, or // entity and instance content mixed into one payload. - bool Encode(base::Vector* out, std::string* error) const; + bool Encode(base::Vector* out, base::String* error) const; private: struct PendingColumn { u32 archetype = 0; - std::string component; + base::String component; u32 stride = 0; u64 layout_hash = 0; base::Vector bytes; @@ -317,17 +318,17 @@ class RX_WORLD_EXPORT CellPayloadWriter { Tier tier_ = Tier::kAbsent; base::Vector archetypes_; base::Vector columns_; - base::Vector prototypes_; + base::Vector prototypes_; base::Vector instances_; }; // fnv1a-64 over the reflected shape of a component: its name, its stride, and // every field's name, type and offset. Two builds that agree on this agree on // the bytes of the struct, which is what makes a column safe to memcpy. -RX_WORLD_EXPORT u64 HashComponentLayout(std::string_view component, u32 stride, - std::span field_names, - std::span field_types, - std::span field_offsets); +RX_WORLD_EXPORT u64 HashComponentLayout(base::StringRef component, u32 stride, + base::Span field_names, + base::Span field_types, + base::Span field_offsets); } // namespace rx::world diff --git a/engine/world/world_map.cc b/engine/world/world_map.cc index 38341689..239bf0d2 100644 --- a/engine/world/world_map.cc +++ b/engine/world/world_map.cc @@ -1,13 +1,19 @@ +#include "base/containers/span.h" +#include "base/memory/move.h" +#include "base/optional.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/format.h" +#include "core/scalar.h" #include "world/world_map.h" -#include -#include +#include namespace rx::world { namespace { -void SetError(std::string* error, std::string message) { - if (error) *error = std::move(message); +void SetError(base::String* error, base::String message) { + if (error) *error = base::move(message); } // The directory an index sits in, which is the prefix its payloads share. @@ -15,33 +21,33 @@ void SetError(std::string* error, std::string message) { // last slash there belongs to the scheme separator, and cutting there would // leave "world:/", which resolves to no mount at all and would turn every // payload read into a missing entry while the index itself loaded fine. -std::string DirectoryOf(std::string_view path) { +base::String DirectoryOf(base::StringRef path) { const size_t scheme = path.find("://"); - const size_t first = scheme == std::string_view::npos ? 0 : scheme + 3; + const size_t first = scheme == base::StringRef::npos ? 0 : scheme + 3; const size_t slash = path.find_last_of('/'); - if (slash == std::string_view::npos || slash < first) { - return scheme == std::string_view::npos ? std::string() : std::string(path.substr(0, first)); + if (slash == base::StringRef::npos || slash < first) { + return scheme == base::StringRef::npos ? base::String() : base::String(path.substr(0, first)); } - return std::string(path.substr(0, slash)); + return base::String(path.substr(0, slash)); } } // namespace -bool WorldMap::Load(const asset::Vfs& vfs, std::string_view index_path, std::string* error) { +bool WorldMap::Load(const asset::Vfs& vfs, base::StringRef index_path, base::String* error) { loaded_ = false; index_ = WorldIndexData{}; payload_prefix_.clear(); index_path_.assign(index_path); - std::optional> bytes = vfs.Read(index_path); + base::Optional> bytes = vfs.Read(index_path); if (!bytes) { - SetError(error, std::string(index_path) + ": no such world index in the mounted archives"); + SetError(error, base::String(index_path) + ": no such world index in the mounted archives"); return false; } - std::string decode_error; - if (!DecodeWorldIndex(std::span(bytes->data(), bytes->size()), &index_, + base::String decode_error; + if (!DecodeWorldIndex(base::Span(bytes->data(), bytes->size()), &index_, &decode_error)) { - SetError(error, std::string(index_path) + ": " + decode_error); + SetError(error, base::String(index_path) + ": " + decode_error); index_ = WorldIndexData{}; return false; } @@ -51,16 +57,16 @@ bool WorldMap::Load(const asset::Vfs& vfs, std::string_view index_path, std::str } bool WorldMap::ReadPayload(const asset::Vfs& vfs, u64 cell, Domain domain, Tier tier, - WorldCellPayload* out, std::string* error) const { + WorldCellPayload* out, base::String* error) const { if (!out) return false; - const std::string path = CellPayloadPath(payload_prefix_, cell, domain, tier); - std::optional> bytes = vfs.Read(path); + const base::String path = CellPayloadPath(payload_prefix_, cell, domain, tier); + base::Optional> bytes = vfs.Read(path); if (!bytes) { SetError(error, path + ": the index lists this payload, but the archive has no such entry"); return false; } - std::string decode_error; - if (!DecodeCellPayload(std::span(bytes->data(), bytes->size()), out, &decode_error)) { + base::String decode_error; + if (!DecodeCellPayload(base::Span(bytes->data(), bytes->size()), out, &decode_error)) { SetError(error, path + ": " + decode_error); return false; } @@ -68,14 +74,14 @@ bool WorldMap::ReadPayload(const asset::Vfs& vfs, u64 cell, Domain domain, Tier // payload to be fixed up: it means the index and the archive came from // different cooks, and every other cell is suspect too. if (out->bake_id != index_.bake_id) { - SetError(error, path + ": baked by " + std::to_string(out->bake_id) + ", but " + index_path_ + - " was baked by " + std::to_string(index_.bake_id)); + SetError(error, path + ": baked by " + rx::ToString(out->bake_id) + ", but " + index_path_ + + " was baked by " + rx::ToString(index_.bake_id)); return false; } if (out->cell_id != cell || out->domain != domain || out->tier != tier) { - SetError(error, path + ": holds cell " + std::to_string(out->cell_id) + " " + + SetError(error, path + ": holds cell " + rx::ToString(out->cell_id) + " " + DomainName(out->domain) + "/" + TierName(out->tier) + ", not cell " + - std::to_string(cell) + " " + DomainName(domain) + "/" + TierName(tier)); + rx::ToString(cell) + " " + DomainName(domain) + "/" + TierName(tier)); return false; } @@ -95,19 +101,19 @@ bool WorldMap::ReadPayload(const asset::Vfs& vfs, u64 cell, Domain domain, Tier for (const WorldArchetypeRecord& archetype : out->archetypes) { for (u64 stable_id : out->StableIds(archetype)) { if (owned(stable_id)) continue; - SetError(error, path + ": stable id " + std::to_string(stable_id) + + SetError(error, path + ": stable id " + rx::ToString(stable_id) + " is outside the range the index gives this cell (" + - std::to_string(record->stable_id_first) + "+" + - std::to_string(record->stable_id_count) + ")"); + rx::ToString(record->stable_id_first) + "+" + + rx::ToString(record->stable_id_count) + ")"); return false; } } for (const WorldInstanceRecord& instance : out->instances) { if (owned(instance.stable_id)) continue; - SetError(error, path + ": instance stable id " + std::to_string(instance.stable_id) + + SetError(error, path + ": instance stable id " + rx::ToString(instance.stable_id) + " is outside the range the index gives this cell (" + - std::to_string(record->stable_id_first) + "+" + - std::to_string(record->stable_id_count) + ")"); + rx::ToString(record->stable_id_first) + "+" + + rx::ToString(record->stable_id_count) + ")"); return false; } return true; @@ -130,13 +136,13 @@ void WorldMap::GatherRegions(const scene::WorldStreamObservation& observer, Doma const scene::WorldStreamRegion region{cell.id, cell.minimum, cell.maximum, 0, channel}; const scene::WorldStreamDemand demand = scene::EvaluateWorldStreamDemand(observer, region); if (!demand.retain) continue; - out->push_back({region, std::min(demand.current_distance, demand.predicted_distance), + out->push_back({region, rx::Min(demand.current_distance, demand.predicted_distance), /*from_claim=*/false}); } } WorldStreamPolicy DefaultWorldStreamPolicy(f32 scale) { - const f32 unit = std::isfinite(scale) && scale > 0 ? scale : 1.0f; + const f32 unit = ::isfinite(scale) && scale > 0 ? scale : 1.0f; WorldStreamPolicy policy; auto set = [&](Domain domain, f32 load, f32 retain, f32 full, u32 rows_per_commit) { DomainStreamPolicy& target = policy[domain]; @@ -178,7 +184,7 @@ bool MakeDomainObservation(const scene::WorldStreamObservation& observer, Domain if ((observer.channels & channel) == 0) return false; *out = observer; out->load_distance = domain_policy.load_distance; - out->retain_distance = std::max(domain_policy.load_distance, domain_policy.retain_distance); + out->retain_distance = rx::Max(domain_policy.load_distance, domain_policy.retain_distance); out->channels = channel; return true; } @@ -187,7 +193,7 @@ bool InNearTierBand(const DomainStreamPolicy& policy, f32 distance, bool current const f32 margin = policy.tier_hysteresis > 1.0f ? policy.tier_hysteresis : 1.0f; const f32 threshold = currently_near ? policy.full_tier_distance * margin : policy.full_tier_distance; - return std::isfinite(distance) && distance <= threshold; + return ::isfinite(distance) && distance <= threshold; } Tier ResolveTier(const WorldIndexData& index, const WorldCellRecord& cell, Domain domain, diff --git a/engine/world/world_map.h b/engine/world/world_map.h index faf7d6bc..c9440c76 100644 --- a/engine/world/world_map.h +++ b/engine/world/world_map.h @@ -1,13 +1,11 @@ #ifndef RX_WORLD_WORLD_MAP_H_ #define RX_WORLD_WORLD_MAP_H_ -#include -#include -#include - #include #include "asset/vfs.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "scene/world_streaming.h" #include "world/world_format.h" @@ -39,11 +37,11 @@ class RX_WORLD_EXPORT WorldMap { public: // `index_path` is a virtual path ("world://city/city.rxworld"). Payloads are // read from the same directory, under the CellPayloadPath convention. - bool Load(const asset::Vfs& vfs, std::string_view index_path, std::string* error); + bool Load(const asset::Vfs& vfs, base::StringRef index_path, base::String* error); bool loaded() const { return loaded_; } const WorldIndexData& index() const { return index_; } - const std::string& payload_prefix() const { return payload_prefix_; } + const base::String& payload_prefix() const { return payload_prefix_; } // Reads and decodes one payload. Fails when the entry is missing, corrupt, // describes a different cell, domain, tier or bake than the index promised, @@ -51,7 +49,7 @@ class RX_WORLD_EXPORT WorldMap { // payload that disagrees with the index it was addressed through means the // archive and the index came from different cooks. bool ReadPayload(const asset::Vfs& vfs, u64 cell, Domain domain, Tier tier, - WorldCellPayload* out, std::string* error) const; + WorldCellPayload* out, base::String* error) const; // Every cell this observer retains that has any payload for `domain`. // @@ -73,8 +71,8 @@ class RX_WORLD_EXPORT WorldMap { private: WorldIndexData index_; - std::string payload_prefix_; - std::string index_path_; + base::String payload_prefix_; + base::String index_path_; bool loaded_ = false; }; diff --git a/engine/world/world_overlay.cc b/engine/world/world_overlay.cc index 9e9bf883..6f1b47ee 100644 --- a/engine/world/world_overlay.cc +++ b/engine/world/world_overlay.cc @@ -1,10 +1,14 @@ +#include "base/algorithm.h" +#include "base/containers/span.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/strings/xstring.h" +#include "core/format.h" +#include "core/scalar.h" #include "world/world_overlay.h" -#include -#include -#include -#include -#include +#include +#include namespace rx::world { namespace { @@ -16,11 +20,11 @@ constexpr u32 kMaximumMoves = 16'000'000; constexpr u32 kDestroyedBytes = 8; constexpr u32 kMoveBytes = 40; -void SetError(std::string* error, std::string message) { - if (error) *error = std::move(message); +void SetError(base::String* error, base::String message) { + if (error) *error = base::move(message); } -u64 Checksum(u64 hash, std::span bytes) { +u64 Checksum(u64 hash, base::Span bytes) { for (u8 byte : bytes) { hash ^= byte; hash *= 0x100000001b3ull; @@ -31,7 +35,7 @@ u64 Checksum(u64 hash, std::span bytes) { // Header included: bake_id is what stops an overlay being applied to the wrong // cook, and a zeroed one reads as "not keyed to any bake", so leaving it out of // the checksum would let corruption re-enable exactly what it guards against. -u64 Checksum(std::span header, std::span body) { +u64 Checksum(base::Span header, base::Span body) { return Checksum(Checksum(0xcbf29ce484222325ull, header), body); } @@ -43,11 +47,11 @@ void AppendU64(base::Vector* bytes, u64 value) { for (u32 shift = 0; shift < 64; shift += 8) bytes->push_back(static_cast(value >> shift)); } -void AppendF32(base::Vector* bytes, f32 value) { AppendU32(bytes, std::bit_cast(value)); } +void AppendF32(base::Vector* bytes, f32 value) { AppendU32(bytes, rx::BitCast(value)); } class Cursor { public: - explicit Cursor(std::span bytes) : bytes_(bytes) {} + explicit Cursor(base::Span bytes) : bytes_(bytes) {} bool ok() const { return ok_; } size_t offset() const { return offset_; } @@ -77,40 +81,41 @@ class Cursor { for (u32 shift = 0; shift < 64; shift += 8) value |= static_cast(U8()) << shift; return value; } - f32 F32() { return std::bit_cast(U32()); } + f32 F32() { return rx::BitCast(U32()); } private: - std::span bytes_; + base::Span bytes_; size_t offset_ = 0; bool ok_ = true; }; bool Finite(const OverlayMove& move) { - return std::isfinite(move.position.x) && std::isfinite(move.position.y) && - std::isfinite(move.position.z) && std::isfinite(move.rotation.x) && - std::isfinite(move.rotation.y) && std::isfinite(move.rotation.z) && - std::isfinite(move.rotation.w) && std::isfinite(move.scale); + return ::isfinite(move.position.x) && ::isfinite(move.position.y) && + ::isfinite(move.position.z) && ::isfinite(move.rotation.x) && + ::isfinite(move.rotation.y) && ::isfinite(move.rotation.z) && + ::isfinite(move.rotation.w) && ::isfinite(move.scale); } } // namespace void WorldOverlay::Destroy(u64 stable_id) { - auto it = std::lower_bound(destroyed_.begin(), destroyed_.end(), stable_id); + auto it = base::LowerBound(destroyed_.begin(), destroyed_.end(), stable_id); if (it == destroyed_.end() || *it != stable_id) destroyed_.insert(it, stable_id); - auto move = std::lower_bound( + auto move = base::LowerBound( moves_.begin(), moves_.end(), stable_id, [](const OverlayMove& entry, u64 wanted) { return entry.stable_id < wanted; }); if (move != moves_.end() && move->stable_id == stable_id) moves_.erase(move); } bool WorldOverlay::IsDestroyed(u64 stable_id) const { - return std::binary_search(destroyed_.begin(), destroyed_.end(), stable_id); + const u64* it = base::LowerBound(destroyed_.begin(), destroyed_.end(), stable_id); + return it != destroyed_.end() && !(stable_id < *it); } bool WorldOverlay::Move(u64 stable_id, Vec3 position, Quat rotation, f32 scale) { if (IsDestroyed(stable_id)) return false; if (!Finite({stable_id, position, rotation, scale})) return false; - auto it = std::lower_bound( + auto it = base::LowerBound( moves_.begin(), moves_.end(), stable_id, [](const OverlayMove& entry, u64 wanted) { return entry.stable_id < wanted; }); if (it != moves_.end() && it->stable_id == stable_id) { @@ -124,16 +129,16 @@ bool WorldOverlay::Move(u64 stable_id, Vec3 position, Quat rotation, f32 scale) } const OverlayMove* WorldOverlay::FindMove(u64 stable_id) const { - auto it = std::lower_bound( + auto it = base::LowerBound( moves_.begin(), moves_.end(), stable_id, [](const OverlayMove& entry, u64 wanted) { return entry.stable_id < wanted; }); return it != moves_.end() && it->stable_id == stable_id ? it : nullptr; } void WorldOverlay::Forget(u64 stable_id) { - auto destroyed = std::lower_bound(destroyed_.begin(), destroyed_.end(), stable_id); + auto destroyed = base::LowerBound(destroyed_.begin(), destroyed_.end(), stable_id); if (destroyed != destroyed_.end() && *destroyed == stable_id) destroyed_.erase(destroyed); - auto move = std::lower_bound( + auto move = base::LowerBound( moves_.begin(), moves_.end(), stable_id, [](const OverlayMove& entry, u64 wanted) { return entry.stable_id < wanted; }); if (move != moves_.end() && move->stable_id == stable_id) moves_.erase(move); @@ -149,15 +154,15 @@ bool WorldOverlay::TouchesRange(u64 first, u32 count) const { // Saturating rather than wrapping: the index refuses a range that runs off the // end of the id space, but this is public and does not get to assume it. const u64 last = first > ~u64{0} - (count - 1) ? ~u64{0} : first + count - 1; - auto destroyed = std::lower_bound(destroyed_.begin(), destroyed_.end(), first); + auto destroyed = base::LowerBound(destroyed_.begin(), destroyed_.end(), first); if (destroyed != destroyed_.end() && *destroyed <= last) return true; - auto move = std::lower_bound( + auto move = base::LowerBound( moves_.begin(), moves_.end(), first, [](const OverlayMove& entry, u64 wanted) { return entry.stable_id < wanted; }); return move != moves_.end() && move->stable_id <= last; } -bool WorldOverlay::Encode(base::Vector* out, std::string* error) const { +bool WorldOverlay::Encode(base::Vector* out, base::String* error) const { if (!out) return false; if (destroyed_.size() > kMaximumDestroyed || moves_.size() > kMaximumMoves) { SetError(error, "world overlay: too many deltas"); @@ -178,30 +183,30 @@ bool WorldOverlay::Encode(base::Vector* out, std::string* error) const { } out->clear(); - out->insert(out->end(), std::begin(kMagic), std::end(kMagic)); + out->insert(out->end(), kMagic, kMagic + sizeof(kMagic)); AppendU32(out, kVersion); AppendU32(out, 0); // flags, reserved AppendU64(out, bake_id_); AppendU32(out, static_cast(destroyed_.size())); AppendU32(out, static_cast(moves_.size())); - AppendU64(out, Checksum(std::span(out->data(), out->size()), - std::span(body.data(), body.size()))); + AppendU64(out, Checksum(base::Span(out->data(), out->size()), + base::Span(body.data(), body.size()))); out->insert(out->end(), body.begin(), body.end()); return true; } -bool WorldOverlay::Decode(std::span bytes, WorldOverlay* out, std::string* error) { +bool WorldOverlay::Decode(base::Span bytes, WorldOverlay* out, base::String* error) { if (!out) return false; Cursor header(bytes); const u8* magic = nullptr; - if (!header.Take(sizeof(kMagic), &magic) || std::memcmp(magic, kMagic, sizeof(kMagic)) != 0) { + if (!header.Take(sizeof(kMagic), &magic) || base::MemCompare(magic, kMagic, sizeof(kMagic)) != 0) { SetError(error, "world overlay: not an RXOVRLAY file"); return false; } const u32 version = header.U32(); if (version != kVersion) { - SetError(error, "world overlay: version " + std::to_string(version) + ", expected " + - std::to_string(kVersion)); + SetError(error, "world overlay: version " + rx::ToString(version) + ", expected " + + rx::ToString(kVersion)); return false; } header.U32(); // flags, reserved @@ -222,11 +227,11 @@ bool WorldOverlay::Decode(std::span bytes, WorldOverlay* out, std::str const u64 expected = static_cast(destroyed_count) * kDestroyedBytes + static_cast(move_count) * kMoveBytes; if (bytes.size() - body_offset != expected) { - SetError(error, "world overlay: body is " + std::to_string(bytes.size() - body_offset) + - " bytes, header describes " + std::to_string(expected)); + SetError(error, "world overlay: body is " + rx::ToString(bytes.size() - body_offset) + + " bytes, header describes " + rx::ToString(expected)); return false; } - const std::span body = bytes.subspan(body_offset); + const base::Span body = bytes.subspan(body_offset); if (Checksum(bytes.first(body_offset - sizeof(u64)), body) != checksum) { SetError(error, "world overlay: checksum mismatch"); return false; @@ -245,7 +250,7 @@ bool WorldOverlay::Decode(std::span bytes, WorldOverlay* out, std::str // Sortedness is what makes IsDestroyed a binary search; a file that lost it // would answer wrongly rather than slowly. if (i > 0 && id <= decoded.destroyed_[i - 1]) { - SetError(error, "world overlay: destroyed ids are not sorted at " + std::to_string(i)); + SetError(error, "world overlay: destroyed ids are not sorted at " + rx::ToString(i)); return false; } decoded.destroyed_.push_back(id); @@ -263,16 +268,16 @@ bool WorldOverlay::Decode(std::span bytes, WorldOverlay* out, std::str move.rotation.w = cursor.F32(); move.scale = cursor.F32(); if (i > 0 && move.stable_id <= decoded.moves_[i - 1].stable_id) { - SetError(error, "world overlay: moves are not sorted at " + std::to_string(i)); + SetError(error, "world overlay: moves are not sorted at " + rx::ToString(i)); return false; } if (!Finite(move)) { - SetError(error, "world overlay: move " + std::to_string(move.stable_id) + + SetError(error, "world overlay: move " + rx::ToString(move.stable_id) + " has a non-finite transform"); return false; } if (decoded.IsDestroyed(move.stable_id)) { - SetError(error, "world overlay: " + std::to_string(move.stable_id) + + SetError(error, "world overlay: " + rx::ToString(move.stable_id) + " is both destroyed and moved"); return false; } @@ -282,7 +287,7 @@ bool WorldOverlay::Decode(std::span bytes, WorldOverlay* out, std::str SetError(error, "world overlay: truncated body"); return false; } - *out = std::move(decoded); + *out = base::move(decoded); return true; } diff --git a/engine/world/world_overlay.h b/engine/world/world_overlay.h index 8b894d08..5ed30b80 100644 --- a/engine/world/world_overlay.h +++ b/engine/world/world_overlay.h @@ -1,11 +1,11 @@ #ifndef RX_WORLD_WORLD_OVERLAY_H_ #define RX_WORLD_WORLD_OVERLAY_H_ -#include -#include #include +#include "base/containers/span.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "core/math.h" #include "core/types.h" @@ -63,11 +63,11 @@ class RX_WORLD_EXPORT WorldOverlay { bool empty() const { return destroyed_.empty() && moves_.empty(); } size_t destroyed_count() const { return destroyed_.size(); } size_t move_count() const { return moves_.size(); } - std::span destroyed() const { - return std::span(destroyed_.data(), destroyed_.size()); + base::Span destroyed() const { + return base::Span(destroyed_.data(), destroyed_.size()); } - std::span moves() const { - return std::span(moves_.data(), moves_.size()); + base::Span moves() const { + return base::Span(moves_.data(), moves_.size()); } // Whether anything at all is recorded for a cell's stable-id range. The @@ -75,8 +75,8 @@ class RX_WORLD_EXPORT WorldOverlay { // takes the bulk copy path rather than the row-by-row one. bool TouchesRange(u64 first, u32 count) const; - bool Encode(base::Vector* out, std::string* error) const; - static bool Decode(std::span bytes, WorldOverlay* out, std::string* error); + bool Encode(base::Vector* out, base::String* error) const; + static bool Decode(base::Span bytes, WorldOverlay* out, base::String* error); private: base::Vector destroyed_; // sorted diff --git a/engine/world/world_stream.cc b/engine/world/world_stream.cc index e4c21ef4..49df7b1b 100644 --- a/engine/world/world_stream.cc +++ b/engine/world/world_stream.cc @@ -1,12 +1,18 @@ #include "world/world_stream.h" -#include -#include -#include -#include +#include +#include #include +#include "base/algorithm.h" +#include "base/containers/span.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/format.h" +#include "core/scalar.h" #include "edit/reflect.h" #include "scene/components.h" @@ -52,10 +58,10 @@ Tier TierFromChannels(u32 channels) { // reference is refused outright. What cannot is a member nobody reflected - // neither this nor the layout hash can see one - so a component meant to be // baked has to be fully reflected, and that is a rule for whoever writes it. -bool BakeableContents(const edit::ComponentDesc& desc, std::string* error) { +bool BakeableContents(const edit::ComponentDesc& desc, base::String* error) { for (u32 i = 0; i < desc.prop_count; ++i) { if (desc.props[i].type != edit::PropType::kEntity) continue; - *error = std::string("field '") + desc.props[i].name + + *error = base::String("field '") + desc.props[i].name + "' is an entity reference, which does not survive a bake; refer across a cell by " "stable id instead"; return false; @@ -64,14 +70,14 @@ bool BakeableContents(const edit::ComponentDesc& desc, std::string* error) { } const ResidentInstance* FindSorted(const base::Vector& instances, u64 stable_id) { - auto it = std::lower_bound( + auto it = base::LowerBound( instances.begin(), instances.end(), stable_id, [](const ResidentInstance& instance, u64 wanted) { return instance.stable_id < wanted; }); return it != instances.end() && it->stable_id == stable_id ? it : nullptr; } ResidentInstance* FindSortedMutable(base::Vector* instances, u64 stable_id) { - auto it = std::lower_bound( + auto it = base::LowerBound( instances->begin(), instances->end(), stable_id, [](const ResidentInstance& instance, u64 wanted) { return instance.stable_id < wanted; }); return it != instances->end() && it->stable_id == stable_id ? it : nullptr; @@ -90,7 +96,7 @@ class ArchiveCellLoader final : public CellLoader { result.ok = map_.ReadPayload(vfs_, request.cell, request.domain, request.tier, &result.payload, &result.error); if (!result.ok) result.payload = WorldCellPayload{}; - ready_.push_back(std::move(result)); + ready_.push_back(base::move(result)); } void Cancel(const CellLoadRequest& request) override { @@ -107,7 +113,7 @@ class ArchiveCellLoader final : public CellLoader { void Poll(base::Vector* out) override { if (!out) return; - for (CellLoadResult& result : ready_) out->push_back(std::move(result)); + for (CellLoadResult& result : ready_) out->push_back(base::move(result)); ready_.clear(); } @@ -123,11 +129,11 @@ base::UniquePointer MakeArchiveCellLoader(const WorldMap& map, const return base::MakeUnique(map, vfs); } -bool RuntimeComponentLayout(std::string_view component, u32* stride, u64* layout_hash) { +bool RuntimeComponentLayout(base::StringRef component, u32* stride, u64* layout_hash) { const edit::ComponentDesc* desc = edit::FindComponentByName(component); if (!desc) return false; const ecs::ComponentInfo& info = ecs::GetComponentInfo(desc->id); - base::Vector names; + base::Vector names; base::Vector types; base::Vector offsets; names.reserve(desc->prop_count); @@ -141,9 +147,9 @@ bool RuntimeComponentLayout(std::string_view component, u32* stride, u64* layout if (stride) *stride = info.size; if (layout_hash) { *layout_hash = HashComponentLayout( - component, info.size, std::span(names.data(), names.size()), - std::span(types.data(), types.size()), - std::span(offsets.data(), offsets.size())); + component, info.size, base::Span(names.data(), names.size()), + base::Span(types.data(), types.size()), + base::Span(offsets.data(), offsets.size())); } return true; } @@ -165,8 +171,8 @@ bool WorldStreamer::SetOverlay(const WorldOverlay* overlay) { // cook order, so applying it here would delete and move whatever rows now // happen to carry those ids. if (overlay->bake_id() != 0 && overlay->bake_id() != map_.index().bake_id) { - RecordError("overlay was recorded against bake " + std::to_string(overlay->bake_id()) + - ", this world is bake " + std::to_string(map_.index().bake_id)); + RecordError("overlay was recorded against bake " + rx::ToString(overlay->bake_id()) + + ", this world is bake " + rx::ToString(map_.index().bake_id)); overlay_ = nullptr; return false; } @@ -175,38 +181,38 @@ bool WorldStreamer::SetOverlay(const WorldOverlay* overlay) { } WorldStreamer::DomainCell* WorldStreamer::Find(DomainState& state, u64 cell) { - auto it = std::lower_bound(state.cells.begin(), state.cells.end(), cell, + auto it = base::LowerBound(state.cells.begin(), state.cells.end(), cell, [](const DomainCell& entry, u64 wanted) { return entry.cell < wanted; }); return it != state.cells.end() && it->cell == cell ? it : nullptr; } const WorldStreamer::DomainCell* WorldStreamer::Find(const DomainState& state, u64 cell) const { - auto it = std::lower_bound(state.cells.begin(), state.cells.end(), cell, + auto it = base::LowerBound(state.cells.begin(), state.cells.end(), cell, [](const DomainCell& entry, u64 wanted) { return entry.cell < wanted; }); return it != state.cells.end() && it->cell == cell ? it : nullptr; } WorldStreamer::DomainCell& WorldStreamer::Emplace(DomainState& state, u64 cell) { - auto it = std::lower_bound(state.cells.begin(), state.cells.end(), cell, + auto it = base::LowerBound(state.cells.begin(), state.cells.end(), cell, [](const DomainCell& entry, u64 wanted) { return entry.cell < wanted; }); if (it != state.cells.end() && it->cell == cell) return *it; DomainCell created; created.cell = cell; - return *state.cells.insert(it, std::move(created)); + return *state.cells.insert(it, base::move(created)); } void WorldStreamer::Erase(DomainState& state, u64 cell) { if (DomainCell* entry = Find(state, cell)) state.cells.erase(entry); } -void WorldStreamer::RecordError(std::string message) { +void WorldStreamer::RecordError(base::String message) { ++error_count_; if (errors_.size() >= kMaximumRetainedErrors) return; - errors_.push_back(std::move(message)); + errors_.push_back(base::move(message)); } -std::span WorldStreamer::errors() const { - return std::span(errors_.data(), errors_.size()); +base::Span WorldStreamer::errors() const { + return base::Span(errors_.data(), errors_.size()); } u64 WorldStreamer::LayoutHash(const edit::ComponentDesc& desc) const { @@ -226,7 +232,7 @@ u64 WorldStreamer::LayoutHash(const edit::ComponentDesc& desc) const { return hash; } -bool WorldStreamer::ResolveSchema(DomainCell& cell, std::string* error) const { +bool WorldStreamer::ResolveSchema(DomainCell& cell, base::String* error) const { cell.resolved.clear(); const WorldCellPayload& payload = cell.payload; if (payload.kind == PayloadKind::kInstances) return true; @@ -235,7 +241,7 @@ bool WorldStreamer::ResolveSchema(DomainCell& cell, std::string* error) const { for (u32 a = 0; a < payload.archetypes.size(); ++a) { const WorldArchetypeRecord& archetype = payload.archetypes[a]; if (payload.StableIds(archetype).size() != archetype.row_count) { - *error = "archetype " + std::to_string(a) + " has no readable stable-id array"; + *error = "archetype " + rx::ToString(a) + " has no readable stable-id array"; return false; } ResolvedArchetype resolved; @@ -243,15 +249,15 @@ bool WorldStreamer::ResolveSchema(DomainCell& cell, std::string* error) const { resolved.column_ids.reserve(archetype.column_count); for (u32 c = 0; c < archetype.column_count; ++c) { const WorldColumnRecord& column = payload.columns[archetype.column_first + c]; - const std::string_view name = payload.String(column.name); + const base::StringRef name = payload.String(column.name); if (payload.ColumnBytes(column).size() != column.data_bytes) { - *error = "archetype " + std::to_string(a) + " column '" + std::string(name) + + *error = "archetype " + rx::ToString(a) + " column '" + base::String(name) + "' is not readable"; return false; } const edit::ComponentDesc* desc = edit::FindComponentByName(name); if (!desc) { - *error = "component '" + std::string(name) + + *error = "component '" + base::String(name) + "' is not registered in this build; the cook and the runtime disagree about what " "a world may contain"; return false; @@ -262,27 +268,27 @@ bool WorldStreamer::ResolveSchema(DomainCell& cell, std::string* error) const { } const ecs::ComponentInfo& info = ecs::GetComponentInfo(desc->id); if (!info.trivially_copyable) { - *error = "component '" + std::string(name) + + *error = "component '" + base::String(name) + "' needs a constructor and cannot be restored by copying bytes"; return false; } if (!BakeableContents(*desc, error)) { - *error = "component '" + std::string(name) + "' " + *error; + *error = "component '" + base::String(name) + "' " + *error; return false; } if (info.size != column.stride) { - *error = "component '" + std::string(name) + "' is " + std::to_string(info.size) + - " bytes here, " + std::to_string(column.stride) + " in the bake"; + *error = "component '" + base::String(name) + "' is " + rx::ToString(info.size) + + " bytes here, " + rx::ToString(column.stride) + " in the bake"; return false; } if (LayoutHash(*desc) != column.layout_hash) { - *error = "component '" + std::string(name) + + *error = "component '" + base::String(name) + "' has a different field layout here than it had at bake time"; return false; } for (ecs::ComponentId existing : resolved.column_ids) { if (existing != desc->id) continue; - *error = "archetype " + std::to_string(a) + " lists component '" + std::string(name) + + *error = "archetype " + rx::ToString(a) + " lists component '" + base::String(name) + "' twice"; return false; } @@ -292,8 +298,9 @@ bool WorldStreamer::ResolveSchema(DomainCell& cell, std::string* error) const { resolved.signature.push_back(desc->id); resolved.column_ids.push_back(desc->id); } - std::sort(resolved.signature.begin(), resolved.signature.end()); - cell.resolved.push_back(std::move(resolved)); + // Equal ids are indistinguishable, so any correct sort agrees. + base::Sort(resolved.signature.begin(), resolved.signature.end()); + cell.resolved.push_back(base::move(resolved)); } return true; } @@ -307,11 +314,11 @@ bool WorldStreamer::MaterializeStep(DomainCell& cell, u32 rows) { if (cell.prototypes.empty() && !payload.prototypes.empty()) { cell.prototypes.reserve(payload.prototypes.size()); for (const WorldPrototypeRecord& prototype : payload.prototypes) { - cell.prototypes.push_back(std::string(payload.String(prototype.name))); + cell.prototypes.push_back(base::String(payload.String(prototype.name))); } } const u32 total = static_cast(payload.instances.size()); - const u32 take = std::min(rows, total - cell.next_instance); + const u32 take = rx::Min(rows, total - cell.next_instance); for (u32 i = 0; i < take; ++i) { const WorldInstanceRecord& source = payload.instances[cell.next_instance + i]; if (overlaid && overlay_->IsDestroyed(source.stable_id)) continue; @@ -341,9 +348,9 @@ bool WorldStreamer::MaterializeStep(DomainCell& cell, u32 rows) { continue; } const ResolvedArchetype& resolved = cell.resolved[cell.next_archetype]; - const std::span stable_ids = payload.StableIds(archetype); + const base::Span stable_ids = payload.StableIds(archetype); const u32 first = cell.next_row; - const u32 take = std::min(remaining, budget); + const u32 take = rx::Min(remaining, budget); const u64 owning_cell = cell.cell; // A destroyed row is skipped here rather than created and destroyed after @@ -363,7 +370,7 @@ bool WorldStreamer::MaterializeStep(DomainCell& cell, u32 rows) { auto source_row = [&](u32 i) { return overlaid ? rows_scratch_[i] : first + i; }; for (u32 c = 0; c < archetype.column_count; ++c) { const WorldColumnRecord& column = payload.columns[archetype.column_first + c]; - const std::span bytes = payload.ColumnBytes(column); + const base::Span bytes = payload.ColumnBytes(column); const ecs::ComponentId id = resolved.column_ids[c]; if (!overlaid) { const u8* source = bytes.data() + static_cast(first) * column.stride; @@ -375,7 +382,7 @@ bool WorldStreamer::MaterializeStep(DomainCell& cell, u32 rows) { // was created from, so a missing run would mean the batch is not // the archetype we asked for. BASE_BUGCHECK(destination != nullptr && run != 0, "world batch column vanished"); - std::memcpy(destination, source + static_cast(written) * column.stride, + base::MemCopy(destination, source + static_cast(written) * column.stride, static_cast(run) * column.stride); written += run; } @@ -385,7 +392,7 @@ bool WorldStreamer::MaterializeStep(DomainCell& cell, u32 rows) { u32 run = 0; void* destination = batch.Column(id, i, &run); BASE_BUGCHECK(destination != nullptr && run != 0, "world batch column vanished"); - std::memcpy(destination, + base::MemCopy(destination, bytes.data() + static_cast(source_row(i)) * column.stride, column.stride); } @@ -465,7 +472,7 @@ void WorldStreamer::DrainLoader() { // own, so it indexes an array only after it has been checked. if (static_cast(result.domain) >= kDomainCount) { RecordError("a cell loader returned an unknown domain for cell " + - std::to_string(result.cell)); + rx::ToString(result.cell)); continue; } DomainState& state = domains_[static_cast(result.domain)]; @@ -476,21 +483,21 @@ void WorldStreamer::DrainLoader() { // A read failure already names the path it failed on; a schema failure is // about the payload's contents and does not. - std::string error; + base::String error; if (result.ok) { - cell->payload = std::move(result.payload); + cell->payload = base::move(result.payload); if (!ResolveSchema(*cell, &error)) { result.ok = false; error = CellPayloadPath(map_.payload_prefix(), result.cell, result.domain, result.tier) + ": " + error; } } else { - error = std::move(result.error); + error = base::move(result.error); } if (!result.ok) { NoteLoadFailure(state, result.cell); - RecordError(std::move(error)); + RecordError(base::move(error)); cell->payload = WorldCellPayload{}; cell->resolved.clear(); // Out of kLoading, so a loader that delivers the same ticket twice cannot @@ -510,7 +517,7 @@ void WorldStreamer::DrainLoader() { } // A load that succeeds clears the cell's tally: only a run of failures // should latch, and a transient one followed by a read is not that. - auto failed = std::lower_bound( + auto failed = base::LowerBound( state.failed.begin(), state.failed.end(), cell->cell, [](const FailedCell& entry, u64 wanted) { return entry.cell < wanted; }); if (failed != state.failed.end() && failed->cell == cell->cell) state.failed.erase(failed); @@ -525,7 +532,7 @@ void WorldStreamer::DrainLoader() { } void WorldStreamer::AdvanceRetirements(Domain domain, DomainState& state) { - const u32 rows = std::max(1u, policy_[domain].rows_per_commit); + const u32 rows = rx::Max(1u, policy_[domain].rows_per_commit); for (size_t i = 0; i < state.cells.size();) { DomainCell& cell = state.cells[i]; if (cell.phase != CellPhase::kRetiring) { @@ -584,7 +591,7 @@ void WorldStreamer::GatherClaims(Domain domain, DomainState& state) { // the near tier for as long as the lease lived. One that does ask counts // exactly like an observer standing there, which is what it is. state.demands.push_back({region, - std::min(demand.current_distance, demand.predicted_distance), + rx::Min(demand.current_distance, demand.predicted_distance), /*from_claim=*/!entry.claim.full_detail}); } } @@ -593,11 +600,14 @@ void WorldStreamer::MergeCandidates(Domain domain, DomainState& state) { const DomainStreamPolicy& domain_policy = policy_[domain]; const u32 channel = 1u << static_cast(domain); - std::sort(state.demands.begin(), state.demands.end(), - [](const CellDemand& a, const CellDemand& b) { - if (a.region.id != b.region.id) return a.region.id < b.region.id; - return a.distance < b.distance; - }); + // Every demand for one id carries the same region (built from that cell's + // record for this domain), and the run below reads only that region and an + // order-free minimum, so any correct sort agrees. + base::Sort(state.demands.begin(), state.demands.end(), + [](const CellDemand& a, const CellDemand& b) { + if (a.region.id != b.region.id) return a.region.id < b.region.id; + return a.distance < b.distance; + }); state.candidates.clear(); state.bands_scratch.clear(); @@ -612,10 +622,10 @@ void WorldStreamer::MergeCandidates(Domain domain, DomainState& state) { // stands at the cell's own middle, so letting it into this would mean that // taking or dropping a lease evicts and rebuilds a cell that was already // resident and correct, in both directions. - f32 band_distance = std::numeric_limits::infinity(); + f32 band_distance = INFINITY; for (size_t d = i; d < end; ++d) { if (state.demands[d].from_claim) continue; - band_distance = std::min(band_distance, state.demands[d].distance); + band_distance = rx::Min(band_distance, state.demands[d].distance); } i = end; @@ -624,7 +634,7 @@ void WorldStreamer::MergeCandidates(Domain domain, DomainState& state) { const WorldCellRecord* record = map_.index().FindCell(id); if (!record) continue; - auto previous = std::lower_bound( + auto previous = base::LowerBound( state.bands.begin(), state.bands.end(), id, [](const CellBand& entry, u64 wanted) { return entry.cell < wanted; }); const bool was_near = @@ -641,14 +651,14 @@ void WorldStreamer::MergeCandidates(Domain domain, DomainState& state) { region.channels = channel | TierChannel(tier); // Correctness is admitted before quality: a hard claim's cell outranks a // nearer cell that only the picture depends on. - if (claims_) region.priority = std::max(region.priority, claims_->Priority(id, channel)); + if (claims_) region.priority = rx::Max(region.priority, claims_->Priority(id, channel)); state.candidates.push_back(region); } state.bands = state.bands_scratch; } void WorldStreamer::NoteLoadFailure(DomainState& state, u64 cell) { - auto it = std::lower_bound(state.failed.begin(), state.failed.end(), cell, + auto it = base::LowerBound(state.failed.begin(), state.failed.end(), cell, [](const FailedCell& entry, u64 wanted) { return entry.cell < wanted; }); if (it == state.failed.end() || it->cell != cell) { it = state.failed.insert(it, FailedCell{cell, 0, 0}); @@ -658,7 +668,7 @@ void WorldStreamer::NoteLoadFailure(DomainState& state, u64 cell) { } bool WorldStreamer::Suppressed(const DomainState& state, u64 cell) const { - auto it = std::lower_bound(state.failed.begin(), state.failed.end(), cell, + auto it = base::LowerBound(state.failed.begin(), state.failed.end(), cell, [](const FailedCell& entry, u64 wanted) { return entry.cell < wanted; }); if (it == state.failed.end() || it->cell != cell) return false; return it->attempts >= kMaximumLoadAttempts && tick_ < it->retry_at_tick; @@ -669,7 +679,7 @@ void WorldStreamer::ClearFailures() { } void WorldStreamer::UpdateDomain(Domain domain, - std::span observers) { + base::Span observers) { DomainState& state = domains_[static_cast(domain)]; const DomainStreamPolicy& domain_policy = policy_[domain]; @@ -687,9 +697,9 @@ void WorldStreamer::UpdateDomain(Domain domain, AdvanceRetirements(domain, state); scene::AdvanceWorldStreaming( - state.plan, std::span(state.observations.data(), + state.plan, base::Span(state.observations.data(), state.observations.size()), - std::span(state.candidates.data(), state.candidates.size()), + base::Span(state.candidates.data(), state.candidates.size()), domain_policy.budget, &state.actions); for (const scene::WorldStreamAction& action : state.actions) { @@ -701,7 +711,7 @@ void WorldStreamer::UpdateDomain(Domain domain, const WorldCellRecord* record = map_.index().FindCell(action.region.id); const Tier tier = TierFromChannels(action.region.channels); if (!record || tier == Tier::kAbsent) { - RecordError("cell " + std::to_string(action.region.id) + " " + DomainName(domain) + + RecordError("cell " + rx::ToString(action.region.id) + " " + DomainName(domain) + ": prepared with no tier the index knows"); scene::ApplyWorldStreamPrepareResult(state.plan, action.ticket, scene::WorldStreamPrepareResult::kFailed); @@ -734,25 +744,27 @@ void WorldStreamer::UpdateDomain(Domain domain, // Unreachable: the plan only commits what it saw prepared. Recorded // rather than swallowed so that if it ever does happen, the message // is the thing that says so. - RecordError("cell " + std::to_string(action.ticket.region) + " " + DomainName(domain) + + RecordError("cell " + rx::ToString(action.ticket.region) + " " + DomainName(domain) + ": commit arrived for a cell that is not decoded"); scene::ApplyWorldStreamCommitResult(state.plan, action.ticket, scene::WorldStreamCommitResult::kFailed); break; } - if (!MaterializeStep(*cell, std::max(1u, domain_policy.rows_per_commit))) { + if (!MaterializeStep(*cell, rx::Max(1u, domain_policy.rows_per_commit))) { scene::ApplyWorldStreamCommitResult(state.plan, action.ticket, scene::WorldStreamCommitResult::kMoreWork); break; } - std::sort(cell->entities.begin(), cell->entities.end(), - [](const StableEntity& a, const StableEntity& b) { - return a.stable_id < b.stable_id; - }); - std::sort(cell->instances.begin(), cell->instances.end(), - [](const ResidentInstance& a, const ResidentInstance& b) { - return a.stable_id < b.stable_id; - }); + // Stable ids are unique within a cell (the payload decoder refuses a + // duplicate), so any correct sort agrees. + base::Sort(cell->entities.begin(), cell->entities.end(), + [](const StableEntity& a, const StableEntity& b) { + return a.stable_id < b.stable_id; + }); + base::Sort(cell->instances.begin(), cell->instances.end(), + [](const ResidentInstance& a, const ResidentInstance& b) { + return a.stable_id < b.stable_id; + }); cell->phase = CellPhase::kPublished; // The payload's bytes have served their purpose; the ECS and the // instance page own the state now. @@ -790,7 +802,7 @@ void WorldStreamer::UpdateDomain(Domain domain, } } -void WorldStreamer::Update(std::span observers) { +void WorldStreamer::Update(base::Span observers) { if (shut_down_) return; ++tick_; DrainLoader(); @@ -832,7 +844,7 @@ ecs::Entity WorldStreamer::Resolve(u64 stable_id) const { // A cell that is still materializing, or already dying, has no answer to // give: half its rows exist and the other half never will. if (!cell || cell->phase != CellPhase::kPublished) continue; - auto it = std::lower_bound( + auto it = base::LowerBound( cell->entities.begin(), cell->entities.end(), stable_id, [](const StableEntity& entry, u64 wanted) { return entry.stable_id < wanted; }); // A game that destroys a cell's entity itself leaves the record behind @@ -845,11 +857,11 @@ ecs::Entity WorldStreamer::Resolve(u64 stable_id) const { return {}; } -std::span WorldStreamer::Instances(u64 cell_id, Domain domain) const { +base::Span WorldStreamer::Instances(u64 cell_id, Domain domain) const { if (static_cast(domain) >= kDomainCount) return {}; const DomainCell* cell = Find(domains_[static_cast(domain)], cell_id); if (!cell || cell->phase != CellPhase::kPublished) return {}; - return std::span(cell->instances.data(), cell->instances.size()); + return base::Span(cell->instances.data(), cell->instances.size()); } void WorldStreamer::ResidentCells(Domain domain, base::Vector* out) const { @@ -862,11 +874,11 @@ void WorldStreamer::ResidentCells(Domain domain, base::Vector* out) const { } } -std::span WorldStreamer::Prototypes(u64 cell_id, Domain domain) const { +base::Span WorldStreamer::Prototypes(u64 cell_id, Domain domain) const { if (static_cast(domain) >= kDomainCount) return {}; const DomainCell* cell = Find(domains_[static_cast(domain)], cell_id); if (!cell || cell->phase != CellPhase::kPublished) return {}; - return std::span(cell->prototypes.data(), cell->prototypes.size()); + return base::Span(cell->prototypes.data(), cell->prototypes.size()); } const ResidentInstance* WorldStreamer::FindInstance(u64 stable_id) const { @@ -889,7 +901,7 @@ ecs::Entity WorldStreamer::Promote(u64 stable_id) { if (!cell || cell->phase != CellPhase::kPublished) continue; ResidentInstance* instance = FindSortedMutable(&cell->instances, stable_id); if (!instance) continue; - auto slot = std::lower_bound( + auto slot = base::LowerBound( cell->entities.begin(), cell->entities.end(), stable_id, [](const StableEntity& entry, u64 wanted) { return entry.stable_id < wanted; }); if (slot != cell->entities.end() && slot->stable_id == stable_id) return {}; // already promoted diff --git a/engine/world/world_stream.h b/engine/world/world_stream.h index 3874b7ed..6611d211 100644 --- a/engine/world/world_stream.h +++ b/engine/world/world_stream.h @@ -1,14 +1,15 @@ #ifndef RX_WORLD_WORLD_STREAM_H_ #define RX_WORLD_WORLD_STREAM_H_ -#include -#include #include #include #include #include "asset/vfs.h" +#include "base/containers/span.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/export.h" #include "ecs/world.h" #include "edit/reflect.h" @@ -60,7 +61,7 @@ struct CellLoadResult { Domain domain = Domain::kGameplay; Tier tier = Tier::kAbsent; bool ok = false; - std::string error; + base::String error; WorldCellPayload payload; }; @@ -165,7 +166,7 @@ class RX_WORLD_EXPORT WorldStreamer { // One tick. `observers` are world-space streaming sources - the player, the // camera, a teleport destination, an AI route request; the streamer derives // one bubble per domain from each. - void Update(std::span observers); + void Update(base::Span observers); // Retires everything and drains it in this call, so the ecs::World can be // destroyed afterwards. Terminal and idempotent: Update does nothing after @@ -185,7 +186,7 @@ class RX_WORLD_EXPORT WorldStreamer { // // The span is invalidated by the next Update, which may move or free the // cell's storage; copy anything that has to outlive the tick. - std::span Instances(u64 cell, + base::Span Instances(u64 cell, Domain domain = Domain::kRepresentation) const; // Every cell of a domain that is published right now, in ascending id order. @@ -198,7 +199,7 @@ class RX_WORLD_EXPORT WorldStreamer { // cell for as long as it is resident, because the payload they were decoded // from is dropped the moment the cell is published. Same span lifetime as // Instances. - std::span Prototypes(u64 cell, + base::Span Prototypes(u64 cell, Domain domain = Domain::kRepresentation) const; // The instance carrying `stable_id`, or null. const ResidentInstance* FindInstance(u64 stable_id) const; @@ -224,7 +225,7 @@ class RX_WORLD_EXPORT WorldStreamer { // payload is a cook or archive bug, so the messages are kept rather than // counted: knowing which cell and why is the whole value. The span is // invalidated by the next failure. - std::span errors() const; + base::Span errors() const; private: struct StableEntity { @@ -262,7 +263,7 @@ class RX_WORLD_EXPORT WorldStreamer { base::Vector instances; // Lifted out of the payload before it is dropped, so a resident instance // can still say what it is. - base::Vector prototypes; + base::Vector prototypes; // Whether the overlay has anything to say about this cell's stable-id // range. Decided once, when the payload arrives, so an untouched cell takes // the bulk copy path rather than the row-by-row one. @@ -312,7 +313,7 @@ class RX_WORLD_EXPORT WorldStreamer { void Erase(DomainState& state, u64 cell); void DrainLoader(); - void UpdateDomain(Domain domain, std::span observers); + void UpdateDomain(Domain domain, base::Span observers); void GatherClaims(Domain domain, DomainState& state); // Folds this tick's per-observer demands into one candidate per cell: nearest // distance wins, the resolved tier joins the region's identity, claims raise @@ -324,12 +325,12 @@ class RX_WORLD_EXPORT WorldStreamer { // The reflected layout hash of a component as this build sees it, memoized. u64 LayoutHash(const edit::ComponentDesc& desc) const; - bool ResolveSchema(DomainCell& cell, std::string* error) const; + bool ResolveSchema(DomainCell& cell, base::String* error) const; // Materializes at most `rows` rows; true when the cell is fully published. bool MaterializeStep(DomainCell& cell, u32 rows); // Destroys at most `rows` rows; true when nothing of the cell is left. bool TeardownStep(DomainCell& cell, u32 rows); - void RecordError(std::string message); + void RecordError(base::String message); const WorldMap& map_; CellLoader& loader_; @@ -342,7 +343,7 @@ class RX_WORLD_EXPORT WorldStreamer { base::Vector rows_scratch_; // Component id to its reflected layout hash; see LayoutHash. mutable base::UnorderedMap layout_hashes_; - base::Vector errors_; + base::Vector errors_; u32 error_count_ = 0; u64 tick_ = 0; bool shut_down_ = false; @@ -351,7 +352,7 @@ class RX_WORLD_EXPORT WorldStreamer { // The reflected layout of a component as the running build sees it, in the same // terms HashComponentLayout hashes at cook time. A cook and a runtime that // disagree here disagree about the bytes of the struct. -RX_WORLD_EXPORT bool RuntimeComponentLayout(std::string_view component, u32* stride, +RX_WORLD_EXPORT bool RuntimeComponentLayout(base::StringRef component, u32* stride, u64* layout_hash); } // namespace rx::world diff --git a/examples/body_jiggle.cc b/examples/body_jiggle.cc index 71163cd2..e75feaa6 100644 --- a/examples/body_jiggle.cc +++ b/examples/body_jiggle.cc @@ -1,11 +1,11 @@ -#include -#include -#include -#include -#include +#include +#include #include "anim/body_dynamics.h" #include "asset/asset_id.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/scalar.h" namespace { @@ -41,7 +41,7 @@ asset::Skeleton MakeExampleRig() { } f32 MorphWeight(const base::Vector &weights, - std::string_view name) { + base::StringRef name) { const rx::u64 hash = asset::MakeAssetId(name).hash; for (const BodyMorphWeight &weight : weights) { if (weight.target == hash) @@ -50,13 +50,13 @@ f32 MorphWeight(const base::Vector &weights, return 0; } -std::string Meter(f32 displacement) { +base::String Meter(f32 displacement) { constexpr int kHalfWidth = 12; // One cell is 4 mm. Positive motion appears right of the rest marker. const int cell = - std::clamp(static_cast(std::round(displacement / 0.004f)), + rx::Clamp(static_cast(::round(displacement / 0.004f)), -kHalfWidth, kHalfWidth); - std::string meter(kHalfWidth * 2 + 1, ' '); + base::String meter(kHalfWidth * 2 + 1, ' '); meter[kHalfWidth] = '|'; if (cell != 0) meter[kHalfWidth + cell] = 'o'; @@ -89,9 +89,9 @@ int main() { constexpr rx::u32 kFrames = 240; base::Vector morphs; - std::puts( + ::puts( "Body jiggle example (o = vertical helper-bone displacement, | = rest)"); - std::puts(" event time chest abdomen " + ::puts(" event time chest abdomen " " compress impact"); for (rx::u32 frame_index = 0; frame_index < kFrames; ++frame_index) { @@ -99,7 +99,7 @@ int main() { pose.ResetToBind(skeleton); // base animation/IK would write the pose here // A subtle gait bob exercises animation-derived driver acceleration. - pose.translation[1].y += std::sin(time * 3.14159265f * 4.0f) * 0.006f; + pose.translation[1].y += ::sin(time * 3.14159265f * 4.0f) * 0.006f; BodyDynamicsFrame frame; const char *event = "settle"; @@ -126,15 +126,15 @@ int main() { const BodyRegionSample abdomen_sample = body.sample(1); const f32 compression = MorphWeight(morphs, "chestCompression"); const f32 impact = MorphWeight(morphs, "chestImpact"); - std::printf(" %-10s %5.2f [%s] [%s] %5.2f %5.2f\n", event, time, + ::printf(" %-10s %5.2f [%s] [%s] %5.2f %5.2f\n", event, time, Meter(chest_sample.translation.y).c_str(), Meter(abdomen_sample.translation.y).c_str(), compression, impact); } - std::puts("\nIn a renderer, upload pose after BodyDynamics::Update and add " + ::puts("\nIn a renderer, upload pose after BodyDynamics::Update and add " "the emitted"); - std::puts("body morph weights to the character's existing dense morph-weight " + ::puts("body morph weights to the character's existing dense morph-weight " "array."); return 0; } diff --git a/runtime/camera_input.cc b/runtime/camera_input.cc index be4c2247..1e8ef52f 100644 --- a/runtime/camera_input.cc +++ b/runtime/camera_input.cc @@ -1,18 +1,21 @@ #include "viewer.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include #include +#include "base/memory/move.h" +#include "base/strings/xstring.h" #include "core/log.h" #include "core/math.h" +#include "core/scalar.h" #include "demo_scenes.h" #include "scene/components.h" +#include "core/file_system.h" +#include +#include // Camera and input: routes per-frame input to the free-fly camera, the // scripted camera drivers (orbit / replay / cinematic showcase / record), and @@ -51,8 +54,8 @@ void Viewer::UpdateCamera(f32 frame_delta) { void Viewer::LookCameraAt(const Vec3& eye, const Vec3& center) { camera_.set_position(eye); Vec3 d = Normalize(center - eye); - camera_.set_yaw_pitch(std::atan2(d.x, -d.z), - std::asin(std::clamp(d.y, -1.0f, 1.0f))); // forward() convention + camera_.set_yaw_pitch(::atan2(d.x, -d.z), + ::asin(rx::Clamp(d.y, -1.0f, 1.0f))); // forward() convention } void Viewer::DriveCamera(f32 dt) { @@ -63,20 +66,20 @@ void Viewer::DriveCamera(f32 dt) { if (const char* c = Cam.get()) { Vec3 p{}; f32 yaw = 0, pitch = 0; - if (std::sscanf(c, "%f,%f,%f,%f,%f", &p.x, &p.y, &p.z, &yaw, &pitch) >= 3) { + if (::sscanf(c, "%f,%f,%f,%f,%f", &p.x, &p.y, &p.z, &yaw, &pitch) >= 3) { camera_.set_position(p); camera_.set_yaw_pitch(yaw, pitch); } } cam_orbit_ = bool(Orbit); - if (const char* r = Record.get()) cam_record_ = std::fopen(r, "wb"); + if (const char* r = Record.get()) cam_record_ = ::fopen(r, "wb"); if (const char* p = Replay.get()) { - if (std::FILE* f = std::fopen(p, "rb")) { + if (FILE* f = ::fopen(p, "rb")) { f32 key[7]; - while (std::fread(key, sizeof(f32), 7, f) == 7) { + while (::fread(key, sizeof(f32), 7, f) == 7) { cam_replay_.push_back({key[0], {key[1], key[2], key[3]}, {key[4], key[5], key[6]}}); } - std::fclose(f); + ::fclose(f); RX_INFO("camera replay: {} keys from {}", cam_replay_.size(), p); } } @@ -88,11 +91,10 @@ void Viewer::DriveCamera(f32 dt) { cam_showcase_ = !showcase_.empty(); if (const char* d = ShowcaseShots.get()) { showcase_shot_dir_ = d; - std::error_code error; - std::filesystem::create_directories(showcase_shot_dir_, error); - if (error) { + fs::CreateDirectories(showcase_shot_dir_); + if (!fs::IsDirectory(showcase_shot_dir_)) { RX_WARN("showcase: cannot create capture directory {}: {}", showcase_shot_dir_, - error.message()); + ::strerror(errno)); showcase_shot_dir_.clear(); } } @@ -111,7 +113,7 @@ void Viewer::DriveCamera(f32 dt) { ShowcasePose p = showcase_.Sample(cam_time_); LookCameraAt(p.eye, p.target); if (!showcase_shot_dir_.empty()) { - std::string label; + base::String label; int idx = showcase_.CaptureCrossed(prev, cam_time_, &label); if (idx >= 0) { for (char& ch : label) { @@ -120,7 +122,7 @@ void Viewer::DriveCamera(f32 dt) { if (!ok) ch = '_'; } char path[1024]; - std::snprintf(path, sizeof(path), "%s/%02d_%s.png", showcase_shot_dir_.c_str(), idx, + ::snprintf(path, sizeof(path), "%s/%02d_%s.png", showcase_shot_dir_.c_str(), idx, label.c_str()); renderer_->CaptureScreenshot(path); RX_INFO("showcase capture: {}", path); @@ -131,8 +133,8 @@ void Viewer::DriveCamera(f32 dt) { // half-second-plus frame (cold streaming/IO hitches, not GPU time). Real // mid-flight stutter (down to ~2 fps) is still counted, so it shows up. if (dt > 0 && dt < 0.5f && cam_time_ >= 1.0f) { - showcase_dt_min_ = std::min(showcase_dt_min_, dt); - showcase_dt_max_ = std::max(showcase_dt_max_, dt); + showcase_dt_min_ = rx::Min(showcase_dt_min_, dt); + showcase_dt_max_ = rx::Max(showcase_dt_max_, dt); showcase_bench_time_ += dt; ++showcase_frames_; } @@ -150,7 +152,7 @@ void Viewer::DriveCamera(f32 dt) { } else if (cam_orbit_) { f32 a = cam_time_ * 0.4f; // radians/sec Vec3 center{0.0f, 1.0f, 0.0f}; - LookCameraAt({center.x + std::cos(a) * 6.0f, 2.4f, center.z + std::sin(a) * 6.0f}, center); + LookCameraAt({center.x + ::cos(a) * 6.0f, 2.4f, center.z + ::sin(a) * 6.0f}, center); } else if (!cam_replay_.empty()) { // Linear interpolation between the bracketing keys for the current time. const CamKey* lo = &cam_replay_[0]; @@ -163,7 +165,7 @@ void Viewer::DriveCamera(f32 dt) { } } f32 span = hi->t - lo->t; - f32 u = span > 1e-5f ? std::clamp((cam_time_ - lo->t) / span, 0.0f, 1.0f) : 0.0f; + f32 u = span > 1e-5f ? rx::Clamp((cam_time_ - lo->t) / span, 0.0f, 1.0f) : 0.0f; auto mix = [&](const Vec3& a, const Vec3& b) { return a + (b - a) * u; }; LookCameraAt(mix(lo->pos, hi->pos), mix(lo->target, hi->target)); } @@ -171,8 +173,8 @@ void Viewer::DriveCamera(f32 dt) { if (cam_record_) { Vec3 p = camera_.position(), t = camera_.target(); f32 key[7] = {cam_time_, p.x, p.y, p.z, t.x, t.y, t.z}; - std::fwrite(key, sizeof(f32), 7, cam_record_); - std::fflush(cam_record_); // survive a timeout kill + ::fwrite(key, sizeof(f32), 7, cam_record_); + ::fflush(cam_record_); // survive a timeout kill } } @@ -182,8 +184,8 @@ void Viewer::BuildShowcase() { // A drone pass over the origin-anchored scene: establishing wide, descending // push-in, low skim, crane-up reveal. Every waypoint sits at ~zero velocity // (smoothstep), so each is a clean, well-framed still. - auto wp = [](Vec3 eye, Vec3 look, f32 travel, bool capture, std::string label) { - return ShowcaseCamera::Waypoint{eye, look, travel, capture, std::move(label)}; + auto wp = [](Vec3 eye, Vec3 look, f32 travel, bool capture, base::String label) { + return ShowcaseCamera::Waypoint{eye, look, travel, capture, base::move(label)}; }; const Vec3 c{0, 0, 0}; showcase_.Add(wp(c + Vec3{-18, 11, 8}, c + Vec3{-2, 2.5f, 0}, 0.0f, false, {})); diff --git a/runtime/debug_ui.cc b/runtime/debug_ui.cc index e1159485..56a902c5 100644 --- a/runtime/debug_ui.cc +++ b/runtime/debug_ui.cc @@ -4,15 +4,10 @@ #if defined(RX_HAS_IMGUI) -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -22,14 +17,20 @@ #include #include "asset/vfs.h" +#include "base/containers/vector.h" +#include "base/optional.h" +#include "base/strings/xstring.h" #include "core/log.h" #include "core/memory/chunk_pool.h" #include "core/memory/frame_arena.h" #include "core/memory/memory_tracker.h" +#include "core/scalar.h" #include "ecs/world.h" #include "render/core/presets.h" #include "render/core/settings_ini.h" #include "render/util/imgui_theme.h" +#include "base/algorithm.h" +#include "core/file_system.h" #ifndef RX_BUILD_ID #define RX_BUILD_ID "unknown" @@ -53,12 +54,12 @@ base::Option PresetsDirOpt{"presets.dir", nullptr, "RX_PRESETS_DIR" // Directory holding the .ini render presets: RX_PRESETS_DIR, else the // compiled-in source path, else a cwd-relative fallback. -std::filesystem::path PresetDir() { +base::String PresetDir() { if (const char* env = PresetsDirOpt.get(); env && *env) return env; #ifdef RX_PRESETS_DIR_DEFAULT - return std::filesystem::path(RX_PRESETS_DIR_DEFAULT); + return RX_PRESETS_DIR_DEFAULT; #else - return std::filesystem::path("engine/render/presets"); + return "engine/render/presets"; #endif } @@ -95,15 +96,15 @@ constexpr const char* kBuildMode = "DEBUG"; constexpr const char* kBuildModeTiny = "D"; #endif -void FormatMemoryValue(char* out, std::size_t out_size, u64 bytes) { +void FormatMemoryValue(char* out, size_t out_size, u64 bytes) { constexpr u64 kMiB = 1024u * 1024u; constexpr u64 kGiB = 1024u * kMiB; if (bytes == 0) - std::snprintf(out, out_size, "--"); + ::snprintf(out, out_size, "--"); else if (bytes >= kGiB) - std::snprintf(out, out_size, "%.1fG", static_cast(bytes) / kGiB); + ::snprintf(out, out_size, "%.1fG", static_cast(bytes) / kGiB); else - std::snprintf(out, out_size, "%.0fM", static_cast(bytes) / kMiB); + ::snprintf(out, out_size, "%.0fM", static_cast(bytes) / kMiB); } ImU32 PressureColor(u64 used, u64 limit) { @@ -147,7 +148,7 @@ bool DebugUi::Initialize(Window& window, render::Renderer& renderer, asset::Vfs* // Roboto out of the engine's fonts:// archive. imgui's built-in font is the // fallback when the archive is absent (an unpacked source tree, say). - std::optional> ttf; + base::Optional> ttf; if (vfs) ttf = vfs->Read(render::kRxDefaultFontPath); if (!ttf || !render::LoadRxImGuiFont(ttf->data(), ttf->size())) { RX_WARN("debug ui: {} unavailable, using the built-in font", @@ -208,7 +209,7 @@ void DebugUi::Build(render::Renderer& renderer, FlyCamera& camera, const ecs::Wo frame_times_[frame_time_cursor_] = frame_delta * 1000.0f; frame_time_cursor_ = (frame_time_cursor_ + 1) % IM_ARRAYSIZE(frame_times_); - frame_time_count_ = std::min(frame_time_count_ + 1, IM_ARRAYSIZE(frame_times_)); + frame_time_count_ = rx::Min(frame_time_count_ + 1, IM_ARRAYSIZE(frame_times_)); // Per-pass GPU timestamps cost real frame time (barriers per pass), so the // renderer only records them while this overlay displays them (the GPU @@ -295,18 +296,18 @@ void DebugUi::Build(render::Renderer& renderer, FlyCamera& camera, const ecs::Wo if (ImGui::CollapsingHeader("Platform preset (.ini)")) { if (!preset_files_scanned_) ScanPresetFiles(); if (preset_files_.empty()) { - ImGui::TextDisabled("no .ini in %s", PresetDir().string().c_str()); + ImGui::TextDisabled("no .ini in %s", PresetDir().c_str()); } else { - std::vector names; + base::Vector names; names.reserve(preset_files_.size()); for (const auto& f : preset_files_) names.push_back(f.c_str()); ImGui::Combo("File", &preset_file_choice_, names.data(), static_cast(names.size())); if (ImGui::Button("Load")) { - const auto path = PresetDir() / preset_files_[preset_file_choice_]; + const base::String path = fs::Join(PresetDir(), preset_files_[preset_file_choice_]); if (render::LoadSettingsIni(path, settings)) { preset_choice_ = 0; // settings are file-tuned now, not a hardware tier preset_status_ = "loaded " + preset_files_[preset_file_choice_]; - RX_INFO("render preset: loaded {}", path.string()); + RX_INFO("render preset: loaded {}", path); } else { preset_status_ = "could not open " + preset_files_[preset_file_choice_]; } @@ -318,12 +319,12 @@ void DebugUi::Build(render::Renderer& renderer, FlyCamera& camera, const ecs::Wo ImGui::InputText("##presetname", preset_save_name_, sizeof(preset_save_name_)); ImGui::SameLine(); if (ImGui::Button("Save")) { - std::string fn = preset_save_name_[0] ? preset_save_name_ : "custom"; - if (fn.size() < 4 || fn.compare(fn.size() - 4, 4, ".ini") != 0) fn += ".ini"; - const auto path = PresetDir() / fn; + base::String fn = preset_save_name_[0] ? preset_save_name_ : "custom"; + if (!fn.ends_with(".ini")) fn += ".ini"; + const base::String path = fs::Join(PresetDir(), fn); if (render::SaveSettingsIni(path, settings)) { preset_status_ = "saved " + fn; - RX_INFO("render preset: saved {}", path.string()); + RX_INFO("render preset: saved {}", path); ScanPresetFiles(); } else { preset_status_ = "could not write " + fn; @@ -762,7 +763,7 @@ void DebugUi::DrawDiagnosticsTab(render::Renderer& renderer, FlyCamera& camera, ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.9f, 0.25f, 0.2f, 1.0f)); } char overlay[32]; - std::snprintf(overlay, sizeof overlay, "%.0f MB", c.budget_bytes * mb); + ::snprintf(overlay, sizeof overlay, "%.0f MB", c.budget_bytes * mb); ImGui::ProgressBar(fill, {-1, 0}, overlay); if (fill > 1.0f) ImGui::PopStyleColor(); } else { @@ -790,7 +791,7 @@ void DebugUi::DrawDiagnosticsTab(render::Renderer& renderer, FlyCamera& camera, if (const render::RenderGraph::Stats& g = renderer.graph_stats(); !g.passes.empty() && ImGui::CollapsingHeader("Frame graph")) { - ImGui::Text("%zu passes, %u barriers", g.passes.size(), g.barrier_count); + ImGui::Text("%zu passes, %u barriers", static_cast(g.passes.size()), g.barrier_count); if (ImGui::BeginTable("fg_passes", 4, ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingStretchProp, @@ -851,43 +852,43 @@ void DebugUi::DrawStatusBar(render::Renderer& renderer, const ecs::World& world, const bool compact = viewport->WorkSize.x < 1440.0f; const bool narrow = viewport->WorkSize.x < 760.0f; const f32 section_gap = compact ? 6.0f : 18.0f; - const f32 graph_width = std::clamp(viewport->WorkSize.x * 0.22f, 64.0f, 280.0f); + const f32 graph_width = rx::Clamp(viewport->WorkSize.x * 0.22f, 64.0f, 280.0f); const ImVec2 graph_min = {bar_max.x - kPadding - graph_width, bar_min.y + 4.0f}; const ImVec2 graph_max = {bar_max.x - kPadding, bar_max.y - 4.0f}; char fps_text[48]; const f32 fps = frame_delta > 0.0f ? 1.0f / frame_delta : 0.0f; if (narrow) - std::snprintf(fps_text, sizeof(fps_text), "%.0fF", fps); + ::snprintf(fps_text, sizeof(fps_text), "%.0fF", fps); else if (compact) - std::snprintf(fps_text, sizeof(fps_text), "%.0f FPS", fps); + ::snprintf(fps_text, sizeof(fps_text), "%.0f FPS", fps); else - std::snprintf(fps_text, sizeof(fps_text), "FPS %.0f %.2f ms", fps, + ::snprintf(fps_text, sizeof(fps_text), "FPS %.0f %.2f ms", fps, frame_delta * 1000.0f); char location_text[80]; if (narrow) - std::snprintf(location_text, sizeof(location_text), "%.0f %.0f %.0f", view.camera.eye.x, + ::snprintf(location_text, sizeof(location_text), "%.0f %.0f %.0f", view.camera.eye.x, view.camera.eye.y, view.camera.eye.z); else if (compact) - std::snprintf(location_text, sizeof(location_text), "XYZ %.1f %.1f %.1f", view.camera.eye.x, + ::snprintf(location_text, sizeof(location_text), "XYZ %.1f %.1f %.1f", view.camera.eye.x, view.camera.eye.y, view.camera.eye.z); else - std::snprintf(location_text, sizeof(location_text), "XYZ %.2f %.2f %.2f", + ::snprintf(location_text, sizeof(location_text), "XYZ %.2f %.2f %.2f", view.camera.eye.x, view.camera.eye.y, view.camera.eye.z); char build_text[96]; if (narrow) - std::snprintf(build_text, sizeof(build_text), "%.6s", RX_BUILD_ID); + ::snprintf(build_text, sizeof(build_text), "%.6s", RX_BUILD_ID); else if (compact) - std::snprintf(build_text, sizeof(build_text), "%s@%.8s", RX_VERSION, RX_BUILD_ID); + ::snprintf(build_text, sizeof(build_text), "%s@%.8s", RX_VERSION, RX_BUILD_ID); else - std::snprintf(build_text, sizeof(build_text), "BUILD %s (%s)", RX_VERSION, RX_BUILD_ID); + ::snprintf(build_text, sizeof(build_text), "BUILD %s (%s)", RX_VERSION, RX_BUILD_ID); char config_text[64]; if (narrow) - std::snprintf(config_text, sizeof(config_text), "%s", kBuildModeTiny); + ::snprintf(config_text, sizeof(config_text), "%s", kBuildModeTiny); else if (compact) - std::snprintf(config_text, sizeof(config_text), "%s", kBuildMode); + ::snprintf(config_text, sizeof(config_text), "%s", kBuildMode); else - std::snprintf(config_text, sizeof(config_text), "%s / %s", RX_BUILD_CONFIG, kBuildMode); + ::snprintf(config_text, sizeof(config_text), "%s / %s", RX_BUILD_CONFIG, kBuildMode); char cpu_memory_value[16]; char gpu_memory_value[16]; @@ -895,11 +896,11 @@ void DebugUi::DrawStatusBar(render::Renderer& renderer, const ecs::World& world, FormatMemoryValue(gpu_memory_value, sizeof(gpu_memory_value), gpu_memory_bytes_); char cpu_memory_text[24]; char gpu_memory_text[24]; - std::snprintf(cpu_memory_text, sizeof(cpu_memory_text), narrow ? "C%s" + ::snprintf(cpu_memory_text, sizeof(cpu_memory_text), narrow ? "C%s" : compact ? "C %s" : "CPU %s", cpu_memory_value); - std::snprintf(gpu_memory_text, sizeof(gpu_memory_text), narrow ? "G%s" + ::snprintf(gpu_memory_text, sizeof(gpu_memory_text), narrow ? "G%s" : compact ? "G %s" : "GPU %s", gpu_memory_value); @@ -913,22 +914,22 @@ void DebugUi::DrawStatusBar(render::Renderer& renderer, const ecs::World& world, char ecs_text[64]; if (narrow) { if (ecs_under_one_percent) - std::snprintf(ecs_text, sizeof(ecs_text), "E%u P<1", ecs_stats.entity_count); + ::snprintf(ecs_text, sizeof(ecs_text), "E%u P<1", ecs_stats.entity_count); else - std::snprintf(ecs_text, sizeof(ecs_text), "E%u P%.0f", ecs_stats.entity_count, ecs_pressure); + ::snprintf(ecs_text, sizeof(ecs_text), "E%u P%.0f", ecs_stats.entity_count, ecs_pressure); } else if (compact) { if (ecs_under_one_percent) - std::snprintf(ecs_text, sizeof(ecs_text), "ECS %u/%u A%u P<1", ecs_stats.entity_count, + ::snprintf(ecs_text, sizeof(ecs_text), "ECS %u/%u A%u P<1", ecs_stats.entity_count, ecs_stats.entity_slots, ecs_stats.archetype_count); else - std::snprintf(ecs_text, sizeof(ecs_text), "ECS %u/%u A%u P%.0f", ecs_stats.entity_count, + ::snprintf(ecs_text, sizeof(ecs_text), "ECS %u/%u A%u P%.0f", ecs_stats.entity_count, ecs_stats.entity_slots, ecs_stats.archetype_count, ecs_pressure); } else { if (ecs_under_one_percent) - std::snprintf(ecs_text, sizeof(ecs_text), "ECS %u/%u ent %u arch <1%% store", + ::snprintf(ecs_text, sizeof(ecs_text), "ECS %u/%u ent %u arch <1%% store", ecs_stats.entity_count, ecs_stats.entity_slots, ecs_stats.archetype_count); else - std::snprintf(ecs_text, sizeof(ecs_text), "ECS %u/%u ent %u arch %.0f%% store", + ::snprintf(ecs_text, sizeof(ecs_text), "ECS %u/%u ent %u arch %.0f%% store", ecs_stats.entity_count, ecs_stats.entity_slots, ecs_stats.archetype_count, ecs_pressure); } @@ -941,9 +942,9 @@ void DebugUi::DrawStatusBar(render::Renderer& renderer, const ecs::World& world, } char stream_text[80]; if (!streaming_active) { - std::snprintf(stream_text, sizeof(stream_text), narrow ? "S-OFF" : "STREAM OFF"); + ::snprintf(stream_text, sizeof(stream_text), narrow ? "S-OFF" : "STREAM OFF"); } else if (streaming.streamable_count == 0) { - std::snprintf(stream_text, sizeof(stream_text), narrow ? "S-IDLE" + ::snprintf(stream_text, sizeof(stream_text), narrow ? "S-IDLE" : compact ? "S IDLE" : "STREAM IDLE"); } else { @@ -952,13 +953,13 @@ void DebugUi::DrawStatusBar(render::Renderer& renderer, const ecs::World& world, FormatMemoryValue(resident, sizeof(resident), streaming.resident_bytes); FormatMemoryValue(budget, sizeof(budget), streaming.budget_bytes); if (narrow) - std::snprintf(stream_text, sizeof(stream_text), "S%s/%s D%u", resident, budget, + ::snprintf(stream_text, sizeof(stream_text), "S%s/%s D%u", resident, budget, streaming.demoted_count); else if (compact) - std::snprintf(stream_text, sizeof(stream_text), "STREAM %s/%s D%u", resident, budget, + ::snprintf(stream_text, sizeof(stream_text), "STREAM %s/%s D%u", resident, budget, streaming.demoted_count); else - std::snprintf(stream_text, sizeof(stream_text), "STREAM ON %s/%s %u/%u demoted", resident, + ::snprintf(stream_text, sizeof(stream_text), "STREAM ON %s/%s %u/%u demoted", resident, budget, streaming.demoted_count, streaming.streamable_count); } @@ -1004,7 +1005,7 @@ void DebugUi::DrawStatusBar(render::Renderer& renderer, const ecs::World& world, for (u32 i = 0; i < frame_time_count_; ++i) { const f32 frame_ms = frame_times_[(oldest + i) % capacity]; const f32 sample_fps = frame_ms > 0.0f ? 1000.0f / frame_ms : 0.0f; - const f32 normalized = std::clamp(sample_fps / kStatusGraphMaxFps, 0.0f, 1.0f); + const f32 normalized = rx::Clamp(sample_fps / kStatusGraphMaxFps, 0.0f, 1.0f); points[i] = {graph_min.x + sample_step * static_cast(i), graph_max.y - graph_height * normalized}; } @@ -1024,11 +1025,17 @@ void DebugUi::DrawStageChart(render::Renderer& renderer, f32 bottom_offset) { struct Bar { const char* name; f32 ms; + u32 pass; }; - std::vector bars; + base::Vector bars; bars.reserve(timings.size()); - for (const auto& t : timings) bars.push_back({t.name.c_str(), t.ms}); - std::sort(bars.begin(), bars.end(), [](const Bar& a, const Bar& b) { return a.ms > b.ms; }); + for (const auto& t : timings) + bars.push_back({t.name.c_str(), t.ms, static_cast(bars.size())}); + // Tied stages fall back to pass order. The ms values are live GPU + // measurements, so no tie order was ever reproducible to preserve. + base::Sort(bars.data(), bars.data() + bars.size(), [](const Bar& a, const Bar& b) { + return a.ms > b.ms || (a.ms == b.ms && a.pass < b.pass); + }); constexpr int kMaxBars = 6; f32 other_ms = 0.0f; @@ -1036,7 +1043,7 @@ void DebugUi::DrawStageChart(render::Renderer& renderer, f32 bottom_offset) { for (size_t i = kMaxBars; i < bars.size(); ++i) other_ms += bars[i].ms; bars.resize(kMaxBars); } - if (other_ms > 0.0f) bars.push_back({"other", other_ms}); + if (other_ms > 0.0f) bars.push_back({"other", other_ms, 0}); if (bars.empty()) return; const f32 max_ms = bars.front().ms > 0.0f ? bars.front().ms : 1.0f; @@ -1071,7 +1078,7 @@ void DebugUi::DrawStageChart(render::Renderer& renderer, f32 bottom_offset) { const f32 head_y = p0.y + pad; dl->AddText(font, font_size, {content_x, head_y}, IM_COL32(206, 214, 232, 255), "GPU STAGES"); char buf[32]; - std::snprintf(buf, sizeof(buf), "%.2f ms", renderer.gpu_frame_ms()); + ::snprintf(buf, sizeof(buf), "%.2f ms", renderer.gpu_frame_ms()); const ImVec2 total_sz = ImGui::CalcTextSize(buf); dl->AddText(font, font_size, {p1.x - pad - total_sz.x, head_y}, IM_COL32(170, 182, 205, 255), buf); @@ -1094,7 +1101,7 @@ void DebugUi::DrawStageChart(render::Renderer& renderer, f32 bottom_offset) { } const f32 ty = y + (row_h - text_h) * 0.5f; - std::snprintf(buf, sizeof(buf), "%.2f", bar.ms); + ::snprintf(buf, sizeof(buf), "%.2f", bar.ms); const ImVec2 vsz = ImGui::CalcTextSize(buf); const f32 val_x = content_x + content_w - 6.0f - vsz.x; // Clip the label so a long pass name never runs into the ms value. @@ -1110,12 +1117,14 @@ void DebugUi::DrawStageChart(render::Renderer& renderer, f32 bottom_offset) { void DebugUi::ScanPresetFiles() { preset_files_.clear(); preset_files_scanned_ = true; - std::error_code ec; - for (const auto& entry : std::filesystem::directory_iterator(PresetDir(), ec)) { - if (entry.is_regular_file(ec) && entry.path().extension() == ".ini") - preset_files_.push_back(entry.path().filename().string()); + base::Vector entries; + fs::ListDirectory(PresetDir(), &entries); + for (const fs::DirEntry& entry : entries) { + if (entry.is_regular && fs::Extension(entry.path) == ".ini") + preset_files_.push_back(base::String(fs::Filename(entry.path))); } - std::sort(preset_files_.begin(), preset_files_.end()); + // File names in one directory are unique, so any correct sort agrees. + base::Sort(preset_files_.data(), preset_files_.data() + preset_files_.size()); if (preset_file_choice_ >= static_cast(preset_files_.size())) preset_file_choice_ = 0; } diff --git a/runtime/debug_ui.h b/runtime/debug_ui.h index d20e1db8..f50740ac 100644 --- a/runtime/debug_ui.h +++ b/runtime/debug_ui.h @@ -3,10 +3,6 @@ // The overlay records through the engine's RHI imgui render backend // (render/util/imgui_renderer.h) - no raw Vulkan, no volk here. -#include -#include -#include -#include #include "core/types.h" #include "core/window.h" @@ -14,6 +10,9 @@ #include "render/core/renderer.h" #if defined(RX_HAS_IMGUI) +#include "base/containers/vector.h" +#include "base/memory/move.h" +#include "base/strings/xstring.h" #include "render/util/imgui_renderer.h" #endif @@ -54,7 +53,7 @@ class DebugUi { // One line of baked-world streaming state for the status bar, refreshed each // frame by the viewer. Empty leaves the section out entirely, which is what // every run without --world sees. - void set_world_status(std::string status) { world_status_ = std::move(status); } + void set_world_status(base::String status) { world_status_ = base::move(status); } // The day/night clock, so the Lighting panel can scrub the time of day and the // timescale. Null leaves those controls out. @@ -100,16 +99,16 @@ class DebugUi { bool gpu_timings_forced_ = false; bool show_demo_ = false; WorldClock* clock_ = nullptr; // day/night cycle, for the Lighting time controls - std::string world_status_; // --world: one status-bar line, empty when off + base::String world_status_; // --world: one status-bar line, empty when off int preset_choice_ = 0; // 0 = custom/hand-tuned, else a QualityPreset combo row // Editable .ini render presets (engine/render/presets): the discovered file // list (lazy-scanned, rescannable), the combo selection, the save-as name // buffer and the last load/save status line. - std::vector preset_files_; + base::Vector preset_files_; bool preset_files_scanned_ = false; int preset_file_choice_ = 0; char preset_save_name_[64] = "custom"; - std::string preset_status_; + base::String preset_status_; #if defined(RX_HAS_IMGUI) render::ImGuiRenderer imgui_renderer_; // RHI imgui render backend #endif diff --git a/runtime/demo_drive.cc b/runtime/demo_drive.cc index 8a751a17..b55fc4d0 100644 --- a/runtime/demo_drive.cc +++ b/runtime/demo_drive.cc @@ -1,9 +1,8 @@ #include "demo_drive.h" -#include -#include -#include -#include +#include +#include +#include #include @@ -12,7 +11,12 @@ #include "asset/material.h" #include "asset/mesh.h" #include "asset/primitives.h" +#include "base/containers/vector.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" #include "core/log.h" +#include "core/scalar.h" #include "ecs/world.h" #include "physics/boat_profiles.h" #include "physics/vehicle_profiles.h" @@ -77,7 +81,7 @@ u32 ProfileFromName(const char* s, u32 fallback) { if (!s) return fallback; const char* keys[kProfileCount] = {"sports", "muscle", "hatch", "suv", "van", "semi"}; for (u32 i = 0; i < kProfileCount; ++i) - if (std::strstr(s, keys[i])) return i; + if (::strstr(s, keys[i])) return i; return fallback; } @@ -111,7 +115,7 @@ u32 BoatProfileFromName(const char* s, u32 fallback) { if (!s) return fallback; const char* keys[kBoatProfileCount] = {"dinghy", "speed", "jetski", "fishing", "barge"}; for (u32 i = 0; i < kBoatProfileCount; ++i) - if (std::strstr(s, keys[i])) return i; + if (::strstr(s, keys[i])) return i; return fallback; } // Cargo cycle L: 0% -> 50% -> 100% -> 125% (structural overload). @@ -135,7 +139,7 @@ const physics::SurfaceType kPalette[kRegionCount] = { physics::SurfaceType::kDirt, physics::SurfaceType::kSand}; f32 SmoothStep(f32 t) { - t = std::clamp(t, 0.0f, 1.0f); + t = rx::Clamp(t, 0.0f, 1.0f); return t * t * (3.0f - 2.0f * t); } @@ -143,38 +147,38 @@ f32 SmoothStep(f32 t) { f32 Feather(f32 v, f32 lo, f32 hi, f32 feather) { f32 a = SmoothStep((v - (lo - feather)) / feather); f32 b = SmoothStep(((hi + feather) - v) / feather); - return std::min(a, b); + return rx::Min(a, b); } u8 RegionAt(f32 x, f32 z) { // Runway: a long asphalt strip on the -X side. if (x >= -74.0f && x <= -50.0f && z >= -152.0f && z <= 152.0f) return kAsphalt; // Road loop centred on the origin (a square ring band). - const f32 m = std::max(std::fabs(x), std::fabs(z)); + const f32 m = rx::Max(::fabs(x), ::fabs(z)); if (m >= 40.0f && m <= 57.0f) { - if (x >= 40.0f && x <= 57.0f && std::fabs(z) <= 9.0f) return kIce; // ice on the east straight + if (x >= 40.0f && x <= 57.0f && ::fabs(z) <= 9.0f) return kIce; // ice on the east straight return kAsphalt; } // Dirt field just inside the loop's north-west. if (x >= -46.0f && x <= -12.0f && z >= 40.0f && z <= 84.0f) return kDirt; // Sand beach ringing the lake. - const f32 ld = std::sqrt((x - kLakeCx) * (x - kLakeCx) + (z - kLakeCz) * (z - kLakeCz)); + const f32 ld = ::sqrt((x - kLakeCx) * (x - kLakeCx) + (z - kLakeCz) * (z - kLakeCz)); if (ld >= 66.0f && ld <= 92.0f) return kSand; return kGrass; } f32 HeightAt(f32 x, f32 z) { - f32 h = 1.8f * std::sin(x * 0.017f) * std::cos(z * 0.019f) + - 1.1f * std::sin((x + z) * 0.011f + 0.6f); + f32 h = 1.8f * ::sin(x * 0.017f) * ::cos(z * 0.019f) + + 1.1f * ::sin((x + z) * 0.011f + 0.6f); // Lake basin: dip below the water surface toward the centre. - const f32 ld = std::sqrt((x - kLakeCx) * (x - kLakeCx) + (z - kLakeCz) * (z - kLakeCz)); + const f32 ld = ::sqrt((x - kLakeCx) * (x - kLakeCx) + (z - kLakeCz) * (z - kLakeCz)); if (ld < 104.0f) { const f32 t = SmoothStep((104.0f - ld) / (104.0f - 40.0f)); h = h + (-6.0f - h) * t; } // Flatten the road loop and the runway so the wheeled vehicles roll on level // ground and the plane has a real runway. - const f32 m = std::max(std::fabs(x), std::fabs(z)); + const f32 m = rx::Max(::fabs(x), ::fabs(z)); const f32 road_w = Feather(m, 40.0f, 57.0f, 3.0f); h = h + (0.0f - h) * road_w; const f32 rw = Feather(x, -74.0f, -50.0f, 3.0f) * Feather(z, -152.0f, 152.0f, 4.0f); @@ -269,7 +273,7 @@ asset::Mesh FinishMesh(MeshBuild& mb, const char* id, asset::AssetId material) { lod.submeshes.push_back({0, static_cast(mb.i.size()), material}); f32 r = 0; for (const asset::Vertex& vert : mb.v) - r = std::max(r, std::sqrt(vert.position[0] * vert.position[0] + + r = rx::Max(r, ::sqrt(vert.position[0] * vert.position[0] + vert.position[1] * vert.position[1] + vert.position[2] * vert.position[2])); mesh.bounds_radius = r; @@ -353,7 +357,7 @@ void DriveDemo::Create() { const Mat4 place = MakeTranslation({p.x, gy, 48.0f}) * MakeFromQuat(QuatFromAxisAngle({0, 1, 0}, kPi)); const Mat4 norm = NormalizeXform(m, p.len, 0.0f, /*sit_on_ground=*/true); - showcase_.push_back({std::move(m), place * norm}); + showcase_.push_back({base::move(m), place * norm}); } car_profile_ = ProfileFromName(StartProfile.get(), 0); // RX_DRIVE_PROFILE @@ -361,11 +365,11 @@ void DriveDemo::Create() { SetupAudio(); if (const char* v = StartVehicle.get()) { - if (!std::strcmp(v, "boat")) + if (!::strcmp(v, "boat")) active_ = Vehicle::kBoat; - else if (!std::strcmp(v, "plane")) + else if (!::strcmp(v, "plane")) active_ = Vehicle::kPlane; - else if (!std::strcmp(v, "kite")) + else if (!::strcmp(v, "kite")) active_ = Vehicle::kKite; } else if (StartBoat.get() || StartCargo.get() > 0.0f) { active_ = Vehicle::kBoat; // a boat-capture env implies the boat is the subject @@ -403,7 +407,7 @@ void DriveDemo::BuildTerrain() { } // Per-quad material index (row-major, matching Jolt's height-field layout). - std::vector mat_idx((kSamples - 1) * (kSamples - 1)); + base::Vector mat_idx((kSamples - 1) * (kSamples - 1)); for (u32 z = 0; z + 1 < kSamples; ++z) { for (u32 x = 0; x + 1 < kSamples; ++x) { const f32 wx = kTerrainMin + (x + 0.5f) * spacing; @@ -452,7 +456,7 @@ void DriveDemo::BuildTerrain() { v.color = 0xffffffff; } } - std::vector buckets[kRegionCount]; + base::Vector buckets[kRegionCount]; for (u32 z = 0; z + 1 < kSamples; ++z) { for (u32 x = 0; x + 1 < kSamples; ++x) { const u8 r = mat_idx[z * (kSamples - 1) + x]; @@ -460,7 +464,7 @@ void DriveDemo::BuildTerrain() { const u32 b = a + 1; const u32 c = a + kSamples; const u32 d = c + 1; - std::vector& bk = buckets[r]; + base::Vector& bk = buckets[r]; for (u32 idx : {a, b, c, b, d, c}) bk.push_back(idx); } } @@ -536,7 +540,7 @@ void DriveDemo::BuildWater() { // the car's tires never read the dry world as submerged. ctx_.physics->set_water_height([](const Vec3& p, f32* height, Vec3* flow) { const f32 d = - std::sqrt((p.x - kLakeCx) * (p.x - kLakeCx) + (p.z - kLakeCz) * (p.z - kLakeCz)); + ::sqrt((p.x - kLakeCx) * (p.x - kLakeCx) + (p.z - kLakeCz) * (p.z - kLakeCz)); if (d > kLakeWaterRadius) return false; *height = kLakeY; if (flow) *flow = {}; @@ -557,13 +561,13 @@ void DriveDemo::BuildWheelMesh() { const f32 a = t * 2.0f * kPi; for (int k = 0; k < 2; ++k) { asset::Vertex v{}; - v.position[0] = std::cos(a); + v.position[0] = ::cos(a); v.position[1] = k ? 0.5f : -0.5f; - v.position[2] = std::sin(a); - v.normal[0] = std::cos(a); - v.normal[2] = std::sin(a); - v.tangent[0] = -std::sin(a); - v.tangent[2] = std::cos(a); + v.position[2] = ::sin(a); + v.normal[0] = ::cos(a); + v.normal[2] = ::sin(a); + v.tangent[0] = -::sin(a); + v.tangent[2] = ::cos(a); v.tangent[3] = 1; v.uv[0] = t * 4.0f; v.uv[1] = k ? 1.0f : 0.0f; @@ -589,16 +593,16 @@ void DriveDemo::BuildBoatHull() { MeshBuild hb; auto width_scale = [](f32 z) -> f32 { const f32 tn = z / kHL; - if (tn > 0) return std::max(0.06f, 1.0f - tn * tn * 0.97f); // pointed bow - return std::max(0.55f, 1.0f - tn * tn * 0.4f); // fuller transom + if (tn > 0) return rx::Max(0.06f, 1.0f - tn * tn * 0.97f); // pointed bow + return rx::Max(0.55f, 1.0f - tn * tn * 0.4f); // fuller transom }; for (u32 si = 0; si <= kSt; ++si) { const f32 z = -kHL + 2.0f * kHL * static_cast(si) / kSt; const f32 ws = width_scale(z); for (u32 pj = 0; pj <= kPr; ++pj) { const f32 th = kPi * static_cast(pj) / kPr; - const f32 x = -kHB * ws * std::cos(th); - const f32 y = kDeck - (kDeck - kKeel) * std::pow(std::sin(th), 0.62f); + const f32 x = -kHB * ws * ::cos(th); + const f32 y = kDeck - (kDeck - kKeel) * ::pow(::sin(th), 0.62f); hb.Add({x, y, z}, static_cast(pj) / kPr, z / 4.0f); } } @@ -667,7 +671,7 @@ void DriveDemo::BuildKite() { kite_anchor_.y = gy + 1.6f; // top of the post kite_spawn_.y = gy + 0.6f; // on the sand, downwind of the post physics::KiteDesc kd; - kite_ = std::make_unique(*ctx_.physics, kd, kite_anchor_, kite_spawn_, 0.0f); + kite_ = base::MakeUnique(*ctx_.physics, kd, kite_anchor_, kite_spawn_, 0.0f); // A steady breeze so the kite auto-launches (and a gentle push on the boat's // topsides / the plane's airmass; the J key still cycles rain wetness, which is @@ -675,7 +679,7 @@ void DriveDemo::BuildKite() { ctx_.physics->set_wind({0.0f, 0.0f, 7.0f}); } -bool DriveDemo::LoadModel(const std::string& path, i32 skip_mesh_index, Model* out) { +bool DriveDemo::LoadModel(const base::String& path, i32 skip_mesh_index, Model* out) { asset::ImportedScene scene; if (!asset::LoadGltfScene(path, &scene)) { RX_WARN("drive: vendor model '{}' unavailable, using graybox fallback", path.c_str()); @@ -702,10 +706,10 @@ bool DriveDemo::LoadModel(const std::string& path, i32 skip_mesh_index, Model* o out->aabb_min = out->aabb_max = p; first = false; } else { - out->aabb_min = {std::min(out->aabb_min.x, p.x), std::min(out->aabb_min.y, p.y), - std::min(out->aabb_min.z, p.z)}; - out->aabb_max = {std::max(out->aabb_max.x, p.x), std::max(out->aabb_max.y, p.y), - std::max(out->aabb_max.z, p.z)}; + out->aabb_min = {rx::Min(out->aabb_min.x, p.x), rx::Min(out->aabb_min.y, p.y), + rx::Min(out->aabb_min.z, p.z)}; + out->aabb_max = {rx::Max(out->aabb_max.x, p.x), rx::Max(out->aabb_max.y, p.y), + rx::Max(out->aabb_max.z, p.z)}; } } } @@ -718,7 +722,7 @@ Mat4 DriveDemo::NormalizeXform(const Model& m, f32 target_len, f32 model_yaw, bool sit_on_ground) const { const Vec3 c{(m.aabb_min.x + m.aabb_max.x) * 0.5f, (m.aabb_min.y + m.aabb_max.y) * 0.5f, (m.aabb_min.z + m.aabb_max.z) * 0.5f}; - const f32 span = std::max(m.aabb_max.x - m.aabb_min.x, m.aabb_max.z - m.aabb_min.z); + const f32 span = rx::Max(m.aabb_max.x - m.aabb_min.x, m.aabb_max.z - m.aabb_min.z); const f32 s = span > 1e-3f ? target_len / span : 1.0f; const f32 py = sit_on_ground ? m.aabb_min.y : c.y; const Mat4 t = MakeTranslation({-c.x, -py, -c.z}); @@ -756,9 +760,9 @@ void DriveDemo::SpawnVehicles() { // optionally overridden + pre-loaded with cargo for headless captures. boat_profile_ = BoatProfileFromName(StartBoat.get(), boat_profile_); // RX_DRIVE_BOAT boat_desc_ = BoatProfileDesc(boat_profile_); - boat_ = std::make_unique(phys, boat_desc_, boat_spawn_, boat_yaw_); + boat_ = base::MakeUnique(phys, boat_desc_, boat_spawn_, boat_yaw_); { - const f32 frac = std::clamp(StartCargo.get(), 0.0f, boat_desc_.cargo_overload_fraction); + const f32 frac = rx::Clamp(StartCargo.get(), 0.0f, boat_desc_.cargo_overload_fraction); // Snap the cargo cycle step to the nearest preset step for key-L cycling. boat_cargo_step_ = frac >= 1.13f ? 3 : (frac >= 0.75f ? 2 : (frac >= 0.25f ? 1 : 0)); boat_cargo_frac_ = frac; @@ -775,15 +779,15 @@ void DriveDemo::SpawnVehicles() { ad.prop_max_power_w = 175000.0f; ad.prop_static_thrust_cap_n = 3400.0f; plane_spawn_.y = HeightAt(plane_spawn_.x, plane_spawn_.z) + 1.4f; - aircraft_ = std::make_unique(phys, ad, plane_spawn_, plane_yaw_); + aircraft_ = base::MakeUnique(phys, ad, plane_spawn_, plane_yaw_); } void DriveDemo::SetupAudio() { if (!ctx_.audio) return; audio::Mixer& mix = ctx_.audio->mixer(); - car_audio_ = std::make_unique(mix, audio::V8Preset()); - boat_audio_ = std::make_unique(mix, BoatEnginePreset(boat_profile_)); - plane_audio_ = std::make_unique(mix, audio::SinglePropPlanePreset()); + car_audio_ = base::MakeUnique(mix, audio::V8Preset()); + boat_audio_ = base::MakeUnique(mix, BoatEnginePreset(boat_profile_)); + plane_audio_ = base::MakeUnique(mix, audio::SinglePropPlanePreset()); } f32 DriveDemo::CarMaxSlip() const { @@ -792,7 +796,7 @@ f32 DriveDemo::CarMaxSlip() const { if (!ctx_.physics->GetVehicleState(car_, &vs)) return 0.0f; f32 slip = 0.0f; for (u32 w = 0; w < vs.wheel_count && w < 4; ++w) - slip = std::max(slip, vs.wheels[w].longitudinal_slip); + slip = rx::Max(slip, vs.wheels[w].longitudinal_slip); return slip; } @@ -872,7 +876,7 @@ void DriveDemo::ResetActive() { // The Kite has no in-place reset; respawn it near-taut and low to re-launch. if (kite_ && kite_->valid()) ctx_.physics->RemoveBody(kite_->body()); physics::KiteDesc kd; - kite_ = std::make_unique(*ctx_.physics, kd, kite_anchor_, kite_spawn_, 0.0f); + kite_ = base::MakeUnique(*ctx_.physics, kd, kite_anchor_, kite_spawn_, 0.0f); } cam_init_ = false; } @@ -895,7 +899,7 @@ void DriveDemo::SetCarProfile(u32 index) { f32 rot[4]; if (ctx_.physics->GetVehicleTransform(car_, &pos, rot)) { const Vec3 fwd = Rotate(Quat{rot[0], rot[1], rot[2], rot[3]}, Vec3{0, 0, 1}); - yaw = std::atan2(fwd.x, fwd.z); + yaw = ::atan2(fwd.x, fwd.z); } ctx_.physics->RemoveVehicle(car_); } @@ -922,16 +926,16 @@ void DriveDemo::SetBoatProfile(u32 index) { const physics::BoatState& s = boat_->state(); const Vec3 fwd = Rotate(s.rotation, Vec3{0, 0, 1}); pos = {s.position.x, boat_spawn_.y, s.position.z}; - yaw = std::atan2(fwd.x, fwd.z); + yaw = ::atan2(fwd.x, fwd.z); ctx_.physics->RemoveBody(boat_->body()); // Boat has no dtor cleanup } - boat_ = std::make_unique(*ctx_.physics, boat_desc_, pos, yaw); + boat_ = base::MakeUnique(*ctx_.physics, boat_desc_, pos, yaw); boat_->SetCargo(boat_cargo_frac_ * boat_desc_.max_cargo_kg); // Swap the audio voice to this profile's engine (dinghy outboard / inboard / // jetski twin). if (ctx_.audio) - boat_audio_ = std::make_unique(ctx_.audio->mixer(), BoatEnginePreset(index)); + boat_audio_ = base::MakeUnique(ctx_.audio->mixer(), BoatEnginePreset(index)); cam_init_ = false; RX_INFO("drive: boat profile -> {} ({} kg, cargo cap {} kg)", BoatProfileName(index), @@ -939,7 +943,7 @@ void DriveDemo::SetBoatProfile(u32 index) { } void DriveDemo::SetBoatCargo(f32 frac) { - boat_cargo_frac_ = std::clamp(frac, 0.0f, boat_desc_.cargo_overload_fraction); + boat_cargo_frac_ = rx::Clamp(frac, 0.0f, boat_desc_.cargo_overload_fraction); if (boat_ && boat_->valid()) boat_->SetCargo(boat_cargo_frac_ * boat_desc_.max_cargo_kg); RX_INFO("drive: boat cargo -> {} kg ({:.0f}% of {} kg)", static_cast(boat_cargo_frac_ * boat_desc_.max_cargo_kg), boat_cargo_frac_ * 100.0f, @@ -1023,7 +1027,7 @@ void DriveDemo::Update(f32 dt, const InputState& input, const ActionState& actio } else if (active_ == Vehicle::kPlane) { if (held(Key::kW)) plane_throttle_ += dt * 0.5f; if (held(Key::kS)) plane_throttle_ -= dt * 0.5f; - plane_throttle_ = std::clamp(plane_throttle_, 0.0f, 1.0f); + plane_throttle_ = rx::Clamp(plane_throttle_, 0.0f, 1.0f); plane_pitch_ = (held(Key::kArrowUp) ? 1.0f : 0.0f) - (held(Key::kArrowDown) ? 1.0f : 0.0f); plane_roll_ = (held(Key::kArrowRight) ? 1.0f : 0.0f) - (held(Key::kArrowLeft) ? 1.0f : 0.0f); plane_rudder_ = (held(Key::kD) ? 1.0f : 0.0f) - (held(Key::kA) ? 1.0f : 0.0f); @@ -1066,14 +1070,14 @@ void DriveDemo::Update(f32 dt, const InputState& input, const ActionState& actio if (ctx_.physics->GetVehicleState(car_, &vs)) { st.rpm = vs.rpm; st.load = vs.engine_load; - st.speed_mps = std::fabs(vs.forward_speed); + st.speed_mps = ::fabs(vs.forward_speed); st.gear = vs.gear; st.is_shifting = vs.is_shifting; st.wheel_count = vs.wheel_count; for (u32 i = 0; i < vs.wheel_count && i < 4; ++i) st.wheel_slip[i] = vs.wheels[i].longitudinal_slip; } - st.throttle = std::fabs(car_throttle_); + st.throttle = ::fabs(car_throttle_); st.slip = CarMaxSlip(); st.position = p; car_audio_->Update(st); @@ -1136,7 +1140,7 @@ void DriveDemo::UpdateChaseCamera(f32 dt, const InputState& input, const ActionS // assumes a forward-facing body, which a kite has not). const Vec3 kp = kite_->state().position; const Vec3 mid = (kite_anchor_ + kp) * 0.5f; - const f32 span = std::max(Length(kp - kite_anchor_), 6.0f); + const f32 span = rx::Max(Length(kp - kite_anchor_), 6.0f); const Vec3 desired_eye = mid + Vec3{span * 0.6f + 8.0f, span * 0.15f + 3.0f, -(span + 12.0f)}; const Vec3 desired_target = mid; if (!cam_init_) { @@ -1144,7 +1148,7 @@ void DriveDemo::UpdateChaseCamera(f32 dt, const InputState& input, const ActionS cam_target_ = desired_target; cam_init_ = true; } else { - const f32 a = 1.0f - std::exp(-dt / 0.2f); + const f32 a = 1.0f - ::exp(-dt / 0.2f); cam_eye_ = Lerp(cam_eye_, desired_eye, a); cam_target_ = Lerp(cam_target_, desired_target, a); } @@ -1159,7 +1163,7 @@ void DriveDemo::UpdateChaseCamera(f32 dt, const InputState& input, const ActionS cam_target_ = desired_target; cam_init_ = true; } else { - const f32 a = 1.0f - std::exp(-dt / 0.14f); + const f32 a = 1.0f - ::exp(-dt / 0.14f); cam_eye_ = Lerp(cam_eye_, desired_eye, a); cam_target_ = Lerp(cam_target_, desired_target, a); } @@ -1225,7 +1229,7 @@ void DriveDemo::Emit(f32 dt, render::FrameView& view) { const f32 box_center_y = boat_->state().position.y; const f32 art_sink = 0.56f * he.y; // 0.28 m for the default hull const f32 max_sink = box_center_y + he.y + 0.05f; // keep drawn deck ~ at/above awash - const f32 sink = std::clamp(art_sink, 0.0f, std::max(0.0f, max_sink)); + const f32 sink = rx::Clamp(art_sink, 0.0f, rx::Max(0.0f, max_sink)); const Mat4 t = MakeTransform(boat_->state().position, boat_->state().rotation, 1.0f) * MakeTranslation({0.0f, -sink, 0.0f}) * ScaleMat(hull_scale); render::DrawItem d{}; @@ -1310,7 +1314,7 @@ void DriveDemo::DrawPanel() { static_cast(car_desc_.mass)); physics::PhysicsWorld::VehicleState vs; if (ctx_.physics->GetVehicleState(car_, &vs)) { - const f32 kmh = std::fabs(vs.forward_speed) * 3.6f; + const f32 kmh = ::fabs(vs.forward_speed) * 3.6f; const char* gear = vs.gear < 0 ? "R" : (vs.gear == 0 ? "N" : nullptr); if (gear) ImGui::Text("speed %.0f km/h gear %s rpm %.0f", kmh, gear, vs.rpm); @@ -1356,7 +1360,7 @@ void DriveDemo::DrawPanel() { ImGui::Text("altitude %.1f m line %.1f m", ks.altitude_m, ks.line_length_m); ImGui::Text("tension %.0f N %s", ks.tension_n, ks.taut ? "taut" : "slack"); ImGui::Text("airspeed %.1f m/s alpha %.0f deg", ks.airspeed_mps, ks.alpha_deg); - ImGui::Text("wind %.1f m/s", std::sqrt(w.x * w.x + w.y * w.y + w.z * w.z)); + ImGui::Text("wind %.1f m/s", ::sqrt(w.x * w.x + w.y * w.y + w.z * w.z)); ImGui::TextDisabled("A/D steer W/S reel out/in"); } } diff --git a/runtime/demo_drive.h b/runtime/demo_drive.h index 42b3658c..35158986 100644 --- a/runtime/demo_drive.h +++ b/runtime/demo_drive.h @@ -1,12 +1,11 @@ #ifndef RX_RUNTIME_DEMO_DRIVE_H_ #define RX_RUNTIME_DEMO_DRIVE_H_ -#include -#include -#include -#include - #include "audio/vehicle_audio.h" +#include "base/containers/pair.h" +#include "base/containers/vector.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" #include "core/input.h" #include "core/input_actions.h" #include "core/math.h" @@ -62,7 +61,7 @@ class DriveDemo { // the .glb was absent, so callers fall back to graybox geometry. struct Model { bool loaded = false; - std::vector> parts; + base::Vector> parts; Vec3 aabb_min{0, 0, 0}; Vec3 aabb_max{0, 0, 0}; }; @@ -79,7 +78,7 @@ class DriveDemo { // sourced from that glTF mesh (the truck's rigid wheel mesh, replaced by the // suspension-driven wheel cylinders). Returns false (and leaves out->loaded // false) when the file is missing. - bool LoadModel(const std::string& path, i32 skip_mesh_index, Model* out); + bool LoadModel(const base::String& path, i32 skip_mesh_index, Model* out); // Recenter + scale-to-fit + yaw a model into a unit placement frame, so the // final draw is `place * NormalizeXform(...) * part.local`. sit_on_ground puts // the model's min-Y at the frame origin (parked pieces); else its centre. @@ -112,9 +111,9 @@ class DriveDemo { // vehicles physics::VehicleId car_ = 0; - std::unique_ptr boat_; - std::unique_ptr aircraft_; - std::unique_ptr kite_; + base::UniquePointer boat_; + base::UniquePointer aircraft_; + base::UniquePointer kite_; Vehicle active_ = Vehicle::kCar; // Active handling profile (0..5: sports/muscle/hatch/suv/van/semi) and the @@ -171,14 +170,14 @@ class DriveDemo { f32 wetness_ = 0; // procedural audio (one voice set per vehicle) - std::unique_ptr car_audio_; - std::unique_ptr boat_audio_; - std::unique_ptr plane_audio_; + base::UniquePointer car_audio_; + base::UniquePointer boat_audio_; + base::UniquePointer plane_audio_; // visuals Model car_model_; Model plane_model_; - std::vector> showcase_; // parked pieces + baked placement + base::Vector> showcase_; // parked pieces + baked placement Mat4 car_norm_ = Mat4::Identity(); Mat4 plane_norm_ = Mat4::Identity(); u64 wheel_mesh_ = 0; // engine-drawn suspension wheel cylinder diff --git a/runtime/demo_grass.cc b/runtime/demo_grass.cc index 27887d83..946e0077 100644 --- a/runtime/demo_grass.cc +++ b/runtime/demo_grass.cc @@ -1,12 +1,12 @@ #include "demo_grass.h" -#include -#include +#include #include #include "asset/primitives.h" #include "core/log.h" +#include "core/scalar.h" #include "scene/components.h" namespace rx { @@ -25,23 +25,23 @@ base::Option GrassFarRadius{"demo.grass.far_radius", 960.0f, "RX_GRASS_FA u32 PackColor(f32 r, f32 g, f32 b) { auto to8 = [](f32 value) { - return static_cast(std::clamp(value, 0.0f, 1.0f) * 255.0f); + return static_cast(rx::Clamp(value, 0.0f, 1.0f) * 255.0f); }; return 0xff000000u | (to8(b) << 16u) | (to8(g) << 8u) | to8(r); } f32 SmoothStep(f32 a, f32 b, f32 value) { - f32 t = std::clamp((value - a) / (b - a), 0.0f, 1.0f); + f32 t = rx::Clamp((value - a) / (b - a), 0.0f, 1.0f); return t * t * (3.0f - 2.0f * t); } } // namespace f32 GrassDemo::TerrainHeight(f32 x, f32 z) const { - f32 broad = std::sin(x * 0.025f) * 4.8f + std::cos(z * 0.031f) * 3.6f; - f32 crossing = std::sin((x + z) * 0.019f + 0.8f) * 3.1f; - f32 detail = std::sin(x * 0.071f - z * 0.049f) * 0.75f + - std::cos(x * 0.043f + z * 0.063f) * 0.55f; + f32 broad = ::sin(x * 0.025f) * 4.8f + ::cos(z * 0.031f) * 3.6f; + f32 crossing = ::sin((x + z) * 0.019f + 0.8f) * 3.1f; + f32 detail = ::sin(x * 0.071f - z * 0.049f) * 0.75f + + ::cos(x * 0.043f + z * 0.063f) * 0.55f; return broad + crossing + detail; } @@ -120,13 +120,13 @@ void GrassDemo::BuildField() { const f32 world_z = kOrigin + static_cast(z) * kStep; render::GrassFieldSample& sample = samples_[z * kResolution + x]; sample.height = TerrainHeight(world_x, world_z); - const f32 patch = 0.5f + 0.25f * std::sin(world_x * 0.057f + world_z * 0.031f) + - 0.20f * std::cos(world_x * 0.021f - world_z * 0.069f); - const f32 path_center = std::sin(world_x * 0.035f) * 7.0f - 4.0f; - const f32 path_distance = std::fabs(world_z - path_center); + const f32 patch = 0.5f + 0.25f * ::sin(world_x * 0.057f + world_z * 0.031f) + + 0.20f * ::cos(world_x * 0.021f - world_z * 0.069f); + const f32 path_center = ::sin(world_x * 0.035f) * 7.0f - 4.0f; + const f32 path_distance = ::fabs(world_z - path_center); // Wide enough to survive the coarser kilometre-scale field texels. const f32 path_mask = SmoothStep(3.0f, 14.0f, path_distance); - sample.density = std::clamp((0.90f + patch * 0.14f) * path_mask, 0.0f, 1.0f); + sample.density = rx::Clamp((0.90f + patch * 0.14f) * path_mask, 0.0f, 1.0f); sample.growth = 0.90f + patch * 0.34f; const bool golden_ridge = sample.height > 4.2f && patch > 0.5f; const bool cool_hollow = sample.height < -2.4f || patch < 0.27f; @@ -146,7 +146,7 @@ void GrassDemo::BuildField() { domain_.seed = 0x7a6b5c4du; domain_.sample_revision = 1; domain_.type_revision = 1; - domain_.settings.candidate_spacing = std::clamp(GrassSpacing.get(), 0.08f, 1.0f); + domain_.settings.candidate_spacing = rx::Clamp(GrassSpacing.get(), 0.08f, 1.0f); domain_.settings.stream_tile_size = 18.0f; domain_.settings.stream_radius = 120.0f; domain_.settings.density_lod_start = 40.0f; @@ -158,10 +158,10 @@ void GrassDemo::BuildField() { domain_.settings.fade_end = 120.0f; // Distant one-segment rings out to roughly a kilometre (RX_GRASS_FAR=0 // restores the classic stream-bounded field). - domain_.settings.far_radius = std::clamp(GrassFarRadius.get(), 0.0f, 4096.0f); + domain_.settings.far_radius = rx::Clamp(GrassFarRadius.get(), 0.0f, 4096.0f); domain_.settings.max_slope_cos = 0.50f; domain_.settings.max_blades = - static_cast(std::clamp(GrassMaxBlades.get(), 1, 262144)); + static_cast(rx::Clamp(GrassMaxBlades.get(), 1, 262144)); } void GrassDemo::BuildTerrain() { @@ -343,11 +343,11 @@ void GrassDemo::Create() { void GrassDemo::Update(f32 dt) { time_ += dt; - const f32 x = std::sin(time_ * 0.34f) * 12.0f - 3.0f; - const f32 z = std::cos(time_ * 0.27f) * 7.0f + 4.0f; + const f32 x = ::sin(time_ * 0.34f) * 12.0f - 3.0f; + const f32 z = ::cos(time_ * 0.27f) * 7.0f + 4.0f; const f32 y = TerrainHeight(x, z) + 0.62f; interaction_position_ = {x, y, z}; - interaction_direction_ = {std::cos(time_ * 0.34f), 0.0f, -std::sin(time_ * 0.27f)}; + interaction_direction_ = {::cos(time_ * 0.34f), 0.0f, -::sin(time_ * 0.27f)}; if (scene::Transform* transform = ctx_.world->Get(interaction_marker_)) { transform->position[0] = x; diff --git a/runtime/demo_gym.cc b/runtime/demo_gym.cc index 47e5694e..2712ea97 100644 --- a/runtime/demo_gym.cc +++ b/runtime/demo_gym.cc @@ -1,12 +1,14 @@ #include "demo_gym.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" #include "core/log.h" +#include "core/scalar.h" #include "ecs/world.h" #include "asset/primitives.h" #include "asset/material.h" @@ -47,7 +49,7 @@ asset::Texture MakeChecker(asset::AssetId id, u32 res, u32 cells, Rgb a, Rgb b, tex.is_srgb = true; tex.data.resize(res * res * 4); const u32 cell_px = res / cells; - const u32 line_w = std::max(1u, res / 128); // ~2px grid lines + const u32 line_w = rx::Max(1u, res / 128); // ~2px grid lines for (u32 y = 0; y < res; ++y) { for (u32 x = 0; x < res; ++x) { const u32 cx = x / cell_px; @@ -63,7 +65,7 @@ asset::Texture MakeChecker(asset::AssetId id, u32 res, u32 cells, Rgb a, Rgb b, c = Rgb{0xE0, 0x82, 0x28}; const f32 fx = static_cast(x % cell_px) / static_cast(cell_px); const f32 fy = static_cast(y % cell_px) / static_cast(cell_px); - if (fx + std::fabs(fy - 0.5f) < 0.55f && fx > 0.15f) c = Rgb{0x18, 0x18, 0x18}; + if (fx + ::fabs(fy - 0.5f) < 0.55f && fx > 0.15f) c = Rgb{0x18, 0x18, 0x18}; } u8* p = &tex.data[(y * res + x) * 4]; p[0] = c.r; @@ -121,9 +123,9 @@ void PushVert(MeshBuilder& b, const Vec3& p, const Vec3& n, const Vec3& tan, f32 void AddBox(MeshBuilder& b, const Vec3& c, const Vec3& h, f32 uv_scale) { for (const Face& f : kFaces) { const u32 base = static_cast(b.lod().vertices.size()); - const f32 hu = std::fabs(Dot(f.u, h)); - const f32 hv = std::fabs(Dot(f.v, h)); - const Vec3 fc = c + f.n * std::fabs(Dot(f.n, h)); + const f32 hu = ::fabs(Dot(f.u, h)); + const f32 hv = ::fabs(Dot(f.v, h)); + const Vec3 fc = c + f.n * ::fabs(Dot(f.n, h)); const Vec3 corners[4] = { fc - f.u * hu - f.v * hv, fc + f.u * hu - f.v * hv, @@ -140,9 +142,9 @@ void AddBox(MeshBuilder& b, const Vec3& c, const Vec3& h, f32 uv_scale) { void AddRotatedBox(MeshBuilder& b, const Vec3& c, const Vec3& h, const Quat& q, f32 uv_scale) { for (const Face& f : kFaces) { const u32 base = static_cast(b.lod().vertices.size()); - const f32 hu = std::fabs(Dot(f.u, h)); - const f32 hv = std::fabs(Dot(f.v, h)); - const Vec3 fc_local = f.n * std::fabs(Dot(f.n, h)); + const f32 hu = ::fabs(Dot(f.u, h)); + const f32 hv = ::fabs(Dot(f.v, h)); + const Vec3 fc_local = f.n * ::fabs(Dot(f.n, h)); const Vec3 local[4] = { fc_local - f.u * hu - f.v * hv, fc_local + f.u * hu - f.v * hv, @@ -194,7 +196,7 @@ asset::Mesh MakeUnitCylinder(asset::AssetId id, u32 segments) { for (u32 i = 0; i <= segments; ++i) { const f32 t = static_cast(i) / segments; const f32 a = t * 2.0f * kPi; - const Vec3 n{std::cos(a), 0, std::sin(a)}; + const Vec3 n{::cos(a), 0, ::sin(a)}; for (int k = 0; k < 2; ++k) { asset::Vertex v{}; v.position[0] = n.x; @@ -234,8 +236,8 @@ Quat HeadingQuat(f32 yaw) { return QuatFromAxisAngle({0, -1, 0}, yaw); } // physics). Mirror the Host's fixed step: RX_FIXED_DT when set, else the // FrameTimer default of 1/60 s. f32 GymFixedStep() { - if (const char* env = std::getenv("RX_FIXED_DT")) { - const f32 v = std::strtof(env, nullptr); + if (const char* env = ::getenv("RX_FIXED_DT")) { + const f32 v = ::strtof(env, nullptr); if (v > 0.0f) return v; } return 1.0f / 60.0f; @@ -265,41 +267,41 @@ void GymDemo::Create() { // Env-gated staging for captures. RX_GYM_VIEW=fp|tp, RX_GYM_SPAWN="x,z"[,y], // RX_GYM_YAW=, RX_GYM_SCRIPT="t:token,t:token,...". - if (const char* spawn = std::getenv("RX_GYM_SPAWN")) { + if (const char* spawn = ::getenv("RX_GYM_SPAWN")) { Vec3 p = spawn_feet_; - if (std::sscanf(spawn, "%f,%f,%f", &p.x, &p.z, &p.y) >= 2) { + if (::sscanf(spawn, "%f,%f,%f", &p.x, &p.z, &p.y) >= 2) { spawn_feet_ = p; ResetPlayer(); } } - if (const char* yaw = std::getenv("RX_GYM_YAW")) { - spawn_yaw_ = std::strtof(yaw, nullptr); + if (const char* yaw = ::getenv("RX_GYM_YAW")) { + spawn_yaw_ = ::strtof(yaw, nullptr); if (auto* st = ctx_.world->Get(player_)) st->yaw = spawn_yaw_; } - if (const char* pitch = std::getenv("RX_GYM_PITCH")) { + if (const char* pitch = ::getenv("RX_GYM_PITCH")) { if (auto* orbit = ctx_.world->Get(player_)) - orbit->pitch = std::strtof(pitch, nullptr); + orbit->pitch = ::strtof(pitch, nullptr); } - if (const char* view = std::getenv("RX_GYM_VIEW")) { - const bool want_tp = std::strcmp(view, "tp") == 0; + if (const char* view = ::getenv("RX_GYM_VIEW")) { + const bool want_tp = ::strcmp(view, "tp") == 0; auto* vm = ctx_.world->Get(player_); if (vm && ((vm->kind == character::CharacterViewKind::kThirdPerson) != want_tp)) { character::ToggleCharacterViewMode(*ctx_.world, player_, camera_output_, player_, view_settings_, {.duration = 0.0f}); } } - if (const char* script = std::getenv("RX_GYM_SCRIPT")) { - std::string s(script); + if (const char* script = ::getenv("RX_GYM_SCRIPT")) { + base::String s(script); size_t pos = 0; while (pos < s.size()) { size_t comma = s.find(',', pos); - if (comma == std::string::npos) comma = s.size(); - std::string entry = s.substr(pos, comma - pos); + if (comma == base::String::npos) comma = s.size(); + base::String entry = s.substr(pos, comma - pos); pos = comma + 1; size_t colon = entry.find(':'); - if (colon == std::string::npos) continue; + if (colon == base::String::npos) continue; ScriptStep step; - step.time = std::strtof(entry.substr(0, colon).c_str(), nullptr); + step.time = ::strtof(entry.substr(0, colon).c_str(), nullptr); step.token = entry.substr(colon + 1); script_.push_back(step); } @@ -414,7 +416,7 @@ void GymDemo::BuildContent() { const f32 ang = deg * kPi / 180.0f; const f32 run = 3.0f, w = 1.4f, thick = 0.15f; const Quat q = QuatFromAxisAngle({1, 0, 0}, -ang); // tilt up toward -Z - const Vec3 center{x, std::sin(ang) * run * 0.5f + 0.05f, z - std::cos(ang) * run * 0.5f}; + const Vec3 center{x, ::sin(ang) * run * 0.5f + 0.05f, z - ::cos(ang) * run * 0.5f}; AddRotatedBox(st, center, {w * 0.5f, thick * 0.5f, run * 0.5f}, q, 1.0f); physics::ShapeDesc box; box.kind = physics::ShapeDesc::Kind::kBox; @@ -518,7 +520,7 @@ void GymDemo::BuildPlayer() { character::CharacterMovementSettings move; const f32 total_half = character::CharacterShape{}.standing_height * 0.5f; // 0.9 to capsule tip const f32 radius = shape.standing_radius; - const f32 half_height = std::max(shape.standing_height * 0.5f - radius, 0.01f); + const f32 half_height = rx::Max(shape.standing_height * 0.5f - radius, 0.01f); player_ = world.Create(); world.Add(player_, scene::Transform{.position = {spawn_feet_.x, spawn_feet_.y, spawn_feet_.z}}); @@ -538,7 +540,7 @@ void GymDemo::BuildPlayer() { world.Add(player_, character::JetpackInput{}); world.Add(player_, character::JetpackState{}); if (ctx_.audio) - jetpack_audio_ = std::make_unique(ctx_.audio->mixer(), + jetpack_audio_ = base::MakeUnique(ctx_.audio->mixer(), audio::LightJetPreset()); // Inventory with a handful of crates to drop. @@ -558,7 +560,7 @@ void GymDemo::BuildPlayer() { } void GymDemo::ResetPlayer() { - spawn_feet_.y = std::max(spawn_feet_.y, 0.0f); + spawn_feet_.y = rx::Max(spawn_feet_.y, 0.0f); character::TeleportCharacter(*ctx_.world, *ctx_.physics, player_, spawn_feet_); if (auto* st = ctx_.world->Get(player_)) st->yaw = spawn_yaw_; } @@ -619,7 +621,7 @@ void GymDemo::FillIntent(const InputState& input, const ActionState& actions, bo void GymDemo::RunScript(f32 dt) { script_time_ += dt; while (script_cursor_ < script_.size() && script_[script_cursor_].time <= script_time_) { - const std::string& t = script_[script_cursor_].token; + const base::String& t = script_[script_cursor_].token; ++script_cursor_; if (t == "fwd") { script_move_fwd_ = 1; script_move_right_ = 0; } else if (t == "fwdhalf") { script_move_fwd_ = 0.5f; script_move_right_ = 0; } // analog: half stick @@ -644,7 +646,7 @@ void GymDemo::SyncViewSettingsToRig() { if (auto* boom = world.Get(player_)) { boom->shoulder_offset = vs.tp_shoulder_offset; boom->height_offset = vs.tp_height_offset; - boom->distance = std::clamp(boom->distance, vs.tp_min_distance, vs.tp_max_distance); + boom->distance = rx::Clamp(boom->distance, vs.tp_min_distance, vs.tp_max_distance); } if (auto* ob = world.Get(player_)) { ob->radius = vs.tp_obstruction_radius; @@ -763,7 +765,7 @@ void GymDemo::Update(f32 dt, const InputState& input, const ActionState& actions // spiral of death) so the physics-coupled update advances on the engine's // Jolt cadence rather than the raw render frame delta. static f32 sim_accum = 0.0f; - sim_accum += std::min(dt, 0.25f); + sim_accum += rx::Min(dt, 0.25f); steps = 0; while (sim_accum >= fixed) { sim_accum -= fixed; @@ -787,7 +789,7 @@ void GymDemo::Update(f32 dt, const InputState& input, const ActionState& actions // Moving platform: ping-pong along X, driven kinematically so a standing body // could ride it (character platform-riding is not yet folded into the module). platform_time_ += fixed; - const f32 offset = std::sin(platform_time_ * 0.6f) * platform_span_; + const f32 offset = ::sin(platform_time_ * 0.6f) * platform_span_; const Vec3 target{platform_center_.x + offset, platform_center_.y, platform_center_.z}; const f32 identity_rot[4] = {0, 0, 0, 1}; if (platform_body_) phys.MoveBodyKinematic(platform_body_, target, identity_rot, fixed); @@ -856,7 +858,7 @@ void GymDemo::Emit(f32 dt, render::FrameView& view) { const bool third_person = vm && vm->kind == character::CharacterViewKind::kThirdPerson; if (third_person && body && tr && st) { const f32 radius = body->radius; - const f32 cyl_len = std::max(2.0f * body->half_height, 0.01f); + const f32 cyl_len = rx::Max(2.0f * body->half_height, 0.01f); const Vec3 feet{tr->position[0], tr->position[1], tr->position[2]}; const f32 center_y = feet.y + radius + cyl_len * 0.5f; // Body faces the smoothed facing yaw (turn smoothing); in third person it @@ -906,14 +908,14 @@ void GymDemo::DrawPanel() { if (!shape || !move || !state) return; ImGui::SetNextWindowSize(ImVec2(340, 820), ImGuiCond_FirstUseEver); - const f32 panel_x = std::max(20.0f, ImGui::GetIO().DisplaySize.x - 360.0f); + const f32 panel_x = rx::Max(20.0f, ImGui::GetIO().DisplaySize.x - 360.0f); ImGui::SetNextWindowPos(ImVec2(panel_x, 20), ImGuiCond_FirstUseEver); if (ImGui::Begin("Gym - character tuning")) { const char* stance = state->stance == character::CharacterStance::kCrouching ? "crouch" : "stand"; const bool tp = vm && vm->kind == character::CharacterViewKind::kThirdPerson; ImGui::Text("view: %s stance: %s %s", tp ? "third-person" : "first-person", stance, state->grounded ? "grounded" : "airborne"); - const f32 speed = std::sqrt(state->velocity.x * state->velocity.x + + const f32 speed = ::sqrt(state->velocity.x * state->velocity.x + state->velocity.z * state->velocity.z); ImGui::Text("speed: %.2f m/s eye: %.2f m crouch: %.0f%%", speed, state->eye_height, state->crouch_blend * 100.0f); @@ -936,7 +938,7 @@ void GymDemo::DrawPanel() { const char* tag = jst->refueling ? " refuel" : (jst->burning ? " BURN" : ""); ImGui::Text("jetpack: %s%s (J toggle, hold Space to burn)", on ? "ON" : "off", tag); char label[32]; - std::snprintf(label, sizeof(label), "fuel %.0f%%", jst->fuel * 100.0f); + ::snprintf(label, sizeof(label), "fuel %.0f%%", jst->fuel * 100.0f); ImGui::ProgressBar(jst->fuel, ImVec2(-1.0f, 0.0f), label); ImGui::Text("thrust %.0f%%", jst->thrust * 100.0f); } diff --git a/runtime/demo_gym.h b/runtime/demo_gym.h index 30c5adbe..bd2ca274 100644 --- a/runtime/demo_gym.h +++ b/runtime/demo_gym.h @@ -1,11 +1,11 @@ #ifndef RX_RUNTIME_DEMO_GYM_H_ #define RX_RUNTIME_DEMO_GYM_H_ -#include -#include -#include #include "audio/vehicle_audio.h" +#include "base/containers/vector.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" #include "core/input.h" #include "core/input_actions.h" #include "core/math.h" @@ -96,7 +96,7 @@ class GymDemo { // Jetpack: a LightJetPreset voice at the player, N1 tracking the spooled thrust // and the roar the burn demand; ducked/muffled when the tank runs dry. - std::unique_ptr jetpack_audio_; + base::UniquePointer jetpack_audio_; Vec3 spawn_feet_{0, 0, 8}; f32 spawn_yaw_ = 0; @@ -114,9 +114,9 @@ class GymDemo { // Env-gated scripted input for staged, headless-style captures (RX_GYM_SCRIPT). struct ScriptStep { f32 time = 0; - std::string token; + base::String token; }; - std::vector script_; + base::Vector script_; f32 script_time_ = 0; u32 script_cursor_ = 0; // Movement the active script segment holds until the next segment. diff --git a/runtime/demo_lookdev.cc b/runtime/demo_lookdev.cc index cfaca4a6..da0bd2c2 100644 --- a/runtime/demo_lookdev.cc +++ b/runtime/demo_lookdev.cc @@ -1,11 +1,9 @@ #include "demo_lookdev.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #if defined(RX_HAS_IMGUI) #include @@ -16,10 +14,18 @@ #include "asset/gltf_loader.h" #include "asset/primitives.h" #include "asset/scene_import.h" +#include "base/containers/span.h" +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" #include "core/log.h" +#include "core/scalar.h" #include "ecs/world.h" #include "render/post/reference_compare.h" #include "scene/components.h" +#include "core/file_system.h" namespace rx { namespace { @@ -103,10 +109,9 @@ const char* RegionName(HumanRegion r) { // named submeshes and nothing else; guessing from the name is what makes the // lab usable on a downloaded asset instead of only on hand-authored content. // It is a guess - the panel lets it be overridden per material. -HumanRegion RegionFromName(std::string name) { - std::transform(name.begin(), name.end(), name.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - auto has = [&](const char* needle) { return name.find(needle) != std::string::npos; }; +HumanRegion RegionFromName(base::String name) { + for (char& c : name) c = static_cast(::tolower(static_cast(c))); + auto has = [&](const char* needle) { return name.find(needle) != base::String::npos; }; if (has("cornea")) return HumanRegion::kCornea; if (has("iris") || has("pupil")) return HumanRegion::kIris; if (has("sclera") || has("eyeball") || has("eye")) return HumanRegion::kSclera; @@ -162,8 +167,8 @@ constexpr FitField kFitFields[] = { } // namespace -std::span LookdevDemo::light_stops() { return kLightStops; } -std::span LookdevDemo::camera_stops() { return kCameraStops; } +base::Span LookdevDemo::light_stops() { return kLightStops; } +base::Span LookdevDemo::camera_stops() { return kCameraStops; } // A procedural sweat / sebum normal map, bound as the SPECULAR normal (Ns). // This is the one layer that proves the split is worth having: droplets have to @@ -177,7 +182,7 @@ asset::Texture MakeSweatNormal(u32 size) { texture.width = size; texture.height = size; texture.is_srgb = false; // a normal map is data, not colour - texture.data.resize(static_cast(size) * size * 4); + texture.data.resize(static_cast(size) * size * 4); // Deterministic droplet field: a fixed hash, so two runs of the lab produce // the same surface and a capture diff stays a renderer diff. @@ -204,12 +209,12 @@ asset::Texture MakeSweatNormal(u32 size) { const f32 radius = cell * (0.18f + 0.22f * (static_cast((h >> 24) & 255u) / 255.0f)); const f32 dx = static_cast(px) - (static_cast(gx) + jx) * cell; const f32 dy = static_cast(py) - (static_cast(gy) + jy) * cell; - const f32 d = std::sqrt(dx * dx + dy * dy); + const f32 d = ::sqrt(dx * dx + dy * dy); if (d >= radius || radius <= 0.0f) continue; // Hemispherical bead: the slope grows toward the rim. const f32 t = d / radius; - const f32 slope = t / std::sqrt(std::max(1.0f - t * t, 1e-3f)); - const f32 k = std::min(slope, 3.0f) / 3.0f; + const f32 slope = t / ::sqrt(rx::Max(1.0f - t * t, 1e-3f)); + const f32 k = rx::Min(slope, 3.0f) / 3.0f; if (d > 1e-4f) { nx += (dx / d) * k; ny += (dy / d) * k; @@ -217,11 +222,11 @@ asset::Texture MakeSweatNormal(u32 size) { } } Vec3 n{nx, ny, 1.0f}; - const f32 len = std::sqrt(n.x * n.x + n.y * n.y + 1.0f); - const std::size_t o = (static_cast(py) * size + px) * 4; - texture.data[o + 0] = static_cast(std::clamp((n.x / len) * 0.5f + 0.5f, 0.0f, 1.0f) * 255.0f); - texture.data[o + 1] = static_cast(std::clamp((n.y / len) * 0.5f + 0.5f, 0.0f, 1.0f) * 255.0f); - texture.data[o + 2] = static_cast(std::clamp((1.0f / len) * 0.5f + 0.5f, 0.0f, 1.0f) * 255.0f); + const f32 len = ::sqrt(n.x * n.x + n.y * n.y + 1.0f); + const size_t o = (static_cast(py) * size + px) * 4; + texture.data[o + 0] = static_cast(rx::Clamp((n.x / len) * 0.5f + 0.5f, 0.0f, 1.0f) * 255.0f); + texture.data[o + 1] = static_cast(rx::Clamp((n.y / len) * 0.5f + 0.5f, 0.0f, 1.0f) * 255.0f); + texture.data[o + 2] = static_cast(rx::Clamp((1.0f / len) * 0.5f + 0.5f, 0.0f, 1.0f) * 255.0f); texture.data[o + 3] = 255; } } @@ -254,13 +259,13 @@ struct LookdevDemo::Impl { HumanRegion region = HumanRegion::kSkin; HumanSurfaceParameters params; bool enabled = true; - std::string name; + base::String name; }; - std::vector parts; - std::vector entities; + base::Vector parts; + base::Vector entities; Vec3 subject_center{0.0f, 1.6f, 0.0f}; f32 subject_radius = 0.12f; // metres; the head's bounding radius - std::string subject_path; + base::String subject_path; bool procedural_subject = false; // Ns for the sweat/sebum layer; bound on every skin part, off until dialled. asset::AssetId sweat_normal{}; @@ -275,8 +280,8 @@ struct LookdevDemo::Impl { f32 gpu_budget_ms = 8.0f; // OLAT purity: no environment unless asked for // comparison - std::string reference_path; - std::string mask_path; + base::String reference_path; + base::String mask_path; char reference_input[256] = {}; char mask_input[256] = {}; @@ -284,8 +289,8 @@ struct LookdevDemo::Impl { // Parameter history: look-dev is a search, and a search without an undo is a // walk. Each entry is the whole part table, which is small and makes the // restore exact rather than field-by-field. - std::vector> history; - std::size_t history_cursor = 0; + base::Vector> history; + size_t history_cursor = 0; f32 history_cooldown = 0.0f; bool dirty = false; bool pending_history = false; @@ -305,16 +310,16 @@ struct LookdevDemo::Impl { f32 step_scale = 0.25f; int passes_left = 0; HumanRegion region = HumanRegion::kSkin; - std::vector stops = {3, 5, 6, 7, 8}; + base::Vector stops = {3, 5, 6, 7, 8}; f32 saved = 0.0f; - std::string log; + base::String log; } fit; // deterministic capture struct Capture { bool running = false; bool finished = false; - std::string dir; + base::String dir; int light = 0; int camera = 0; int settle = 0; @@ -332,8 +337,8 @@ struct LookdevDemo::Impl { void SelectCamera(int index); void StepFit(); void StepCapture(); - void SavePreset(const std::string& path) const; - bool LoadPreset(const std::string& path); + void SavePreset(const base::String& path) const; + bool LoadPreset(const base::String& path); void DrawPanel(); render::CameraPose ResolveCamera() const; void EmitLights(render::FrameView& view); @@ -352,9 +357,9 @@ void LookdevDemo::Impl::BuildProceduralSubject() { part.name = name; part.region = region; part.params = render::HumanPreset(region); - part.material.id = asset::MakeAssetId(std::string("builtin/lookdev/mat_") + name); + part.material.id = asset::MakeAssetId(base::String("builtin/lookdev/mat_") + name); part.material.name = name; - std::memcpy(part.material.base_color_factor, color, sizeof(f32) * 3); + base::MemCopy(part.material.base_color_factor, color, sizeof(f32) * 3); part.material.base_color_factor[3] = 1.0f; part.material.roughness_factor = roughness; part.material.human = true; @@ -389,7 +394,7 @@ void LookdevDemo::Impl::BuildProceduralSubject() { ctx.world->Add(e, t); ctx.world->Add(e, scene::Renderable{mesh.id}); entities.push_back(e); - parts.push_back(std::move(part)); + parts.push_back(base::move(part)); }; const f32 skin_color[3] = {0.62f, 0.44f, 0.35f}; @@ -429,7 +434,7 @@ void LookdevDemo::Impl::LoadSubject() { ctx.renderer->UploadTexture(sweat); sweat_normal = sweat.id; } - std::string path; + base::String path; if (const char* explicit_path = LookdevSubject.get()) path = explicit_path; if (path.empty() && ctx.config && !ctx.config->scene_path.empty()) path = ctx.config->scene_path; if (path.empty()) { @@ -439,13 +444,13 @@ void LookdevDemo::Impl::LoadSubject() { // tree, so it predates this work). Pass it explicitly if you want it. const char* candidates[] = {"assets/head/head.glb", "assets/head/lps_head.glb"}; for (const char* candidate : candidates) { - if (std::filesystem::exists(candidate)) { + if (fs::Exists(candidate)) { path = candidate; break; } } } - if (path.empty() || !std::filesystem::exists(path)) { + if (path.empty() || !fs::Exists(path)) { RX_INFO("lookdev: no head asset found, using the procedural stand-in " "(run tools/get_head_scan.sh, or pass RX_LOOKDEV_SUBJECT=)"); BuildProceduralSubject(); @@ -471,8 +476,8 @@ void LookdevDemo::Impl::LoadSubject() { Vec3 p{v.position[0] * instance.scale + instance.position.x, v.position[1] * instance.scale + instance.position.y, v.position[2] * instance.scale + instance.position.z}; - lo = {std::min(lo.x, p.x), std::min(lo.y, p.y), std::min(lo.z, p.z)}; - hi = {std::max(hi.x, p.x), std::max(hi.y, p.y), std::max(hi.z, p.z)}; + lo = {rx::Min(lo.x, p.x), rx::Min(lo.y, p.y), rx::Min(lo.z, p.z)}; + hi = {rx::Max(hi.x, p.x), rx::Max(hi.y, p.y), rx::Max(hi.z, p.z)}; } } Vec3 extent{hi.x - lo.x, hi.y - lo.y, hi.z - lo.z}; @@ -509,13 +514,13 @@ void LookdevDemo::Impl::LoadSubject() { v.position[2] * instance.scale + instance.position.z}; if (p.y < slice_floor) continue; const Vec3 d{p.x - subject_center.x, p.y - subject_center.y, p.z - subject_center.z}; - r2 = std::max(r2, d.x * d.x + d.y * d.y + d.z * d.z); + r2 = rx::Max(r2, d.x * d.x + d.y * d.y + d.z * d.z); } } - subject_radius = std::max(std::sqrt(r2), 1e-3f); + subject_radius = rx::Max(::sqrt(r2), 1e-3f); } else { subject_center = {(lo.x + hi.x) * 0.5f, (lo.y + hi.y) * 0.5f, (lo.z + hi.z) * 0.5f}; - subject_radius = 0.5f * std::max({extent.x, extent.y, extent.z}); + subject_radius = 0.5f * rx::Max({extent.x, extent.y, extent.z}); } } @@ -526,7 +531,7 @@ void LookdevDemo::Impl::LoadSubject() { } for (asset::Material& material : scene.materials) { Part part; - part.name = material.name.empty() ? std::string("material") : material.name; + part.name = material.name.empty() ? base::String("material") : material.name; part.region = RegionFromName(part.name); part.params = render::HumanPreset(part.region); part.material = material; @@ -545,7 +550,7 @@ void LookdevDemo::Impl::LoadSubject() { part.material.skin_params.mfp[2] = mfp_mm * 0.2f; } if (!ctx.config->headless) ctx.renderer->UploadMaterial(part.material); - parts.push_back(std::move(part)); + parts.push_back(base::move(part)); } if (!ctx.config->headless) { for (const asset::Mesh& mesh : scene.meshes) ctx.renderer->UploadMesh(mesh); @@ -557,7 +562,7 @@ void LookdevDemo::Impl::LoadSubject() { t.position[0] = instance.position.x; t.position[1] = instance.position.y; t.position[2] = instance.position.z; - std::memcpy(t.rotation, instance.rotation, sizeof(f32) * 4); + base::MemCopy(t.rotation, instance.rotation, sizeof(f32) * 4); t.scale = instance.scale; ctx.world->Add(e, t); ctx.world->Add(e, scene::Renderable{mesh.id}); @@ -582,8 +587,8 @@ void LookdevDemo::Impl::ApplyPartsToRenderer() { // rig render::CameraPose LookdevDemo::Impl::ResolveCamera() const { - const CameraStop& stop = kCameraStops[std::clamp(camera_index, 0, - static_cast(std::size(kCameraStops)) - 1)]; + const CameraStop& stop = kCameraStops[rx::Clamp(camera_index, 0, + static_cast((sizeof(kCameraStops) / sizeof(kCameraStops[0]))) - 1)]; const f32 yaw = stop.yaw_degrees * kDeg; const f32 pitch = stop.pitch_degrees * kDeg; // Distances are authored for a head of ~0.115 m radius; scaling by the actual @@ -592,15 +597,15 @@ render::CameraPose LookdevDemo::Impl::ResolveCamera() const { const f32 distance = stop.distance * scale; render::CameraPose pose; pose.target = subject_center; - pose.eye = {subject_center.x + std::sin(yaw) * std::cos(pitch) * distance, - subject_center.y - std::sin(pitch) * distance, - subject_center.z - std::cos(yaw) * std::cos(pitch) * distance}; + pose.eye = {subject_center.x + ::sin(yaw) * ::cos(pitch) * distance, + subject_center.y - ::sin(pitch) * distance, + subject_center.z - ::cos(yaw) * ::cos(pitch) * distance}; pose.fov_y = stop.fov_degrees * kDeg; return pose; } void LookdevDemo::Impl::SelectLight(int index) { - const int count = static_cast(std::size(kLightStops)); + const int count = static_cast((sizeof(kLightStops) / sizeof(kLightStops[0]))); light_index = ((index % count) + count) % count; const LightStop& stop = kLightStops[light_index]; render::RenderSettings& s = ctx.renderer->settings(); @@ -621,7 +626,7 @@ void LookdevDemo::Impl::SelectLight(int index) { } void LookdevDemo::Impl::SelectCamera(int index) { - const int count = static_cast(std::size(kCameraStops)); + const int count = static_cast((sizeof(kCameraStops) / sizeof(kCameraStops[0]))); camera_index = ((index % count) + count) % count; } @@ -632,7 +637,7 @@ void LookdevDemo::Impl::EmitLights(render::FrameView& view) { const Vec3 t = Normalize(stop.travel); // Place the emitter a fixed multiple of the subject size back along the light // direction, so a stop frames the same way on any asset. - const f32 distance = std::max(subject_radius * 6.0f, 0.6f); + const f32 distance = rx::Max(subject_radius * 6.0f, 0.6f); // An area light's `intensity` is RADIANCE, so a 0.9 m panel and an 8 mm ball // at the same number differ by three orders of magnitude in how much light // they put on the face. The stops are authored as an ILLUMINANCE target (the @@ -643,7 +648,7 @@ void LookdevDemo::Impl::EmitLights(render::FrameView& view) { render::PointLight light; const Vec3 d = Normalize(travel); const f32 area = (type == 3u) ? (2.0f * size) * (2.0f * size) : kPi * size * size; - const f32 solid_angle = std::max(area / std::max(distance * distance, 1e-6f), 1e-6f); + const f32 solid_angle = rx::Max(area / rx::Max(distance * distance, 1e-6f), 1e-6f); const f32 intensity = illuminance / solid_angle; light.pos_radius[0] = subject_center.x - d.x * distance; light.pos_radius[1] = subject_center.y - d.y * distance; @@ -715,7 +720,7 @@ void LookdevDemo::Impl::StepFit() { fit.running = false; return; } - const int field_count = static_cast(std::size(kFitFields)); + const int field_count = static_cast((sizeof(kFitFields) / sizeof(kFitFields[0]))); // Skip fields above the current stage. while (fit.field < field_count && kFitFields[fit.field].stage > fit.stage) ++fit.field; if (fit.field >= field_count) { @@ -747,7 +752,7 @@ void LookdevDemo::Impl::StepFit() { if (!range.known) { // Stepping a field over a made-up range is not a fit, it is a random walk. fit.running = false; - fit.log = std::string("no safe range for ") + field.name; + fit.log = base::String("no safe range for ") + field.name; return; } f32& value = target->params.*(field.member); @@ -777,23 +782,23 @@ void LookdevDemo::Impl::StepFit() { const f32 step = span * fit.step_scale * 0.25f; if (fit.probe == 0) { fit.saved = value; - value = std::clamp(fit.saved - step, range.lo, range.hi); + value = rx::Clamp(fit.saved - step, range.lo, range.hi); fit.probe = 1; } else if (fit.probe == 1) { - value = std::clamp(fit.saved + step, range.lo, range.hi); + value = rx::Clamp(fit.saved + step, range.lo, range.hi); fit.probe = 2; } else { const bool minus = fit.error[1] < fit.error[0] && fit.error[1] <= fit.error[2]; const bool plus = fit.error[2] < fit.error[0] && fit.error[2] < fit.error[1]; if (minus) { - value = std::clamp(fit.saved - step, range.lo, range.hi); + value = rx::Clamp(fit.saved - step, range.lo, range.hi); } else if (plus) { - value = std::clamp(fit.saved + step, range.lo, range.hi); + value = rx::Clamp(fit.saved + step, range.lo, range.hi); } else { value = fit.saved; // neither direction helped; leave it alone } char line[192]; - std::snprintf(line, sizeof(line), "%s %.4f (%s)\n", field.name, value, + ::snprintf(line, sizeof(line), "%s %.4f (%s)\n", field.name, value, minus ? "-" : (plus ? "+" : "=")); fit.log += line; ++fit.field; @@ -818,8 +823,8 @@ void LookdevDemo::Impl::StepCapture() { --capture.settle; return; } - const int lights = static_cast(std::size(kLightStops)); - const int cameras = static_cast(std::size(kCameraStops)); + const int lights = static_cast((sizeof(kLightStops) / sizeof(kLightStops[0]))); + const int cameras = static_cast((sizeof(kCameraStops) / sizeof(kCameraStops[0]))); if (capture.light >= lights) { capture.running = false; capture.finished = true; @@ -827,7 +832,7 @@ void LookdevDemo::Impl::StepCapture() { return; } char name[512]; - std::snprintf(name, sizeof(name), "%s/lookdev_%02d_%s__%s.png", capture.dir.c_str(), + ::snprintf(name, sizeof(name), "%s/lookdev_%02d_%s__%s.png", capture.dir.c_str(), capture.light, kLightStops[capture.light].name + 3, kCameraStops[capture.camera].name); // Spaces in a stop name would make the filename awkward to script over. @@ -849,57 +854,57 @@ void LookdevDemo::Impl::StepCapture() { // A flat key=value file, one section per part. Deliberately not a binary blob: // a validation preset is something people diff, review and paste into a bug. -void LookdevDemo::Impl::SavePreset(const std::string& path) const { - FILE* f = std::fopen(path.c_str(), "wb"); +void LookdevDemo::Impl::SavePreset(const base::String& path) const { + FILE* f = ::fopen(path.c_str(), "wb"); if (!f) { RX_WARN("lookdev: cannot write {}", path); return; } - std::fprintf(f, "# rx character look-dev preset\n"); - std::fprintf(f, "model_version %u\n", render::kHumanModelVersion); - std::fprintf(f, "subject %s\n", subject_path.empty() ? "" : subject_path.c_str()); - std::fprintf(f, "light %d\ncamera %d\ntier %d\nexposure %.6f\n", light_index, camera_index, + ::fprintf(f, "# rx character look-dev preset\n"); + ::fprintf(f, "model_version %u\n", render::kHumanModelVersion); + ::fprintf(f, "subject %s\n", subject_path.empty() ? "" : subject_path.c_str()); + ::fprintf(f, "light %d\ncamera %d\ntier %d\nexposure %.6f\n", light_index, camera_index, static_cast(tier), exposure_scale); for (const Part& part : parts) { const HumanSurfaceParameters& p = part.params; - std::fprintf(f, "\n[part] %s\nregion %s\nenabled %d\n", part.name.c_str(), + ::fprintf(f, "\n[part] %s\nregion %s\nenabled %d\n", part.name.c_str(), RegionName(part.region), part.enabled ? 1 : 0); - std::fprintf(f, "diffuse_fresnel %.6f %.6f %.6f\n", p.diffuse_fresnel_peak, + ::fprintf(f, "diffuse_fresnel %.6f %.6f %.6f\n", p.diffuse_fresnel_peak, p.diffuse_fresnel_falloff, p.diffuse_fresnel_tangent_falloff); - std::fprintf(f, "retro %.6f %.6f %.6f\n", p.retroreflection_peak, p.retroreflection_falloff, + ::fprintf(f, "retro %.6f %.6f %.6f\n", p.retroreflection_peak, p.retroreflection_falloff, p.retroreflection_tangent_falloff); - std::fprintf(f, "terminator %.6f %.6f\n", p.smooth_terminator_amount, + ::fprintf(f, "terminator %.6f %.6f\n", p.smooth_terminator_amount, p.smooth_terminator_length); - std::fprintf(f, "specular %.6f %.6f %.6f\n", p.specular_fresnel_falloff, + ::fprintf(f, "specular %.6f %.6f %.6f\n", p.specular_fresnel_falloff, p.secondary_roughness_scale, p.secondary_specular_weight); - std::fprintf(f, "transport %.6f %.6f %.6f %.6f %.6f\n", p.mean_free_path, p.subsurface_scale, + ::fprintf(f, "transport %.6f %.6f %.6f %.6f %.6f\n", p.mean_free_path, p.subsurface_scale, p.transmission, p.extinction_scale, p.thickness_scale); - std::fprintf(f, "tint %.6f %.6f %.6f\n", p.transmission_tint[0], p.transmission_tint[1], + ::fprintf(f, "tint %.6f %.6f %.6f\n", p.transmission_tint[0], p.transmission_tint[1], p.transmission_tint[2]); - std::fprintf(f, "layer %.6f %.6f %.6f\n", p.corneal_wetness, p.cavity_occlusion, + ::fprintf(f, "layer %.6f %.6f %.6f\n", p.corneal_wetness, p.cavity_occlusion, p.specular_normal_strength); - std::fprintf(f, "eye %.6f %.6f %.6f %.6f %.6f %.6f %.6f\n", p.iris_depth, p.iris_radius, + ::fprintf(f, "eye %.6f %.6f %.6f %.6f %.6f %.6f %.6f\n", p.iris_depth, p.iris_radius, p.pupil_scale, p.limbal_ring_size, p.limbal_ring_power, p.cornea_ior, p.iris_shadow_depth); - std::fprintf(f, "residual %.6f\n", p.residual_weight); + ::fprintf(f, "residual %.6f\n", p.residual_weight); } - std::fclose(f); + ::fclose(f); RX_INFO("lookdev: preset written to {}", path); } -bool LookdevDemo::Impl::LoadPreset(const std::string& path) { - FILE* f = std::fopen(path.c_str(), "rb"); +bool LookdevDemo::Impl::LoadPreset(const base::String& path) { + FILE* f = ::fopen(path.c_str(), "rb"); if (!f) return false; char line[512]; Part* current = nullptr; - while (std::fgets(line, sizeof(line), f)) { + while (::fgets(line, sizeof(line), f)) { if (line[0] == '#' || line[0] == '\n') continue; char key[64] = {}; - if (std::sscanf(line, "%63s", key) != 1) continue; - const char* rest = line + std::strlen(key); - if (std::strcmp(key, "[part]") == 0) { + if (::sscanf(line, "%63s", key) != 1) continue; + const char* rest = line + ::strlen(key); + if (::strcmp(key, "[part]") == 0) { char name[192] = {}; - std::sscanf(rest, " %191[^\n]", name); + ::sscanf(rest, " %191[^\n]", name); current = nullptr; for (Part& part : parts) { if (part.name == name) { @@ -909,9 +914,9 @@ bool LookdevDemo::Impl::LoadPreset(const std::string& path) { } continue; } - if (std::strcmp(key, "model_version") == 0) { + if (::strcmp(key, "model_version") == 0) { u32 version = 0; - std::sscanf(rest, " %u", &version); + ::sscanf(rest, " %u", &version); if (version != render::kHumanModelVersion) { // Fitted numbers are measurements against a specific model. Loading // them under a different one produces plausible-looking values that are @@ -919,63 +924,63 @@ bool LookdevDemo::Impl::LoadPreset(const std::string& path) { RX_WARN("lookdev: preset {} was fitted against model version {}, this build is {} - " "not loading", path, version, render::kHumanModelVersion); - std::fclose(f); + ::fclose(f); return false; } continue; } - if (std::strcmp(key, "light") == 0) { - std::sscanf(rest, " %d", &light_index); + if (::strcmp(key, "light") == 0) { + ::sscanf(rest, " %d", &light_index); continue; } - if (std::strcmp(key, "camera") == 0) { - std::sscanf(rest, " %d", &camera_index); + if (::strcmp(key, "camera") == 0) { + ::sscanf(rest, " %d", &camera_index); continue; } - if (std::strcmp(key, "tier") == 0) { + if (::strcmp(key, "tier") == 0) { int t = 0; - std::sscanf(rest, " %d", &t); - tier = static_cast(std::clamp(t, 0, 2)); + ::sscanf(rest, " %d", &t); + tier = static_cast(rx::Clamp(t, 0, 2)); continue; } - if (std::strcmp(key, "exposure") == 0) { - std::sscanf(rest, " %f", &exposure_scale); + if (::strcmp(key, "exposure") == 0) { + ::sscanf(rest, " %f", &exposure_scale); continue; } if (!current) continue; HumanSurfaceParameters& p = current->params; - if (std::strcmp(key, "diffuse_fresnel") == 0) { - std::sscanf(rest, " %f %f %f", &p.diffuse_fresnel_peak, &p.diffuse_fresnel_falloff, + if (::strcmp(key, "diffuse_fresnel") == 0) { + ::sscanf(rest, " %f %f %f", &p.diffuse_fresnel_peak, &p.diffuse_fresnel_falloff, &p.diffuse_fresnel_tangent_falloff); - } else if (std::strcmp(key, "retro") == 0) { - std::sscanf(rest, " %f %f %f", &p.retroreflection_peak, &p.retroreflection_falloff, + } else if (::strcmp(key, "retro") == 0) { + ::sscanf(rest, " %f %f %f", &p.retroreflection_peak, &p.retroreflection_falloff, &p.retroreflection_tangent_falloff); - } else if (std::strcmp(key, "terminator") == 0) { - std::sscanf(rest, " %f %f", &p.smooth_terminator_amount, &p.smooth_terminator_length); - } else if (std::strcmp(key, "specular") == 0) { - std::sscanf(rest, " %f %f %f", &p.specular_fresnel_falloff, &p.secondary_roughness_scale, + } else if (::strcmp(key, "terminator") == 0) { + ::sscanf(rest, " %f %f", &p.smooth_terminator_amount, &p.smooth_terminator_length); + } else if (::strcmp(key, "specular") == 0) { + ::sscanf(rest, " %f %f %f", &p.specular_fresnel_falloff, &p.secondary_roughness_scale, &p.secondary_specular_weight); - } else if (std::strcmp(key, "transport") == 0) { - std::sscanf(rest, " %f %f %f %f %f", &p.mean_free_path, &p.subsurface_scale, &p.transmission, + } else if (::strcmp(key, "transport") == 0) { + ::sscanf(rest, " %f %f %f %f %f", &p.mean_free_path, &p.subsurface_scale, &p.transmission, &p.extinction_scale, &p.thickness_scale); - } else if (std::strcmp(key, "tint") == 0) { - std::sscanf(rest, " %f %f %f", &p.transmission_tint[0], &p.transmission_tint[1], + } else if (::strcmp(key, "tint") == 0) { + ::sscanf(rest, " %f %f %f", &p.transmission_tint[0], &p.transmission_tint[1], &p.transmission_tint[2]); - } else if (std::strcmp(key, "layer") == 0) { - std::sscanf(rest, " %f %f %f", &p.corneal_wetness, &p.cavity_occlusion, + } else if (::strcmp(key, "layer") == 0) { + ::sscanf(rest, " %f %f %f", &p.corneal_wetness, &p.cavity_occlusion, &p.specular_normal_strength); - } else if (std::strcmp(key, "eye") == 0) { - std::sscanf(rest, " %f %f %f %f %f %f %f", &p.iris_depth, &p.iris_radius, &p.pupil_scale, + } else if (::strcmp(key, "eye") == 0) { + ::sscanf(rest, " %f %f %f %f %f %f %f", &p.iris_depth, &p.iris_radius, &p.pupil_scale, &p.limbal_ring_size, &p.limbal_ring_power, &p.cornea_ior, &p.iris_shadow_depth); - } else if (std::strcmp(key, "residual") == 0) { - std::sscanf(rest, " %f", &p.residual_weight); - } else if (std::strcmp(key, "enabled") == 0) { + } else if (::strcmp(key, "residual") == 0) { + ::sscanf(rest, " %f", &p.residual_weight); + } else if (::strcmp(key, "enabled") == 0) { int e = 1; - std::sscanf(rest, " %d", &e); + ::sscanf(rest, " %d", &e); current->enabled = e != 0; } } - std::fclose(f); + ::fclose(f); ApplyPartsToRenderer(); SelectLight(light_index); RX_INFO("lookdev: preset loaded from {}", path); @@ -1000,14 +1005,14 @@ void LookdevDemo::Impl::DrawPanel() { if (ImGui::CollapsingHeader("Rig", ImGuiTreeNodeFlags_DefaultOpen)) { int light = light_index; if (ImGui::SliderInt("OLAT light", &light, 0, - static_cast(std::size(kLightStops)) - 1, + static_cast((sizeof(kLightStops) / sizeof(kLightStops[0]))) - 1, kLightStops[light_index].name)) { SelectLight(light); } ImGui::TextDisabled("left/right arrows cycle lights, up/down cycle cameras"); int camera = camera_index; if (ImGui::SliderInt("Camera", &camera, 0, - static_cast(std::size(kCameraStops)) - 1, + static_cast((sizeof(kCameraStops) / sizeof(kCameraStops[0]))) - 1, kCameraStops[camera_index].name)) { SelectCamera(camera); } @@ -1026,9 +1031,9 @@ void LookdevDemo::Impl::DrawPanel() { "preset caps this hardware at tier %d", cap); } const f32 head_px = ImGui::GetIO().DisplaySize.y * 2.0f * subject_radius / - std::max(kCameraStops[camera_index].distance * + rx::Max(kCameraStops[camera_index].distance * (subject_radius / 0.115f) * - std::tan(kCameraStops[camera_index].fov_degrees * kDeg * 0.5f) * + ::tan(kCameraStops[camera_index].fov_degrees * kDeg * 0.5f) * 2.0f, 1e-4f); ImGui::Text("subject height: %.0f px -> tier %d suggested", head_px, @@ -1071,7 +1076,7 @@ void LookdevDemo::Impl::DrawPanel() { cs.exposure_scale = exposure_scale; if (cs.collect_stats) { const Compare::Stats stats = compare.stats(cs.region); - ImGui::Text("rmse %.5f mae %.5f coverage %.0f px", std::sqrt(stats.mean_squared_error), + ImGui::Text("rmse %.5f mae %.5f coverage %.0f px", ::sqrt(stats.mean_squared_error), stats.mean_absolute_error, stats.coverage); } ImGui::SliderFloat("frozen exposure", &exposure_scale, 0.05f, 8.0f, "%.3f", @@ -1092,9 +1097,10 @@ void LookdevDemo::Impl::DrawPanel() { for (Part& part : parts) ReseedShaping(part.params, render::HumanNeutral()); dirty = true; } - ImGui::TextDisabled("history %zu/%zu", history_cursor, history.size()); + ImGui::TextDisabled("history %zu/%zu", history_cursor, + static_cast(history.size())); - for (std::size_t i = 0; i < parts.size(); ++i) { + for (size_t i = 0; i < parts.size(); ++i) { Part& part = parts[i]; ImGui::PushID(static_cast(i)); if (ImGui::TreeNodeEx(part.name.c_str(), @@ -1196,8 +1202,7 @@ void LookdevDemo::Impl::DrawPanel() { capture = Capture{}; capture.running = true; capture.dir = LookdevShots.get() ? LookdevShots.get() : "build/lookdev-shots"; - std::error_code ec; - std::filesystem::create_directories(capture.dir, ec); + fs::CreateDirectories(capture.dir); SelectLight(0); SelectCamera(0); capture.settle = 12; @@ -1228,7 +1233,7 @@ void LookdevDemo::Impl::DrawPanel() { // public -LookdevDemo::LookdevDemo(EngineContext& ctx) : impl_(std::make_unique(ctx)) {} +LookdevDemo::LookdevDemo(EngineContext& ctx) : impl_(base::MakeUnique(ctx)) {} LookdevDemo::~LookdevDemo() = default; void LookdevDemo::Create() { @@ -1280,7 +1285,7 @@ void LookdevDemo::Create() { auto& compare = impl_->ctx.renderer->reference_compare(); if (const char* ref = LookdevReference.get()) { impl_->reference_path = ref; - std::snprintf(impl_->reference_input, sizeof(impl_->reference_input), "%s", ref); + ::snprintf(impl_->reference_input, sizeof(impl_->reference_input), "%s", ref); compare.LoadReference(*impl_->ctx.renderer->device(), impl_->reference_path); compare.settings().mode = Compare::Mode::kWipe; } @@ -1288,8 +1293,7 @@ void LookdevDemo::Create() { if (const char* shots = LookdevShots.get()) { impl_->capture.running = true; impl_->capture.dir = shots; - std::error_code ec; - std::filesystem::create_directories(impl_->capture.dir, ec); + fs::CreateDirectories(impl_->capture.dir); impl_->SelectLight(0); impl_->SelectCamera(0); impl_->capture.settle = 16; @@ -1337,7 +1341,7 @@ void LookdevDemo::Emit(f32 dt, render::FrameView& view) { impl_->PushHistory(); impl_->pending_history = false; } - impl_->history_cooldown = std::max(impl_->history_cooldown - dt, 0.0f); + impl_->history_cooldown = rx::Max(impl_->history_cooldown - dt, 0.0f); impl_->StepFit(); impl_->StepCapture(); diff --git a/runtime/demo_lookdev.h b/runtime/demo_lookdev.h index 70436db6..a4f6c856 100644 --- a/runtime/demo_lookdev.h +++ b/runtime/demo_lookdev.h @@ -1,11 +1,9 @@ #ifndef RX_RUNTIME_DEMO_LOOKDEV_H_ #define RX_RUNTIME_DEMO_LOOKDEV_H_ -#include -#include -#include - #include "asset/material.h" +#include "base/containers/span.h" +#include "base/memory/unique_pointer.h" #include "core/input.h" #include "core/input_actions.h" #include "core/math.h" @@ -72,12 +70,12 @@ class LookdevDemo { f32 distance; // metres from the head centre f32 fov_degrees; }; - static std::span light_stops(); - static std::span camera_stops(); + static base::Span light_stops(); + static base::Span camera_stops(); private: struct Impl; - std::unique_ptr impl_; + base::UniquePointer impl_; }; } // namespace rx diff --git a/runtime/demo_nav.cc b/runtime/demo_nav.cc index 7a52397c..4c408e9a 100644 --- a/runtime/demo_nav.cc +++ b/runtime/demo_nav.cc @@ -1,9 +1,10 @@ #include "demo_nav.h" -#include -#include +#include +#include #include "asset/primitives.h" +#include "base/containers/span.h" #include "core/log.h" #include "nav/nav_debug.h" #include "nav/path.h" @@ -38,7 +39,7 @@ struct Mesa { }; constexpr Mesa kMesas[] = {{18, 74, 7.0f, 5.0f}, {76, 14, 8.0f, 6.0f}}; -f32 RiverCenter(f32 x) { return 48.0f + 7.0f * std::sin(x * 0.07f); } +f32 RiverCenter(f32 x) { return 48.0f + 7.0f * ::sin(x * 0.07f); } constexpr f32 kRiverHalfWidth = 3.2f; f32 Smoothstep(f32 t) { @@ -68,20 +69,20 @@ NavDemo::NavDemo(EngineContext& ctx) NavDemo::Ground NavDemo::SampleGround(f32 x, f32 z) const { Ground g; // Rolling hills, three octaves. - g.height = 2.2f * std::sin(x * 0.06f) * std::cos(z * 0.05f) + - 1.1f * std::sin(x * 0.13f + 1.7f) * std::sin(z * 0.11f + 0.6f) + - 0.4f * std::sin(x * 0.31f) * std::cos(z * 0.29f); + g.height = 2.2f * ::sin(x * 0.06f) * ::cos(z * 0.05f) + + 1.1f * ::sin(x * 0.13f + 1.7f) * ::sin(z * 0.11f + 0.6f) + + 0.4f * ::sin(x * 0.31f) * ::cos(z * 0.29f); // Mesas: flat tops, steep unwalkable flanks (the slope check below). for (const Mesa& mesa : kMesas) { const f32 dx = x - mesa.x; const f32 dz = z - mesa.z; - const f32 d = std::sqrt(dx * dx + dz * dz); + const f32 d = ::sqrt(dx * dx + dz * dz); g.height += mesa.height * Smoothstep((mesa.radius - d) / 3.0f); } // The river carves a bed below the banks. - const f32 river_d = std::fabs(z - RiverCenter(x)); + const f32 river_d = ::fabs(z - RiverCenter(x)); if (river_d < kRiverHalfWidth + 1.5f) { const f32 t = Smoothstep((kRiverHalfWidth + 1.5f - river_d) / (kRiverHalfWidth + 1.5f)); g.height -= 2.0f * t; @@ -136,8 +137,8 @@ void NavDemo::BuildTerrainMesh() { v.tangent[3] = 1; v.uv[0] = x / 8.0f; v.uv[1] = z / 8.0f; - const f32 slope = std::sqrt(hx * hx + hz * hz) / 0.8f; - u32 tone = PackColor(0.13f + 0.04f * std::sin(x * 0.9f) * std::sin(z * 0.77f), 0.34f, + const f32 slope = ::sqrt(hx * hx + hz * hz) / 0.8f; + u32 tone = PackColor(0.13f + 0.04f * ::sin(x * 0.9f) * ::sin(z * 0.77f), 0.34f, 0.10f); // grass, gently mottled if (g.area == kAreaRock) tone = PackColor(0.32f, 0.30f, 0.27f); if (g.area == kAreaWater) tone = PackColor(0.10f, 0.16f, 0.30f); @@ -233,15 +234,15 @@ void NavDemo::BuildTerrainMesh() { for (int i = 0; i < 9; ++i) { const f32 angle = RandRange(rng, 0, 6.2831f); const f32 r = RandRange(rng, 0.3f, field.radius - 0.5f); - const f32 x = field.x + std::cos(angle) * r; - const f32 z = field.z + std::sin(angle) * r; + const f32 x = field.x + ::cos(angle) * r; + const f32 z = field.z + ::sin(angle) * r; ecs::Entity rock = ctx_.world->Create(); scene::Transform t; t.position[0] = x; t.position[1] = SampleGround(x, z).height + 0.05f; t.position[2] = z; - t.rotation[1] = std::sin(angle * 0.5f); - t.rotation[3] = std::cos(angle * 0.5f); + t.rotation[1] = ::sin(angle * 0.5f); + t.rotation[3] = ::cos(angle * 0.5f); t.scale = RandRange(rng, 0.35f, 0.9f); ctx_.world->Add(rock, t); ctx_.world->Add(rock, scene::Renderable{boulder.id}); @@ -308,7 +309,7 @@ void NavDemo::Create() { BuildTerrainMesh(); SpawnActors(); - if (const char* lines_env = std::getenv("RX_NAV_LINES")) { + if (const char* lines_env = ::getenv("RX_NAV_LINES")) { draw_lines_ = lines_env[0] != '0'; } @@ -383,7 +384,7 @@ void NavDemo::Emit(f32 dt, render::FrameView& view) { nav::AppendAgentLines(mesh_, {t.position[0], t.position[1], t.position[2]}, agent, &lines_); }); - view.debug_lines = std::span(lines_.begin(), lines_.size()); + view.debug_lines = base::Span(lines_.begin(), lines_.size()); } } // namespace rx diff --git a/runtime/demo_placement.cc b/runtime/demo_placement.cc index c6f577d3..d2f44427 100644 --- a/runtime/demo_placement.cc +++ b/runtime/demo_placement.cc @@ -1,10 +1,13 @@ #include "demo_placement.h" -#include -#include +#include +#include #include "asset/primitives.h" +#include "base/containers/span.h" +#include "base/memory/unique_pointer.h" #include "core/log.h" +#include "core/scalar.h" #include "placement/placement_math.h" #include "placement_demo_assets.h" #include "scene/components.h" @@ -21,7 +24,7 @@ constexpr u32 kWorldResolution = 512; constexpr u32 kTerrainGrid = 384; // The road winds north-south through the world. -f32 RoadCenter(f32 z) { return 90.0f * std::sin(z * 0.004f) + 30.0f * std::sin(z * 0.011f); } +f32 RoadCenter(f32 z) { return 90.0f * ::sin(z * 0.004f) + 30.0f * ::sin(z * 0.011f); } constexpr f32 kRoadHalfWidth = 4.5f; // The lake basin carved into the height field. @@ -51,9 +54,9 @@ f32 Fbm(f32 x, f32 z, f32 base_size, u32 seed, u32 octaves) { PlacementDemo::PlacementDemo(EngineContext& ctx) : ctx_(ctx), world_(kWorldOrigin, kWorldOrigin, kWorldExtent, kWorldResolution) { - const char* lines = std::getenv("RX_PLACEMENT_LINES"); + const char* lines = ::getenv("RX_PLACEMENT_LINES"); draw_lines_ = lines && lines[0] == '1'; - const char* fly = std::getenv("RX_PLACEMENT_FLY"); + const char* fly = ::getenv("RX_PLACEMENT_FLY"); autopilot_ = fly && fly[0] == '1'; } @@ -63,13 +66,13 @@ f32 PlacementDemo::TerrainHeight(f32 x, f32 z) const { // Rolling hills with a broad ridge, softened toward the road and carved // into a basin around the lake. f32 height = 10.0f * Fbm(x, z, 420.0f, 101u, 4) + 3.0f * Fbm(x, z, 90.0f, 202u, 3); - f32 road_dist = std::fabs(x - RoadCenter(z)); - f32 road_blend = std::min(road_dist / 22.0f, 1.0f); + f32 road_dist = ::fabs(x - RoadCenter(z)); + f32 road_blend = rx::Min(road_dist / 22.0f, 1.0f); road_blend = road_blend * road_blend * (3.0f - 2.0f * road_blend); f32 dx = x - kLakeX; f32 dz = z - kLakeZ; - f32 lake_dist = std::sqrt(dx * dx + dz * dz); - f32 basin = std::min(lake_dist / kLakeRadius, 1.0f); + f32 lake_dist = ::sqrt(dx * dx + dz * dz); + f32 basin = rx::Min(lake_dist / kLakeRadius, 1.0f); basin = basin * basin * (3.0f - 2.0f * basin); height = height * (0.35f + 0.65f * road_blend); // road hugs gentler ground height = height * basin + (kWaterLevel - 4.5f) * (1.0f - basin); @@ -90,12 +93,12 @@ void PlacementDemo::BuildWorldData() { f32 base = placement::ValueNoise(x, z, 340.0f, 11u); f32 detail = placement::ValueNoise(x, z, 130.0f, 12u); f32 v = base * 0.7f + detail * 0.3f; - f32 t = std::clamp((v - 0.38f) / 0.34f, 0.0f, 1.0f); + f32 t = rx::Clamp((v - 0.38f) / 0.34f, 0.0f, 1.0f); return t * t * (3.0f - 2.0f * t); }); world_.Generate(map_road_, [](f32 x, f32 z) { - f32 d = std::fabs(x - RoadCenter(z)); - return std::clamp(1.0f - (d - kRoadHalfWidth) / 4.0f, 0.0f, 1.0f); + f32 d = ::fabs(x - RoadCenter(z)); + return rx::Clamp(1.0f - (d - kRoadHalfWidth) / 4.0f, 0.0f, 1.0f); }); world_.Generate(map_water_, [this](f32 x, f32 z) { return TerrainHeight(x, z) < kWaterLevel + 0.4f ? 1.0f : 0.0f; @@ -113,7 +116,7 @@ void PlacementDemo::BuildEcotopes() { placement::PlacementConfig config; config.radius_tiles = 3; - system_ = std::make_unique(&world_, map_height_, config); + system_ = base::MakeUnique(&world_, map_height_, config); auto exclude = [this](DensityProgram& p) { p.Map(map_road_).OneMinus().Mul().Map(map_water_).OneMinus().Mul(); @@ -259,7 +262,7 @@ void PlacementDemo::BuildTerrainMesh() { const f32 dhz = world_.Sample(map_height_, x, z + h) - world_.Sample(map_height_, x, z - h); const f32 inv = - 1.0f / std::sqrt(dhx * dhx + dhz * dhz + 4.0f * h * h); + 1.0f / ::sqrt(dhx * dhx + dhz * dhz + 4.0f * h * h); v.normal[0] = -dhx * inv; v.normal[1] = 2.0f * h * inv; v.normal[2] = -dhz * inv; @@ -428,12 +431,12 @@ void PlacementDemo::Create() { if (system_->pending().empty()) break; gpu_.GenerateImmediate(*ctx_.renderer->device(), *system_, system_->pending(), initial); } - ApplyResults({initial.data(), initial.size()}); + ApplyResults(base::Span(initial.data(), initial.size())); RX_INFO("placement demo: {} instances across {} tiles at spawn", initial.size(), live_.size()); } -void PlacementDemo::ApplyResults(std::span instances) { +void PlacementDemo::ApplyResults(base::Span instances) { if (instances.empty()) return; // Bucket by (layer, tile) and build one instance group per bucket; groups // attach to their tile so eviction can retire them. @@ -465,7 +468,7 @@ void PlacementDemo::ApplyResults(std::span inst placement::TileKey key{layer_stacks_[bucket.layer], bucket.tile_x, bucket.tile_z}; render::InstanceGroupHandle group = ctx_.renderer->CreateInstanceGroup( layer_meshes_[bucket.layer], - {bucket.transforms.data(), bucket.transforms.size()}); + base::Span(bucket.transforms.data(), bucket.transforms.size())); if (!group) { RX_WARN("placement demo: instance group rejected (layer {}, {} instances)", bucket.layer, bucket.transforms.size()); @@ -514,7 +517,7 @@ void PlacementDemo::Emit(f32 dt, render::FrameView& view) { // Instance groups harvested by last frame's hook become renderer state // here, outside command recording. if (!harvested_tiles_.empty() || !harvested_.empty()) { - ApplyResults({harvested_.data(), harvested_.size()}); + ApplyResults(base::Span(harvested_.data(), harvested_.size())); harvested_.clear(); harvested_tiles_.clear(); } @@ -547,7 +550,7 @@ void PlacementDemo::Emit(f32 dt, render::FrameView& view) { {origin.x, y, origin.z + size}, color}); lines_.push_back({{origin.x, y, origin.z + size}, {origin.x, y, origin.z}, color}); } - view.debug_lines_overlay = {lines_.data(), lines_.size()}; + view.debug_lines_overlay = base::Span(lines_.data(), lines_.size()); } } diff --git a/runtime/demo_placement.h b/runtime/demo_placement.h index ddaf641b..a5ccf8a4 100644 --- a/runtime/demo_placement.h +++ b/runtime/demo_placement.h @@ -3,6 +3,8 @@ #include +#include "base/containers/span.h" +#include "base/memory/unique_pointer.h" #include "core/math.h" #include "engine_context.h" #include "placement/gpu_placement.h" @@ -44,12 +46,12 @@ class PlacementDemo { void BuildWorldData(); void BuildEcotopes(); void BuildTerrainMesh(); - void ApplyResults(std::span instances); + void ApplyResults(base::Span instances); void DestroyTileGroups(const placement::TileKey& key); EngineContext& ctx_; placement::WorldData world_; - std::unique_ptr system_; + base::UniquePointer system_; placement::GpuPlacement gpu_; bool gpu_ready_ = false; diff --git a/runtime/demo_puppet.cc b/runtime/demo_puppet.cc index d37abc07..a0f4fb6a 100644 --- a/runtime/demo_puppet.cc +++ b/runtime/demo_puppet.cc @@ -1,12 +1,13 @@ #include "demo_puppet.h" -#include -#include -#include +#include #include "asset/asset_id.h" #include "asset/primitives.h" +#include "base/containers/span.h" +#include "core/format.h" #include "core/log.h" +#include "core/scalar.h" #include "scene/components.h" namespace rx { @@ -60,7 +61,7 @@ void PushRing(base::Vector& out, const Vec3& p, f32 r, u32 rg Vec3 prev{p.x + r, p.y, p.z}; for (u32 i = 1; i <= segs; ++i) { const f32 a = 6.2831853f * static_cast(i) / static_cast(segs); - Vec3 cur{p.x + std::cos(a) * r, p.y, p.z + std::sin(a) * r}; + Vec3 cur{p.x + ::cos(a) * r, p.y, p.z + ::sin(a) * r}; PushLine(out, prev, cur, rgba); prev = cur; } @@ -70,7 +71,7 @@ void PushRing(base::Vector& out, const Vec3& p, f32 r, u32 rg void PushArrow(base::Vector& out, const Vec3& from, const Vec3& dir, u32 rgba) { const Vec3 tip{from.x + dir.x, from.y + dir.y, from.z + dir.z}; PushLine(out, from, tip, rgba); - const f32 len = std::sqrt(dir.x * dir.x + dir.z * dir.z); + const f32 len = ::sqrt(dir.x * dir.x + dir.z * dir.z); if (len < 1e-3f) return; const Vec3 back{-dir.x / len, 0, -dir.z / len}; const Vec3 side{back.z, 0, -back.x}; // perpendicular in the ground plane @@ -100,7 +101,7 @@ void PuppetDemo::BuildArena() { // collider. `rot` is the render/physics rotation quaternion (xyzw). auto solid = [&](const Vec3& center, f32 hx, f32 hy, f32 hz, const f32 rot[4], u32 tint) { asset::Mesh box = - asset::MakeBox(hx, hy, hz, asset::MakeAssetId("puppet/box_" + std::to_string(tag++))); + asset::MakeBox(hx, hy, hz, asset::MakeAssetId("puppet/box_" + rx::ToString(tag++))); box.lods[0].submeshes.push_back({0, static_cast(box.lods[0].indices.size()), mat.id}); if (!ctx_.config->headless) ctx_.renderer->UploadMesh(box); ecs::Entity e = ctx_.world->Create(); @@ -125,7 +126,7 @@ void PuppetDemo::BuildArena() { // A shallow ~15 deg ramp off to the +X/+Z corner (scenery + a slope to probe). const f32 ramp_deg = 15.0f * 3.14159265f / 180.0f; - const f32 rh = std::sin(ramp_deg * 0.5f), rw = std::cos(ramp_deg * 0.5f); + const f32 rh = ::sin(ramp_deg * 0.5f), rw = ::cos(ramp_deg * 0.5f); const f32 ramp_rot[4] = {rh, 0, 0, rw}; // tilt about +X solid({3.0f, 0.35f, 3.0f}, 1.6f, 0.08f, 2.0f, ramp_rot, 0x45483f); @@ -187,7 +188,7 @@ void PuppetDemo::BuildProxies() { const BodyPart part = static_cast(i); const Vec3 e = ext(part); asset::Mesh box = - asset::MakeBox(e.x, e.y, e.z, asset::MakeAssetId("puppet/proxy_" + std::to_string(i))); + asset::MakeBox(e.x, e.y, e.z, asset::MakeAssetId("puppet/proxy_" + rx::ToString(i))); box.lods[0].submeshes.push_back({0, static_cast(box.lods[0].indices.size()), mat.id}); if (!ctx_.config->headless) ctx_.renderer->UploadMesh(box); proxy_[i] = ctx_.world->Create(); @@ -242,8 +243,8 @@ locomotion::LocomotionIntent PuppetDemo::ScriptedIntent() const { } else if (t < 17.0f) { // Turn gradually from -Z toward -X over 3 s (an abrupt heading snap while // walking tips this slow ragdoll over); velocity follows the heading. - const f32 phi = std::min((t - 11.0f) / 3.0f, 1.0f) * 1.5707963f; - const Vec3 dir{-std::sin(phi), 0, -std::cos(phi)}; + const f32 phi = rx::Min((t - 11.0f) / 3.0f, 1.0f) * 1.5707963f; + const Vec3 dir{-::sin(phi), 0, -::cos(phi)}; intent.desired_facing = dir; intent.desired_velocity = {dir.x * kSpeed, 0, dir.z * kSpeed}; } else { @@ -268,7 +269,7 @@ void PuppetDemo::Step(f32 dt) { pending_small_push_ = pending_big_push_ = false; rng_ = rng_ * 1664525u + 1013904223u; const f32 a = 6.2831853f * (static_cast((rng_ >> 8) & 0xffff) / 65535.0f); - const Vec3 impulse{std::cos(a) * mag, 0, std::sin(a) * mag}; + const Vec3 impulse{::cos(a) * mag, 0, ::sin(a) * mag}; ctx_.physics->ApplyImpulse(controller_.rig().body[static_cast(BodyPart::kTorso)], impulse); RX_INFO("puppet demo: push {} kg*m/s", mag); } @@ -385,7 +386,7 @@ void PuppetDemo::EmitDebugLines(render::FrameView& view) { } PushLine(lines_, com, {com.x, ground_y, com.z}, PackColor(0.55f, 0.55f, 0.55f)); - view.debug_lines = std::span(lines_.begin(), lines_.size()); + view.debug_lines = base::Span(lines_.begin(), lines_.size()); } } // namespace rx diff --git a/runtime/demo_scenes.cc b/runtime/demo_scenes.cc index 1780b64e..26c85272 100644 --- a/runtime/demo_scenes.cc +++ b/runtime/demo_scenes.cc @@ -1,13 +1,8 @@ #include "demo_scenes.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include #include @@ -17,8 +12,16 @@ #include "audio/thunder_synth.h" #include "asset/materialx.h" #include "asset/primitives.h" +#include "base/algorithm.h" +#include "base/containers/span.h" +#include "base/containers/vector.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" +#include "core/format.h" #include "core/log.h" #include "core/math.h" +#include "core/scalar.h" #include "physics/water_waves.h" #include "render/atmosphere/lightning.h" #include "render/geometry/hair_groom.h" @@ -66,10 +69,10 @@ void DemoScenes::CreateSceneHookDemoScene() { camera_.speed = 4.0f; if (!config_.headless) { - scene_hook_ = std::make_unique(); + scene_hook_ = base::MakeUnique(); if (!scene_hook_->Init(renderer_)) { RX_WARN("scenehook demo unavailable; showing the rx geometry only"); - scene_hook_.reset(); + scene_hook_.Reset(); } } } @@ -96,10 +99,10 @@ void DemoScenes::CreateSceneHookRhiDemoScene() { camera_.speed = 4.0f; if (!config_.headless) { - scene_hook_rhi_ = std::make_unique(); + scene_hook_rhi_ = base::MakeUnique(); if (!scene_hook_rhi_->Init(renderer_)) { RX_WARN("scenehook-rhi demo unavailable; showing the rx geometry only"); - scene_hook_rhi_.reset(); + scene_hook_rhi_.Reset(); } } } @@ -166,8 +169,8 @@ void DemoScenes::CreateBubbleDemoScene() { world.Each( [dt](ecs::Entity, BubbleAgent& agent, scene::Transform& t) { agent.time += dt; - t.position[0] = std::sin(agent.time * agent.rate_x) * agent.extent; - t.position[2] = std::cos(agent.time * agent.rate_z) * agent.extent; + t.position[0] = ::sin(agent.time * agent.rate_x) * agent.extent; + t.position[2] = ::cos(agent.time * agent.rate_z) * agent.extent; }); }); @@ -179,10 +182,10 @@ void DemoScenes::CreateBubbleDemoScene() { camera_.speed = 10.0f; if (!config_.headless) { - bubble_viz_ = std::make_unique(); + bubble_viz_ = base::MakeUnique(); if (!bubble_viz_->Init(renderer_)) { RX_WARN("bubble visualizer unavailable; tinting only"); - bubble_viz_.reset(); + bubble_viz_.Reset(); } } RX_INFO("bubbles demo: 3 wandering players, 169 replicated pawns, RX_NET_BUBBLES=0 hides the spheres"); @@ -202,9 +205,9 @@ void DemoScenes::EmitBubbles(render::FrameView& view) { void DemoScenes::Shutdown() { if (placement_) { placement_->Shutdown(); - placement_.reset(); + placement_.Reset(); } - grass_.reset(); + grass_.Reset(); if (cloth_ != 0) { physics_.RemoveCloth(cloth_); cloth_ = 0; @@ -212,14 +215,14 @@ void DemoScenes::Shutdown() { // Retire service-owning demos while the host's physics and audio systems are // still alive. main.cc destroys Host before Viewer, so leaving these until the // DemoScenes destructor would make their teardown call through dead services. - puppet_.reset(); - drive_.reset(); - gym_.reset(); - shooter_.reset(); - feature_gym_.reset(); - scene_hook_.reset(); - scene_hook_rhi_.reset(); - bubble_viz_.reset(); + puppet_.Reset(); + drive_.Reset(); + gym_.Reset(); + shooter_.Reset(); + feature_gym_.Reset(); + scene_hook_.Reset(); + scene_hook_rhi_.Reset(); + bubble_viz_.Reset(); } bool DemoScenes::BuildFeatureGymTour(ShowcaseCamera& camera) { @@ -245,7 +248,7 @@ void DemoScenes::ApplyRenderPolicy() { settings.weather = weather_demo_weather_; settings.cloud_coverage = 0.68f; settings.cloudscape_controls = weather_demo_controls_; - settings.sun_intensity = std::min(settings.sun_intensity, 1.3f); + settings.sun_intensity = rx::Min(settings.sun_intensity, 1.3f); settings.ibl_intensity = 0.4f; settings.ddgi = false; settings.ssgi = false; @@ -283,10 +286,10 @@ void DemoScenes::EmitToView(f32 dt, render::FrameView& view) { // keeps driving so RX_GAME_HOUR still turns the demo to night). The deck's // gloom is a cap on the direct light, not an absolute, so re-clamp per frame. if (weather_scene_) { - renderer_.settings().sun_intensity = std::min(renderer_.settings().sun_intensity, 1.3f); + renderer_.settings().sun_intensity = rx::Min(renderer_.settings().sun_intensity, 1.3f); const render::WeatherSettings &weather = renderer_.settings().weather; weather_map_offset_ += - Vec2{std::cos(weather.wind_yaw), std::sin(weather.wind_yaw)} * (weather.wind_speed * dt); + Vec2{::cos(weather.wind_yaw), ::sin(weather.wind_yaw)} * (weather.wind_speed * dt); renderer_.settings().cloudscape_controls.map_offset = weather_map_offset_; } if (storm_enabled_) UpdateStorm(dt); @@ -323,8 +326,8 @@ void DemoScenes::EmitToView(f32 dt, render::FrameView& view) { // path traces real shadows from it). fire_time_ += dt; f32 t = fire_time_; - f32 flicker = 0.82f + 0.12f * std::sin(t * 11.7f) + 0.06f * std::sin(t * 23.3f + 1.7f) + - 0.05f * std::sin(t * 5.1f + 0.6f); + f32 flicker = 0.82f + 0.12f * ::sin(t * 11.7f) + 0.06f * ::sin(t * 23.3f + 1.7f) + + 0.05f * ::sin(t * 5.1f + 0.6f); render::PointLight l; l.pos_radius[0] = gpu_particle_emitter_.x; l.pos_radius[1] = gpu_particle_emitter_.y + 0.55f; @@ -352,17 +355,17 @@ void DemoScenes::EmitToView(f32 dt, render::FrameView& view) { // and lags as the head bobs. if (!hair_sims_.empty()) { hair_time_ += dt; - Vec3 wind = Vec3{0.5f, 0.0f, 0.25f} * (0.6f + 0.4f * std::sin(hair_time_ * 2.1f)) + - Vec3{0.0f, 0.15f * std::sin(hair_time_ * 3.7f), 0.0f}; + Vec3 wind = Vec3{0.5f, 0.0f, 0.25f} * (0.6f + 0.4f * ::sin(hair_time_ * 2.1f)) + + Vec3{0.0f, 0.15f * ::sin(hair_time_ * 3.7f), 0.0f}; for (physics::StrandGroomId sim : hair_sims_) physics_.SetStrandGroomWind(sim, wind); if (hair_orbit_strands_ != 0) { f32 a = hair_time_ * 0.9f; // Mostly a head turn (rotation keeps the head under the hair) plus a // small bob, so the hair sways without exposing the scalp. - Vec3 pos{hair_orbit_center_.x + 0.03f * std::sin(a), - hair_orbit_center_.y + 0.02f * std::sin(a * 1.7f), hair_orbit_center_.z}; + Vec3 pos{hair_orbit_center_.x + 0.03f * ::sin(a), + hair_orbit_center_.y + 0.02f * ::sin(a * 1.7f), hair_orbit_center_.z}; Mat4 m = - MakeTranslation(pos) * MakeFromQuat(QuatFromAxisAngle({0, 1, 0}, 0.8f * std::sin(a))); + MakeTranslation(pos) * MakeFromQuat(QuatFromAxisAngle({0, 1, 0}, 0.8f * ::sin(a))); physics_.SetStrandGroomTransform(hair_orbit_strands_, m, dt); renderer_.SetHairGroomTransform(hair_orbit_groom_, m); } @@ -374,7 +377,7 @@ void DemoScenes::EmitToView(f32 dt, render::FrameView& view) { // floaters on its own, with no CPU-fed foam confusing the picture. if (!water_cubes_.empty()) { water_time_ += dt; // wave clock for the Gerstner buoyancy proxy - const char* disturb = std::getenv("RX_WATER_DISTURB"); + const char* disturb = ::getenv("RX_WATER_DISTURB"); if (!(disturb && disturb[0] == '0')) EmitWaterDisturbances(dt, view); } } @@ -384,14 +387,14 @@ void DemoScenes::EmitWaterDisturbances(f32 dt, render::FrameView& view) { // hard it moves; a still cube leaves only a faint standing ripple. A moving // cube stretches its splat into a directional wake (elongation), and a cube // slamming into the surface throws a one-shot splash (foam pulse + spray). - const f32 inv_dt = 1.0f / std::max(dt, 1e-4f); + const f32 inv_dt = 1.0f / rx::Max(dt, 1e-4f); for (u32 i = 0; i < water_cubes_.size(); ++i) { Vec3 pos; f32 rot[4]; if (!physics_.GetBodyTransform(water_cubes_[i], &pos, rot)) continue; Vec3 vel = (pos - water_cube_prev_[i]) * inv_dt; water_cube_prev_[i] = pos; - water_cube_slam_cd_[i] = std::max(water_cube_slam_cd_[i] - dt, 0.0f); + water_cube_slam_cd_[i] = rx::Max(water_cube_slam_cd_[i] - dt, 0.0f); // Local wave surface (height + its vertical velocity) under the cube, from // the same Gerstner proxy the buoyancy rides, so slam is measured against @@ -399,20 +402,20 @@ void DemoScenes::EmitWaterDisturbances(f32 dt, render::FrameView& view) { f32 surface_vy = 0.0f; f32 surface = physics::GerstnerWaveHeight(pos.x, pos.z, water_time_, nullptr, &surface_vy); - f32 horiz = std::sqrt(vel.x * vel.x + vel.z * vel.z); - f32 vert = std::fabs(vel.y); + f32 horiz = ::sqrt(vel.x * vel.x + vel.z * vel.z); + f32 vert = ::fabs(vel.y); f32 motion = horiz + vert; render::WaterDisturbance d; d.position = {pos.x, 0.0f, pos.z}; d.radius = 2.2f; // Rates (per second); the field integrates them over the foam time constant. - d.ripple_strength = std::min(motion * 0.4f, 2.0f); - d.foam_amount = std::min(std::max(motion - 0.2f, 0.0f) * 0.7f, 1.8f); + d.ripple_strength = rx::Min(motion * 0.4f, 2.0f); + d.foam_amount = rx::Min(rx::Max(motion - 0.2f, 0.0f) * 0.7f, 1.8f); d.velocity_x = vel.x; d.velocity_z = vel.z; // Faster horizontal motion stretches the wake down-track; cubes carry no // meaningful yaw, so no turn skew (a ship would pass its hull yaw rate). - d.elongation = std::min(horiz * 0.35f, 2.5f); + d.elongation = rx::Min(horiz * 0.35f, 2.5f); d.angular_velocity = 0.0f; view.water_disturbances.push_back(d); @@ -423,15 +426,15 @@ void DemoScenes::EmitWaterDisturbances(f32 dt, render::FrameView& view) { const f32 rel_vy = vel.y - surface_vy; const f32 kSlamSpeed = 2.5f; // m/s of downward closing speed const f32 kWaterlineBand = 1.4f; // cube half-height + margin - if (rel_vy < -kSlamSpeed && std::fabs(pos.y - surface) < kWaterlineBand && + if (rel_vy < -kSlamSpeed && ::fabs(pos.y - surface) < kWaterlineBand && water_cube_slam_cd_[i] <= 0.0f) { water_cube_slam_cd_[i] = 0.6f; - f32 slam = std::min(-rel_vy, 12.0f); + f32 slam = rx::Min(-rel_vy, 12.0f); render::WaterDisturbance s; s.position = {pos.x, 0.0f, pos.z}; s.radius = 3.4f; // splashes ring wider than the wake s.ripple_strength = slam * 0.9f; // sharp pressure pulse - s.foam_amount = std::min(1.5f + slam * 0.25f, 4.0f); // big whitewater burst + s.foam_amount = rx::Min(1.5f + slam * 0.25f, 4.0f); // big whitewater burst s.velocity_x = 0.0f; // radial: an impact has no heading s.velocity_z = 0.0f; s.elongation = 0.0f; @@ -460,8 +463,8 @@ void DemoScenes::SpawnSplashSpray(const Vec3& pos, f32 surface, f32 strength) { p.position = {pos.x + (rnd() - 0.5f) * 1.6f, surface + 0.1f, pos.z + (rnd() - 0.5f) * 1.6f}; f32 ang = rnd() * 6.2831853f; f32 spread = 0.6f + rnd() * (0.5f + 0.15f * strength); - p.velocity = {std::cos(ang) * spread, 2.0f + rnd() * (1.0f + 0.25f * strength), - std::sin(ang) * spread}; + p.velocity = {::cos(ang) * spread, 2.0f + rnd() * (1.0f + 0.25f * strength), + ::sin(ang) * spread}; p.max_life = 0.5f + rnd() * 0.4f; p.life = p.max_life; p.size = 0.03f + rnd() * 0.03f; @@ -481,7 +484,7 @@ struct Spin { // Hermite smoothstep. Robust to reversed edges (e0 > e1), so a feature can ramp // in either direction of z below. inline f32 SmoothStep(f32 e0, f32 e1, f32 x) { - f32 t = std::clamp((x - e0) / (e1 - e0), 0.0f, 1.0f); + f32 t = rx::Clamp((x - e0) / (e1 - e0), 0.0f, 1.0f); return t * t * (3.0f - 2.0f * t); } @@ -495,7 +498,7 @@ f32 FluidDemoBed(f32 x, f32 z) { // Lower basin is the datum (~0). Everything is built up from here. // Domain-edge rim: the outer ~12 m rises to ~2.5 m on every side so the flood // can't visibly spill out of the 128 m domain. - f32 edge = std::max(std::fabs(x), std::fabs(z)); + f32 edge = rx::Max(::fabs(x), ::fabs(z)); f32 h = 2.5f * SmoothStep(52.0f, 64.0f, edge); // Upper reservoir plateau (~6 m), gated to the central x band so it walls the @@ -504,29 +507,29 @@ f32 FluidDemoBed(f32 x, f32 z) { // channel throat stays above the 5.2 m fill level; a longer ramp (the first // cut used -12..-24) leaves a ~4 m shoulder the reservoir quietly drains // around, dam or no dam. - f32 plateau_x = 1.0f - SmoothStep(22.0f, 32.0f, std::fabs(x)); + f32 plateau_x = 1.0f - SmoothStep(22.0f, 32.0f, ::fabs(x)); f32 plateau = 6.0f * SmoothStep(-13.0f, -19.0f, z) * plateau_x; // Carve the reservoir bowl into the plateau: floor ~3.5 m, centred (0,-40), // radius ~18 m (a smooth radial depression). - f32 rb = std::sqrt(x * x + (z + 40.0f) * (z + 40.0f)); + f32 rb = ::sqrt(x * x + (z + 40.0f) * (z + 40.0f)); f32 bowl = 2.5f * (1.0f - SmoothStep(6.0f, 18.0f, rb)); - h = std::max(h, plateau - bowl); + h = rx::Max(h, plateau - bowl); // Channel notch: the reservoir's only outlet. A 10 m gap at x in [-5,5] whose // floor descends 3.5 m -> 0.5 m as z runs -24 -> -12, min-blended through the // plateau lip (1 m feather in x, ramps in/out over ~1 m in z). f32 channel_floor = 3.5f - 3.0f * SmoothStep(-24.0f, -12.0f, z); - f32 cmask = (1.0f - SmoothStep(5.0f, 6.0f, std::fabs(x))) * + f32 cmask = (1.0f - SmoothStep(5.0f, 6.0f, ::fabs(x))) * SmoothStep(-25.0f, -24.0f, z) * (1.0f - SmoothStep(-13.0f, -12.0f, z)); - h -= std::max(0.0f, h - channel_floor) * cmask; + h -= rx::Max(0.0f, h - channel_floor) * cmask; // Lava hill on the +x side: a smooth ridge ~8 m peaking near (35,-30). Its +z // flank runs ~30+ m downhill into the lower basin (slope ~15 deg), so the vent // near its top feeds a flow that runs, slows and solidifies before pooling. f32 hx = x - 35.0f, hz = z + 30.0f; - f32 hill = 8.0f * std::exp(-(hx * hx + hz * hz) / (2.0f * 12.0f * 12.0f)); - h = std::max(h, hill); + f32 hill = 8.0f * ::exp(-(hx * hx + hz * hz) / (2.0f * 12.0f * 12.0f)); + h = rx::Max(h, hill); return h; } @@ -621,7 +624,7 @@ void DemoScenes::CreateWaterDemoScene() { for (int i = 0; i < 6; ++i) { ecs::Entity block = world_.Create(); f32 angle = static_cast(i) * 1.047f; - Vec3 position{std::cos(angle) * 6.0f, 2.0f + (i % 3), std::sin(angle) * 6.0f}; + Vec3 position{::cos(angle) * 6.0f, 2.0f + (i % 3), ::sin(angle) * 6.0f}; world_.Add(block, scene::Transform{.position = {position.x, position.y, position.z}}); world_.Add(block, scene::Renderable{cube.id}); // Density 450 kg/m^3 against the 1.2x buoyancy factor settles the cubes at @@ -684,7 +687,7 @@ void DemoScenes::CreateWaterDemoScene() { for (u32 gx = 0; gx <= kIslandGrid; ++gx) { f32 lx = -kRadius + 2.0f * kRadius * static_cast(gx) / kIslandGrid; f32 lz = -kRadius + 2.0f * kRadius * static_cast(gy) / kIslandGrid; - f32 g = std::exp(-(lx * lx + lz * lz) / (2.0f * kSigma * kSigma)); + f32 g = ::exp(-(lx * lx + lz * lz) / (2.0f * kSigma * kSigma)); f32 slope = kPeak * 2.0f * g / (kSigma * kSigma); // -dh/dr factor Vec3 n = Normalize(Vec3{slope * lx, 1.0f, slope * lz}); asset::Vertex v{}; @@ -727,7 +730,7 @@ void DemoScenes::CreateWaterDemoScene() { // Turn the wetting field on and point it at this beach. // RX_SHORE_WETTING=0 must survive as a kill switch for A/B captures even // though this scene opts in. - const char* shore_env = std::getenv("RX_SHORE_WETTING"); + const char* shore_env = ::getenv("RX_SHORE_WETTING"); renderer_.settings().shore_wetting = !(shore_env && shore_env[0] == '0'); renderer_.settings().shore_island[0] = kIslandCenterX; renderer_.settings().shore_island[1] = kIslandCenterZ; @@ -756,9 +759,9 @@ void DemoScenes::RebuildFluidBed() { // The wall's flat top must run past the notch shoulders (bed ~5.2-5.9 m // at |x| 5.5-6.5): the startup slosh piles water ~1 m above the rest // level and spills over anything lower than ~6.5 m there. - f32 dmx = 1.0f - SmoothStep(7.0f, 8.5f, std::fabs(x)); + f32 dmx = 1.0f - SmoothStep(7.0f, 8.5f, ::fabs(x)); f32 dmz = SmoothStep(-18.5f, -17.5f, z) * (1.0f - SmoothStep(-14.5f, -13.5f, z)); - h = std::max(h, 8.5f * dmx * dmz); + h = rx::Max(h, 8.5f * dmx * dmz); } fluid_bed_[static_cast(j) * res + i] = h; } @@ -791,8 +794,8 @@ void DemoScenes::CreateFluidDemoScene() { f32 z = fluid_domain_.origin[1] + (static_cast(j) + 0.5f) * l; for (u32 i = 0; i < res; ++i) { f32 x = fluid_domain_.origin[0] + (static_cast(i) + 0.5f) * l; - f32 rb = std::sqrt(x * x + (z + 40.0f) * (z + 40.0f)); - bool in_reservoir = rb < 19.0f || (std::fabs(x) < 6.0f && z < -18.0f && z > -25.0f); + f32 rb = ::sqrt(x * x + (z + 40.0f) * (z + 40.0f)); + bool in_reservoir = rb < 19.0f || (::fabs(x) < 6.0f && z < -18.0f && z > -25.0f); if (!in_reservoir) continue; f32 depth = 5.2f - FluidDemoBed(x, z); if (depth > 0.0f) fluid_initial_water_[static_cast(j) * res + i] = depth; @@ -826,8 +829,8 @@ void DemoScenes::CreateFluidDemoScene() { for (u32 gx = 0; gx <= kGrid; ++gx) { f32 x = -kSkirt + 2.0f * kSkirt * static_cast(gx) / kGrid; f32 z = -kSkirt + 2.0f * kSkirt * static_cast(gy) / kGrid; - f32 sx = std::clamp(x, -64.0f, 64.0f); // skirt clamps to the domain edge - f32 sz = std::clamp(z, -64.0f, 64.0f); + f32 sx = rx::Clamp(x, -64.0f, 64.0f); // skirt clamps to the domain edge + f32 sz = rx::Clamp(z, -64.0f, 64.0f); f32 y = FluidDemoBed(sx, sz); Vec3 n = Normalize(Vec3{FluidDemoBed(sx - kEps, sz) - FluidDemoBed(sx + kEps, sz), 2.0f * kEps, @@ -924,9 +927,9 @@ void DemoScenes::EmitFluid(f32 dt, render::FrameView& view) { // bump bed_version so the solver re-uploads, and start sinking the boxes. if (dam_up_) { bool trigger = false; - const char* frame_env = std::getenv("RX_FLUID_DAM_FRAME"); + const char* frame_env = ::getenv("RX_FLUID_DAM_FRAME"); if (frame_env && frame_env[0]) { - trigger = fluid_frame_ >= static_cast(std::strtoull(frame_env, nullptr, 10)); + trigger = fluid_frame_ >= static_cast(::strtoull(frame_env, nullptr, 10)); } else { trigger = fluid_time_ >= 10.0f; } @@ -956,7 +959,7 @@ void DemoScenes::EmitFluid(f32 dt, render::FrameView& view) { render::FluidSource lava; lava.position = {35.0f, 0.0f, -30.0f}; lava.radius = 2.5f; - lava.rate = 0.35f * (1.0f + 0.3f * std::sin(fluid_time_ * 0.7f)); + lava.rate = 0.35f * (1.0f + 0.3f * ::sin(fluid_time_ * 0.7f)); lava.fluid = 1; lava.temperature = 1250.0f; view.fluid_sources.push_back(lava); @@ -979,7 +982,7 @@ void DemoScenes::CreateMaterialDemoScene() { int counter = 0; auto spawn = [&](Vec3 pos, asset::Material mat) { - std::string tag = "builtin/matdemo/" + std::to_string(counter++); + base::String tag = "builtin/matdemo/" + rx::ToString(counter++); mat.id = asset::MakeAssetId(tag + "/mat"); asset::Mesh sphere = asset::MakeSphere(0.5f, 32, 48, asset::MakeAssetId(tag + "/mesh")); sphere.lods[0].submeshes[0].material = mat.id; @@ -1092,7 +1095,7 @@ void DemoScenes::UpdateParticles(f32 dt, render::FrameView& view) { p.position = particle_emitter_; f32 ang = rnd() * 6.2831853f; f32 spread = rnd() * 0.65f; - p.velocity = {std::cos(ang) * spread, 2.8f + rnd() * 1.4f, std::sin(ang) * spread}; + p.velocity = {::cos(ang) * spread, 2.8f + rnd() * 1.4f, ::sin(ang) * spread}; p.max_life = 0.8f + rnd() * 0.6f; p.life = p.max_life; p.size = 0.02f + rnd() * 0.02f; @@ -1160,9 +1163,9 @@ void DemoScenes::CreateGaussianDemoScene() { for (u32 i = 0; i < kCount; ++i) { f32 t = (static_cast(i) + 0.5f) / static_cast(kCount); f32 y = 1.0f - 2.0f * t; - f32 r = std::sqrt(std::max(0.0f, 1.0f - y * y)); + f32 r = ::sqrt(rx::Max(0.0f, 1.0f - y * y)); f32 phi = static_cast(i) * golden; - Vec3 dir{std::cos(phi) * r, y, std::sin(phi) * r}; + Vec3 dir{::cos(phi) * r, y, ::sin(phi) * r}; render::GaussianInstance g; g.position[0] = dir.x * radius; g.position[1] = dir.y * radius + 1.8f; @@ -1207,7 +1210,7 @@ void DemoScenes::CreateLodDemoScene() { // Three spheres at increasing distance, landing on lod 0 / 1 / 2 in turn. const Vec3 pos[3] = {{-1.6f, 0.9f, 4.5f}, {1.5f, 0.9f, 2.0f}, {-1.3f, 0.9f, -0.5f}}; for (int i = 0; i < 3; ++i) { - std::string tag = "builtin/loddemo/" + std::to_string(i); + base::String tag = "builtin/loddemo/" + rx::ToString(i); asset::Mesh sphere = asset::MakeLodSphere(1.2f, asset::MakeAssetId(tag + "/mesh")); for (asset::MeshLod& lod : sphere.lods) lod.submeshes[0].material = mat.id; if (!config_.headless) renderer_.UploadMesh(sphere); @@ -1253,7 +1256,7 @@ void DemoScenes::CreateCornellDemoScene() { world_.Add(e, scene::Renderable{mesh.id}); }; auto box = [&](f32 hx, f32 hy, f32 hz) { - return asset::MakeBox(hx, hy, hz, asset::MakeAssetId("builtin/cornell/" + std::to_string(counter++))); + return asset::MakeBox(hx, hy, hz, asset::MakeAssetId("builtin/cornell/" + rx::ToString(counter++))); }; add(box(2.0f, 0.1f, 2.0f), white, {0, -0.1f, 0}); // floor (top at y = 0) @@ -1306,7 +1309,7 @@ void DemoScenes::CreateInteriorDemoScene() { }; auto box = [&](f32 hx, f32 hy, f32 hz) { return asset::MakeBox(hx, hy, hz, - asset::MakeAssetId("builtin/interior/" + std::to_string(counter++))); + asset::MakeAssetId("builtin/interior/" + rx::ToString(counter++))); }; // Shell: 4 m x 3 m x 4 m interior (half-extents 2, 1.5, 2). @@ -1342,7 +1345,7 @@ void DemoScenes::CreateInteriorDemoScene() { s.interior_ambient = {0.015f, 0.015f, 0.02f}; s.interior_directional_intensity = 0.0f; // no fill: isolate the lamp + GI const render::InteriorVolume room{Vec3{-2.0f, 0.0f, -2.0f}, Vec3{2.0f, 3.0f, 2.0f}}; - renderer_.SetInteriorVolumes(std::span(&room, 1)); + renderer_.SetInteriorVolumes(base::Span(&room, 1)); camera_.set_position({0.0f, 1.4f, 1.3f}); camera_.set_yaw_pitch(0.0f, -0.05f); // face the back wall from just inside the door @@ -1382,7 +1385,7 @@ void DemoScenes::CreateImposterDemoScene() { auto push_vertex = [&](f32 x, f32 y, f32 z, f32 nx, f32 ny, f32 nz, u32 color) { asset::Vertex v{}; v.position[0] = x; v.position[1] = y; v.position[2] = z; - f32 len = std::sqrt(nx * nx + ny * ny + nz * nz); + f32 len = ::sqrt(nx * nx + ny * ny + nz * nz); v.normal[0] = nx / len; v.normal[1] = ny / len; v.normal[2] = nz / len; v.tangent[3] = 1.0f; v.color = color; @@ -1400,9 +1403,9 @@ void DemoScenes::CreateImposterDemoScene() { const f32 tw = 0.14f, th = 1.1f; for (int s = 0; s < 4; ++s) { f32 a0 = s * 1.5708f, a1 = a0 + 1.5708f; - f32 x0 = std::cos(a0) * tw, z0 = std::sin(a0) * tw; - f32 x1 = std::cos(a1) * tw, z1 = std::sin(a1) * tw; - f32 nx = std::cos(a0 + 0.7854f), nz = std::sin(a0 + 0.7854f); + f32 x0 = ::cos(a0) * tw, z0 = ::sin(a0) * tw; + f32 x1 = ::cos(a1) * tw, z1 = ::sin(a1) * tw; + f32 nx = ::cos(a0 + 0.7854f), nz = ::sin(a0 + 0.7854f); u32 v0 = push_vertex(x0, 0.0f, z0, nx, 0, nz, kBrown); u32 v1 = push_vertex(x1, 0.0f, z1, nx, 0, nz, kBrown); u32 v2 = push_vertex(x1, th, z1, nx, 0, nz, kBrown); @@ -1416,13 +1419,13 @@ void DemoScenes::CreateImposterDemoScene() { const int kSegs = 10; for (int s = 0; s < kSegs; ++s) { f32 a0 = s * 6.2831853f / kSegs, a1 = (s + 1) * 6.2831853f / kSegs; - f32 x0 = std::cos(a0) * radius, z0 = std::sin(a0) * radius; - f32 x1 = std::cos(a1) * radius, z1 = std::sin(a1) * radius; + f32 x0 = ::cos(a0) * radius, z0 = ::sin(a0) * radius; + f32 x1 = ::cos(a1) * radius, z1 = ::sin(a1) * radius; f32 am = (a0 + a1) * 0.5f; u32 color = (s & 1) ? kGreen : kGreenDark; u32 v0 = push_vertex(x0, base, z0, x0, radius * 0.6f, z0, color); u32 v1 = push_vertex(x1, base, z1, x1, radius * 0.6f, z1, color); - u32 v2 = push_vertex(0.0f, tip, 0.0f, std::cos(am), 0.8f, std::sin(am), color); + u32 v2 = push_vertex(0.0f, tip, 0.0f, ::cos(am), 0.8f, ::sin(am), color); lod.indices.push_back(v0); lod.indices.push_back(v2); lod.indices.push_back(v1); @@ -1458,14 +1461,14 @@ void DemoScenes::CreateImposterDemoScene() { }; const u32 baked = config_.headless ? render::ImposterPass::kNoMesh : renderer_.BakeImposter(tree); - std::vector instances; + base::Vector instances; for (int i = 0; i < 4000; ++i) { f32 ang = next_rand() * 6.2831853f; f32 dist = 22.0f + next_rand() * 170.0f; render::ImposterPass::Instance inst; - inst.position[0] = std::cos(ang) * dist; + inst.position[0] = ::cos(ang) * dist; inst.position[1] = 0.0f; - inst.position[2] = std::sin(ang) * dist; + inst.position[2] = ::sin(ang) * dist; inst.scale = 0.8f + next_rand() * 0.7f; inst.mesh = baked == render::ImposterPass::kNoMesh ? 0 : baked; instances.push_back(inst); @@ -1474,8 +1477,8 @@ void DemoScenes::CreateImposterDemoScene() { f32 ang = next_rand() * 6.2831853f; f32 dist = 6.0f + next_rand() * 12.0f; ecs::Entity t = world_.Create(); - world_.Add(t, scene::Transform{.position = {std::cos(ang) * dist, 0.0f, - std::sin(ang) * dist}}); + world_.Add(t, scene::Transform{.position = {::cos(ang) * dist, 0.0f, + ::sin(ang) * dist}}); world_.Add(t, scene::Renderable{tree.id}); } if (baked != render::ImposterPass::kNoMesh) renderer_.SetImposterInstances(instances); @@ -1617,8 +1620,8 @@ void DemoScenes::CreateStrandHairDemoScene() { f32 hr = head_radius; renderer_.HairGroomHead(id, &hc, &hr); asset::Mesh head = asset::MakeSphere(hr * 0.92f, 24, 36, - asset::MakeAssetId(std::string("builtin/strands/head") + - std::to_string(i))); + asset::MakeAssetId(base::String("builtin/strands/head") + + rx::ToString(i))); head.lods[0].submeshes.push_back({0, static_cast(head.lods[0].indices.size()), skin.id}); renderer_.UploadMesh(head); ecs::Entity h = world_.Create(); @@ -1812,7 +1815,7 @@ void DemoScenes::EmitCloth(render::FrameView& view) { return; } - std::fill(cloth_normals_.begin(), cloth_normals_.end(), Vec3{}); + base::Fill(cloth_normals_.begin(), cloth_normals_.end(), Vec3{}); cloth_lines_.clear(); for (size_t i = 0; i < cloth_indices_.size(); i += 3) { const u32 a = cloth_indices_[i + 0]; @@ -1862,7 +1865,7 @@ void DemoScenes::EmitCloth(render::FrameView& view) { view.draws.push_back( {cloth_mesh_, Mat4::Identity(), Mat4::Identity(), skin_offset}); view.debug_lines_overlay = - std::span(cloth_lines_.data(), cloth_lines_.size()); + base::Span(cloth_lines_.data(), cloth_lines_.size()); } void DemoScenes::CreateVirtualGeometryDemoScene() { @@ -1877,17 +1880,17 @@ void DemoScenes::CreateVirtualGeometryDemoScene() { // 30). 2048^2 is a ~8.4M-triangle source mesh. u32 kGrid = 640; f32 kSize = 300.0f; - std::vector dem; + base::Vector dem; f32 hm_step = 30.0f; - if (const char* hm = std::getenv("RX_VGEO_HEIGHTMAP")) { + if (const char* hm = ::getenv("RX_VGEO_HEIGHTMAP")) { u32 n = 2048; - if (const char* e = std::getenv("RX_VGEO_HM_SIZE")) n = std::max(2, std::atoi(e)); - if (const char* e = std::getenv("RX_VGEO_HM_STEP")) hm_step = std::atof(e); - std::ifstream f(hm, std::ios::binary); - if (f) { + if (const char* e = ::getenv("RX_VGEO_HM_SIZE")) n = rx::Max(2, ::atoi(e)); + if (const char* e = ::getenv("RX_VGEO_HM_STEP")) hm_step = ::atof(e); + base::File f = fs::OpenFile(hm, base::File::FLAG_OPEN | base::File::FLAG_READ); + if (f.IsValid()) { dem.resize(static_cast(n) * n); - f.read(reinterpret_cast(dem.data()), dem.size() * sizeof(f32)); - if (f.gcount() == static_cast(dem.size() * sizeof(f32))) { + if (fs::ReadAt(f, 0, + base::Span(reinterpret_cast(dem.data()), dem.size() * sizeof(f32)))) { kGrid = n - 1; kSize = static_cast(n - 1) * hm_step; RX_INFO("vgeo demo: heightmap {} ({}x{}, {:.1f} km)", hm, n, n, kSize / 1000.0f); @@ -1905,9 +1908,9 @@ void DemoScenes::CreateVirtualGeometryDemoScene() { asset::MeshLod& lod = terrain.lods[0]; lod.vertices.reserve(static_cast(kGrid + 1) * (kGrid + 1)); auto analytic = [](f32 x, f32 z) { - return 3.0f * std::sin(x * 0.05f) * std::cos(z * 0.045f) + - 0.8f * std::sin(x * 0.31f + 1.7f) * std::sin(z * 0.27f) + - 0.15f * std::sin(x * 1.7f) * std::cos(z * 1.9f); + return 3.0f * ::sin(x * 0.05f) * ::cos(z * 0.045f) + + 0.8f * ::sin(x * 0.31f + 1.7f) * ::sin(z * 0.27f) + + 0.15f * ::sin(x * 1.7f) * ::cos(z * 1.9f); }; const u32 hm_n = kGrid + 1; auto height = [&](f32 wx, f32 wz, u32 xi, u32 zi) { @@ -1928,7 +1931,7 @@ void DemoScenes::CreateVirtualGeometryDemoScene() { f32 hx = height(wx + e, wz, x1, z) - height(wx - e, wz, x0, z); f32 hz = height(wx, wz + e, x, z1) - height(wx, wz - e, x, z0); f32 nx = -hx / (2.0f * e), nz = -hz / (2.0f * e); - f32 len = std::sqrt(nx * nx + 1.0f + nz * nz); + f32 len = ::sqrt(nx * nx + 1.0f + nz * nz); v.normal[0] = nx / len; v.normal[1] = 1.0f / len; v.normal[2] = nz / len; @@ -1954,18 +1957,19 @@ void DemoScenes::CreateVirtualGeometryDemoScene() { // RX_VGEO_ALBEDO= drapes an image over the terrain by planar xz // projection: raw RGBA8 full mip chain, RX_VGEO_ALBEDO_SIZE px at mip 0 // (default 4096). Shown by the default-shaded (debug 0) resolve mode. - if (const char* al = std::getenv("RX_VGEO_ALBEDO")) { + if (const char* al = ::getenv("RX_VGEO_ALBEDO")) { u32 an = 4096; - if (const char* e = std::getenv("RX_VGEO_ALBEDO_SIZE")) an = std::max(1, std::atoi(e)); + if (const char* e = ::getenv("RX_VGEO_ALBEDO_SIZE")) an = rx::Max(1, ::atoi(e)); size_t bytes = 0; for (u32 m = an;; m /= 2) { bytes += static_cast(m) * m * 4; if (m == 1) break; } - std::ifstream f(al, std::ios::binary); - std::vector mips(bytes); - if (f && f.read(reinterpret_cast(mips.data()), bytes) && !config_.headless) { - renderer_.SetVirtualGeometryAlbedo({mips.data(), mips.size()}, an, 1.0f / kSize); + base::File f = fs::OpenFile(al, base::File::FLAG_OPEN | base::File::FLAG_READ); + base::Vector mips(bytes); + if (f.IsValid() && fs::ReadAt(f, 0, base::Span(mips.data(), mips.size())) && + !config_.headless) { + renderer_.SetVirtualGeometryAlbedo(ByteSpan(mips.data(), mips.size()), an, 1.0f / kSize); } else { RX_ERROR("vgeo demo: albedo {} unreadable or short", al); } @@ -1973,9 +1977,9 @@ void DemoScenes::CreateVirtualGeometryDemoScene() { // RX_VGEO_INSTANCES=N tiles the terrain N x N: N^2 x 800k source triangles // feed the gpu cull while the rastered count stays bounded by the screen. int grid = 1; - if (const char* env = std::getenv("RX_VGEO_INSTANCES")) grid = std::max(1, std::atoi(env)); + if (const char* env = ::getenv("RX_VGEO_INSTANCES")) grid = rx::Max(1, ::atoi(env)); if (grid > 1 && !config_.headless) { - std::vector instances; + base::Vector instances; instances.reserve(static_cast(grid) * grid); for (int z = 0; z < grid; ++z) { for (int x = 0; x < grid; ++x) { @@ -2001,9 +2005,9 @@ void DemoScenes::CreateVirtualGeometryDemoScene() { camera_.speed = 300.0f; } // RX_VGEO_CAM="x,y,z,yaw,pitch" pins the fly camera for repeatable captures. - if (const char* cam = std::getenv("RX_VGEO_CAM")) { + if (const char* cam = ::getenv("RX_VGEO_CAM")) { f32 v[5] = {0, 0, 0, 0, 0}; - if (std::sscanf(cam, "%f,%f,%f,%f,%f", &v[0], &v[1], &v[2], &v[3], &v[4]) == 5) { + if (::sscanf(cam, "%f,%f,%f,%f,%f", &v[0], &v[1], &v[2], &v[3], &v[4]) == 5) { camera_.set_position({v[0], v[1], v[2]}); camera_.set_yaw_pitch(v[3], v[4]); } @@ -2058,13 +2062,13 @@ void DemoScenes::CreateBrickDemoScene() { // Two-course running bond with rounded mortar channels. f32 row = v * 8.0f; f32 col = u * 4.0f + (static_cast(row) % 2 ? 0.5f : 0.0f); - f32 fy = row - std::floor(row); - f32 fx = col - std::floor(col); + f32 fy = row - ::floor(row); + f32 fx = col - ::floor(col); auto channel = [](f32 t, f32 w) { - f32 d = std::min(t, 1.0f - t) / w; // distance to the mortar line - return std::min(d, 1.0f); + f32 d = rx::Min(t, 1.0f - t) / w; // distance to the mortar line + return rx::Min(d, 1.0f); }; - f32 h = std::min(channel(fx, 0.06f), channel(fy, 0.10f)); + f32 h = rx::Min(channel(fx, 0.06f), channel(fy, 0.10f)); h = h * h * (3.0f - 2.0f * h); // rounded shoulder // Slight per-brick height variation + surface grain. u32 bx = static_cast(col), by = static_cast(row); @@ -2096,7 +2100,7 @@ void DemoScenes::CreateBrickDemoScene() { f32 hx = brick_height(u + e, v) - brick_height(u - e, v); f32 hy = brick_height(u, v + e) - brick_height(u, v - e); f32 nx = -hx * 6.0f, ny = -hy * 6.0f, nz = 1.0f; - f32 len = std::sqrt(nx * nx + ny * ny + nz * nz); + f32 len = ::sqrt(nx * nx + ny * ny + nz * nz); normal.data[o] = static_cast((nx / len * 0.5f + 0.5f) * 255.0f); normal.data[o + 1] = static_cast((ny / len * 0.5f + 0.5f) * 255.0f); normal.data[o + 2] = static_cast((nz / len * 0.5f + 0.5f) * 255.0f); @@ -2171,11 +2175,11 @@ void DemoScenes::CreateBrickDemoScene() { auto blob = [](f32 u, f32 v, u32 seed_base, int arms) { // Irregular radial blob: radius modulated by a few sine lobes. f32 du = u - 0.5f, dv = v - 0.5f; - f32 r = std::sqrt(du * du + dv * dv) * 2.2f; - f32 ang = std::atan2(dv, du); - f32 rim = 0.75f + 0.18f * std::sin(ang * arms + seed_base) + - 0.12f * std::sin(ang * (arms * 2 + 1) + seed_base * 1.7f); - return std::max(0.0f, 1.0f - r / rim); + f32 r = ::sqrt(du * du + dv * dv) * 2.2f; + f32 ang = ::atan2(dv, du); + f32 rim = 0.75f + 0.18f * ::sin(ang * arms + seed_base) + + 0.12f * ::sin(ang * (arms * 2 + 1) + seed_base * 1.7f); + return rx::Max(0.0f, 1.0f - r / rim); }; for (u32 y = 0; y < atlas.height; ++y) { for (u32 x = 0; x < atlas.width; ++x) { @@ -2184,7 +2188,7 @@ void DemoScenes::CreateBrickDemoScene() { if (x < 256) { // blood f32 u = (x + 0.5f) / 256.0f; f32 m = blob(u, v, 3, 7); - f32 a = m > 0.02f ? std::min(1.0f, m * 2.2f) : 0.0f; + f32 a = m > 0.02f ? rx::Min(1.0f, m * 2.2f) : 0.0f; atlas.data[o] = static_cast(90 + 40 * m); atlas.data[o + 1] = static_cast(8 + 10 * m); atlas.data[o + 2] = static_cast(8 + 8 * m); @@ -2192,8 +2196,8 @@ void DemoScenes::CreateBrickDemoScene() { } else { // moss f32 u = (x - 256 + 0.5f) / 256.0f; f32 m = blob(u, v, 11, 9); - f32 grain = 0.7f + 0.3f * blob(std::fmod(u * 5.0f, 1.0f), std::fmod(v * 5.0f, 1.0f), 5, 5); - f32 a = m > 0.05f ? std::min(1.0f, m * 1.6f) * grain : 0.0f; + f32 grain = 0.7f + 0.3f * blob(::fmod(u * 5.0f, 1.0f), ::fmod(v * 5.0f, 1.0f), 5, 5); + f32 a = m > 0.05f ? rx::Min(1.0f, m * 1.6f) * grain : 0.0f; atlas.data[o] = static_cast(40 + 25 * m); atlas.data[o + 1] = static_cast(85 + 60 * m * grain); atlas.data[o + 2] = static_cast(28 + 15 * m); @@ -2213,7 +2217,7 @@ void DemoScenes::CreateBrickDemoScene() { channels.data.resize(static_cast(channels.width) * channels.height * 4); auto moss_height = [&](f32 u, f32 v) { f32 m = blob(u, v, 11, 9); - f32 grain = 0.7f + 0.3f * blob(std::fmod(u * 5.0f, 1.0f), std::fmod(v * 5.0f, 1.0f), 5, 5); + f32 grain = 0.7f + 0.3f * blob(::fmod(u * 5.0f, 1.0f), ::fmod(v * 5.0f, 1.0f), 5, 5); return m * grain; }; for (u32 y = 0; y < channels.height; ++y) { @@ -2227,7 +2231,7 @@ void DemoScenes::CreateBrickDemoScene() { nx = (moss_height(u - e, v) - moss_height(u + e, v)) * 3.0f; ny = (moss_height(u, v - e) - moss_height(u, v + e)) * 3.0f; } - f32 nz = std::sqrt(std::max(1.0f - nx * nx - ny * ny, 0.05f)); + f32 nz = ::sqrt(rx::Max(1.0f - nx * nx - ny * ny, 0.05f)); channels.data[o] = static_cast((nx * 0.5f + 0.5f) * 255.0f); channels.data[o + 1] = static_cast((ny * 0.5f + 0.5f) * 255.0f); channels.data[o + 2] = static_cast((nz * 0.5f + 0.5f) * 255.0f); @@ -2302,10 +2306,10 @@ void DemoScenes::CreateSilhouettePomDemoScene() { // Grid of rounded domes; deep flat gaps between them give the limb something // to carve. Round-shouldered so the normal map stays smooth. f32 cu = u * kTilesU, cv = v * kTilesV; - f32 fu = cu - std::floor(cu) - 0.5f; - f32 fv = cv - std::floor(cv) - 0.5f; - f32 d = std::sqrt(fu * fu + fv * fv) * 2.0f; // 0 centre .. ~1.4 corner - f32 h = std::max(0.0f, 1.0f - d / 0.82f); + f32 fu = cu - ::floor(cu) - 0.5f; + f32 fv = cv - ::floor(cv) - 0.5f; + f32 d = ::sqrt(fu * fu + fv * fv) * 2.0f; // 0 centre .. ~1.4 corner + f32 h = rx::Max(0.0f, 1.0f - d / 0.82f); return h * h * (3.0f - 2.0f * h); // smoothstep dome }; asset::Texture height; @@ -2329,7 +2333,7 @@ void DemoScenes::CreateSilhouettePomDemoScene() { f32 hx = stud_height(u + e, v) - stud_height(u - e, v); f32 hy = stud_height(u, v + e) - stud_height(u, v - e); f32 nx = -hx * 6.0f, ny = -hy * 6.0f, nz = 1.0f; - f32 len = std::sqrt(nx * nx + ny * ny + nz * nz); + f32 len = ::sqrt(nx * nx + ny * ny + nz * nz); normal.data[o] = static_cast((nx / len * 0.5f + 0.5f) * 255.0f); normal.data[o + 1] = static_cast((ny / len * 0.5f + 0.5f) * 255.0f); normal.data[o + 2] = static_cast((nz / len * 0.5f + 0.5f) * 255.0f); @@ -2422,7 +2426,7 @@ void DemoScenes::CreateSssDemoScene() { auto spawn_sphere = [&](Vec3 pos, f32 radius, bool skin, f32 perfusion, const char* tag) { asset::Material mat; - mat.id = asset::MakeAssetId(std::string("builtin/sss/mat_") + tag); + mat.id = asset::MakeAssetId(base::String("builtin/sss/mat_") + tag); mat.base_color_factor[0] = 0.62f; mat.base_color_factor[1] = 0.44f; mat.base_color_factor[2] = 0.34f; @@ -2441,7 +2445,7 @@ void DemoScenes::CreateSssDemoScene() { mat.skin_params.mfp[2] = 0.20f; mat.skin_params.perfusion = perfusion; asset::Mesh sphere = - asset::MakeSphere(radius, 48, 64, asset::MakeAssetId(std::string("builtin/sss/") + tag)); + asset::MakeSphere(radius, 48, 64, asset::MakeAssetId(base::String("builtin/sss/") + tag)); sphere.lods[0].submeshes[0].material = mat.id; if (!config_.headless) { renderer_.UploadMaterial(mat); @@ -2465,14 +2469,14 @@ void DemoScenes::CreateSssDemoScene() { // the left is the plain ggx control blob. auto spawn_hair = [&](Vec3 pos, bool hair, const char* tag) { asset::Material mat; - mat.id = asset::MakeAssetId(std::string("builtin/sss/hairmat_") + tag); + mat.id = asset::MakeAssetId(base::String("builtin/sss/hairmat_") + tag); mat.base_color_factor[0] = 0.16f; mat.base_color_factor[1] = 0.10f; mat.base_color_factor[2] = 0.06f; mat.roughness_factor = 0.45f; mat.hair = hair; asset::Mesh sphere = asset::MakeSphere( - 0.45f, 48, 64, asset::MakeAssetId(std::string("builtin/sss/hair_") + tag)); + 0.45f, 48, 64, asset::MakeAssetId(base::String("builtin/sss/hair_") + tag)); sphere.lods[0].submeshes[0].material = mat.id; if (!config_.headless) { renderer_.UploadMaterial(mat); @@ -2551,8 +2555,8 @@ void DemoScenes::CreateFireDemoScene() { f32 a = static_cast(i) * 1.2566f; Quat q = QuatFromAxisAngle({0, 1, 0}, a); ecs::Entity e = world_.Create(); - world_.Add(e, scene::Transform{.position = {std::cos(a) * 0.28f, 0.10f + 0.02f * i, - std::sin(a) * 0.28f}, + world_.Add(e, scene::Transform{.position = {::cos(a) * 0.28f, 0.10f + 0.02f * i, + ::sin(a) * 0.28f}, .rotation = {q.x, q.y, q.z, q.w}}); world_.Add(e, scene::Renderable{log.id}); } @@ -2822,7 +2826,7 @@ void DemoScenes::CreateOcclusionDemoScene() { f32 x = -1.1f + gx * 0.2f; f32 y = 0.7f + gy * 0.2f; f32 z = -2.0f - (gx % 3) * 0.6f; - std::string tag = "builtin/occl/c" + std::to_string(idx++); + base::String tag = "builtin/occl/c" + rx::ToString(idx++); add_box(asset::MakeCube(0.04f, asset::MakeAssetId(tag)), cube_mat, {x, y, z}); } } @@ -2856,7 +2860,7 @@ void DemoScenes::CreatePointLightDemoScene() { // A few low bumps so the lights wrap over shapes, not just a flat plane. for (int i = 0; i < 5; ++i) { f32 x = -3.2f + i * 1.6f; - std::string tag = "builtin/lights/bump" + std::to_string(i); + base::String tag = "builtin/lights/bump" + rx::ToString(i); asset::Mesh s = asset::MakeSphere(0.6f, 24, 32, asset::MakeAssetId(tag)); s.lods[0].submeshes.push_back({0, static_cast(s.lods[0].indices.size()), floor_mat.id}); if (!config_.headless) renderer_.UploadMesh(s); @@ -2889,11 +2893,11 @@ void DemoScenes::CreatePointLightDemoScene() { spot.color_intensity[0] = 1.0f; spot.color_intensity[1] = 0.95f; spot.color_intensity[2] = 0.8f; spot.color_intensity[3] = 20.0f; f32 dir[3] = {0.35f, -0.85f, -0.4f}; - f32 len = std::sqrt(dir[0]*dir[0]+dir[1]*dir[1]+dir[2]*dir[2]); + f32 len = ::sqrt(dir[0]*dir[0]+dir[1]*dir[1]+dir[2]*dir[2]); spot.direction_type[0] = dir[0]/len; spot.direction_type[1] = dir[1]/len; spot.direction_type[2] = dir[2]/len; spot.direction_type[3] = 1.0f; // spot - spot.params[0] = std::cos(0.28f); // inner ~16 deg - spot.params[1] = std::cos(0.45f); // outer ~26 deg + spot.params[0] = ::cos(0.28f); // inner ~16 deg + spot.params[1] = ::cos(0.45f); // outer ~26 deg demo_lights_.push_back(spot); render::PointLight ball; @@ -3157,7 +3161,7 @@ void DemoScenes::CreateSkyDemoScene() { // The weather layer: four states spanning the model's range. Dwell and // transition times shrink under RX_SKY_FAST so a capture sees a change. - weather_sys_ = std::make_unique(7u); + weather_sys_ = base::MakeUnique(7u); const bool fast = bool(SkyFast); auto tune = [&](weather::WeatherState s) { if (fast) { @@ -3314,9 +3318,9 @@ void DemoScenes::CreateSwampDemoScene() { // Small-angle lean as a quaternion straight from the half-angles: the // snags list drunkenly instead of standing surveyor-straight. f32 hx = tilt_x * 0.5f, hz = tilt_z * 0.5f; - t.rotation[0] = std::sin(hx); - t.rotation[2] = std::sin(hz); - t.rotation[3] = std::cos(hx) * std::cos(hz); + t.rotation[0] = ::sin(hx); + t.rotation[2] = ::sin(hz); + t.rotation[3] = ::cos(hx) * ::cos(hz); world_.Add(e, t); world_.Add(e, scene::Renderable{mesh.id}); }; @@ -3335,7 +3339,7 @@ void DemoScenes::CreateSwampDemoScene() { f32 z = (next01() - 0.5f) * 120.0f; f32 w = 2.5f + next01() * 4.0f; char tag[64]; - std::snprintf(tag, sizeof(tag), "builtin/swamp/hummock_%d", i); + ::snprintf(tag, sizeof(tag), "builtin/swamp/hummock_%d", i); f32 h = 0.5f + next01() * 0.5f; add_box(tag, w, h, w * (0.7f + next01() * 0.6f), moss_mat, {x, h * 0.5f, z}, 0, 0); } @@ -3343,19 +3347,19 @@ void DemoScenes::CreateSwampDemoScene() { for (int i = 0; i < 26; ++i) { f32 x = (next01() - 0.5f) * 140.0f; f32 z = (next01() - 0.5f) * 140.0f; - if (std::fabs(x) < 4.0f && std::fabs(z) < 4.0f) continue; // keep the spawn clear + if (::fabs(x) < 4.0f && ::fabs(z) < 4.0f) continue; // keep the spawn clear f32 h = next01() < 0.3f ? 3.0f + next01() * 3.0f : 8.0f + next01() * 9.0f; f32 w = 0.25f + next01() * 0.35f; f32 tx = (next01() - 0.5f) * 0.16f; f32 tz = (next01() - 0.5f) * 0.16f; char tag[64]; - std::snprintf(tag, sizeof(tag), "builtin/swamp/snag_%d", i); + ::snprintf(tag, sizeof(tag), "builtin/swamp/snag_%d", i); add_box(tag, w, h, w, snag_mat, {x, h * 0.5f, z}, tx, tz); } // One forced swamp state: low thin stratus lid, still air, thick shallow // mist. Wetness is pinned in EmitSky so the mud keeps its puddle sheen. - weather_sys_ = std::make_unique(3u); + weather_sys_ = base::MakeUnique(3u); weather::WeatherState swamp; swamp.name = "swamp"; swamp.coverage = 0.68f; @@ -3402,7 +3406,7 @@ void DemoScenes::EmitSky(f32 dt) { if (swamp_scene_) { // Standing water never dries: pin the surface response so the mud keeps // its puddle sheen without any rain falling. - rs.weather.wetness = std::max(rs.weather.wetness, 0.8f); + rs.weather.wetness = rx::Max(rs.weather.wetness, 0.8f); } sky_controls_ = rs.cloudscape_controls; sky_weather_ = rs.weather; @@ -3412,9 +3416,9 @@ void DemoScenes::EmitSky(f32 dt) { Vec3 cam = camera_.position(); f32 dx = cc.tornado_pos.x - cam.x; f32 dz = cc.tornado_pos.y - cam.z; - f32 flat = std::sqrt(dx * dx + dz * dz); + f32 flat = ::sqrt(dx * dx + dz * dz); // forward() is (sin yaw, sin pitch, -cos yaw): aim a third up the funnel. - camera_.set_yaw_pitch(std::atan2(dx, -dz), std::atan2(300.0f - cam.y, flat)); + camera_.set_yaw_pitch(::atan2(dx, -dz), ::atan2(300.0f - cam.y, flat)); } if (cc.tornado_strength > 0.05f && !sky_tornado_seen_) { sky_tornado_seen_ = true; @@ -3440,7 +3444,7 @@ void DemoScenes::EmitSky(f32 dt) { Vec3 cam = camera_.position(); f32 dx = w.strike_pos.x - cam.x, dy = w.strike_pos.y + 400.0f - cam.y; f32 dz = w.strike_pos.z - cam.z; - f32 dist = std::sqrt(dx * dx + dy * dy + dz * dz); + f32 dist = ::sqrt(dx * dx + dy * dy + dz * dz); sky_thunder_.push_back({dist / 343.0f, w.strike_pos, w.strike_seed, w.strike_energy, dist}); } for (u32 i = 0; i < sky_thunder_.size();) { @@ -3480,8 +3484,8 @@ void DemoScenes::UpdateStorm(f32 dt) { f32 env = render::LightningSystem::Envelope(w.strike_age, w.strike_seed); // Peak ~0.35: the global consumers multiply hard (sun +9x, ambient +0.5 // at flash 1.0) and would white the staged overcast frame out above that. - w.lightning = std::min( - 1.0f, w.strike_energy * (0.22f * std::exp(-w.strike_age * 9.0f) + 0.35f * env)); + w.lightning = rx::Min( + 1.0f, w.strike_energy * (0.22f * ::exp(-w.strike_age * 9.0f) + 0.35f * env)); if (w.strike_age > 1.2f) { // envelope + afterglow both spent w.strike_age = -1.0f; w.lightning = 0.0f; @@ -3500,7 +3504,7 @@ void DemoScenes::UpdateStorm(f32 dt) { if (test_dist > 0.0f) { // Deterministic: directly in front of the camera at the given distance. Vec3 fwd = camera_.forward(); - f32 len = std::sqrt(fwd.x * fwd.x + fwd.z * fwd.z); + f32 len = ::sqrt(fwd.x * fwd.x + fwd.z * fwd.z); Vec3 dir = len > 1e-4f ? Vec3{fwd.x / len, 0.0f, fwd.z / len} : Vec3{0, 0, -1}; Vec3 eye = camera_.position(); w.strike_pos = {eye.x + dir.x * test_dist, 0.0f, eye.z + dir.z * test_dist}; @@ -3511,7 +3515,7 @@ void DemoScenes::UpdateStorm(f32 dt) { f32 az = hash01(w.strike_seed * 3u + 1u) * 6.2831853f; f32 dist = 300.0f + 600.0f * hash01(w.strike_seed * 3u + 2u); Vec3 eye = camera_.position(); - w.strike_pos = {eye.x + std::cos(az) * dist, 0.0f, eye.z + std::sin(az) * dist}; + w.strike_pos = {eye.x + ::cos(az) * dist, 0.0f, eye.z + ::sin(az) * dist}; w.strike_energy = 0.65f + 0.35f * hash01(w.strike_seed * 3u + 3u); storm_next_strike_ = storm_time_ + 6.0f + 3.0f * hash01(w.strike_seed * 7u + 5u); } @@ -3544,9 +3548,9 @@ void DemoScenes::CreateMaterialXDemoScene() { world_.Add(floor, scene::Transform{.position = {0, -8.6f, 0}}); // top at y = -0.6 world_.Add(floor, scene::Renderable{ground.id}); - base::Vector paths; + base::Vector paths; if (const char* env = Mtlx.get()) { - std::string s = env, cur; + base::String s = env, cur; for (char c : s) { if (c == ',') { if (!cur.empty()) paths.push_back(cur); @@ -3562,10 +3566,10 @@ void DemoScenes::CreateMaterialXDemoScene() { int n = static_cast(paths.size()); for (int i = 0; i < n; ++i) { asset::Material mat; - mat.id = asset::MakeAssetId("builtin/mtlx/mat" + std::to_string(i)); + mat.id = asset::MakeAssetId("builtin/mtlx/mat" + rx::ToString(i)); if (!asset::LoadMaterialX(paths[i], &mat)) continue; if (!config_.headless) renderer_.UploadMaterial(mat); - std::string tag = "builtin/mtlx/sphere" + std::to_string(i); + base::String tag = "builtin/mtlx/sphere" + rx::ToString(i); asset::Mesh sphere = asset::MakeSphere(0.6f, 40, 60, asset::MakeAssetId(tag)); sphere.lods[0].submeshes[0].material = mat.id; if (!config_.headless) renderer_.UploadMesh(sphere); @@ -3653,7 +3657,7 @@ void DemoScenes::CreateDemoScene() { if (config_.demo_scene == "lookdev") { // The character reference lab: OLAT rig, frozen framings, reference // comparison and live material fitting. See docs/CHARACTER_RENDERING.md. - lookdev_ = std::make_unique(ctx_); + lookdev_ = base::MakeUnique(ctx_); lookdev_->Create(); return; } @@ -3714,47 +3718,47 @@ void DemoScenes::CreateDemoScene() { return; } if (config_.demo_scene == "ship") { - ship_ = std::make_unique(ctx_); + ship_ = base::MakeUnique(ctx_); ship_->Create(); return; } if (config_.demo_scene == "nav") { - nav_ = std::make_unique(ctx_); + nav_ = base::MakeUnique(ctx_); nav_->Create(); return; } if (config_.demo_scene == "placement") { - placement_ = std::make_unique(ctx_); + placement_ = base::MakeUnique(ctx_); placement_->Create(); return; } if (config_.demo_scene == "grass") { - grass_ = std::make_unique(ctx_); + grass_ = base::MakeUnique(ctx_); grass_->Create(); return; } if (config_.demo_scene == "gym") { - gym_ = std::make_unique(ctx_); + gym_ = base::MakeUnique(ctx_); gym_->Create(); return; } if (config_.demo_scene == "shooter" || config_.demo_scene == "fps") { - shooter_ = std::make_unique(ctx_); + shooter_ = base::MakeUnique(ctx_); shooter_->Create(); return; } if (config_.demo_scene == "puppet") { - puppet_ = std::make_unique(ctx_); + puppet_ = base::MakeUnique(ctx_); puppet_->Create(); return; } if (config_.demo_scene == "drive") { - drive_ = std::make_unique(ctx_); + drive_ = base::MakeUnique(ctx_); drive_->Create(); return; } if (config_.demo_scene == "featuregym" || config_.demo_scene == "feature-gym") { - feature_gym_ = std::make_unique(ctx_); + feature_gym_ = base::MakeUnique(ctx_); feature_gym_->Create(); return; } @@ -3778,8 +3782,8 @@ void DemoScenes::CreateDemoScene() { scheduler_.AddSystem(ecs::Stage::kSim, "demo_spin", [](ecs::World& world, f32 dt) { world.Each([dt](ecs::Entity, Spin& spin, scene::Transform& t) { spin.angle += spin.speed * dt; - t.rotation[1] = std::sin(spin.angle * 0.5f); - t.rotation[3] = std::cos(spin.angle * 0.5f); + t.rotation[1] = ::sin(spin.angle * 0.5f); + t.rotation[3] = ::cos(spin.angle * 0.5f); }); }); RX_INFO("no scene given, spinning a cube instead"); @@ -3880,9 +3884,9 @@ void DemoScenes::EmitLocomotion(f32 dt, render::FrameView& view) { if (ctx_.actions) { f32 mx = ctx_.actions->axis(Axis::kMoveX); f32 my = ctx_.actions->axis(Axis::kMoveY); - f32 mag = std::sqrt(mx * mx + my * my); + f32 mag = ::sqrt(mx * mx + my * my); if (mag > 0.05f) { - speed = std::min(mag, 1.0f) * 4.5f; // full stick -> run + speed = rx::Min(mag, 1.0f) * 4.5f; // full stick -> run input_driven = true; } } diff --git a/runtime/demo_scenes.h b/runtime/demo_scenes.h index 87234122..e01bca66 100644 --- a/runtime/demo_scenes.h +++ b/runtime/demo_scenes.h @@ -1,8 +1,6 @@ #ifndef RX_RUNTIME_DEMO_SCENES_H_ #define RX_RUNTIME_DEMO_SCENES_H_ -#include -#include #include @@ -11,6 +9,8 @@ #include "anim/pose.h" #include "anim/rig_player.h" #include "asset/skeleton.h" +#include "base/containers/vector.h" +#include "base/memory/unique_pointer.h" #include "core/math.h" #include "engine_context.h" #include "net/bubble.h" @@ -52,19 +52,19 @@ class DemoScenes { // The character/inventory gym (--demo gym), or null for any other scene. The // gym drives its own camera + input, so the Viewer routes OnUpdate to it. - GymDemo* gym() { return gym_.get(); } + GymDemo* gym() { return gym_.Get_UseOnlyIfYouKnowWhatYouareDoing(); } // The FPS range (--demo shooter), or null. Owns its camera + input like the // gym, so the Viewer routes OnUpdate to it. - ShooterDemo* shooter() { return shooter_.get(); } + ShooterDemo* shooter() { return shooter_.Get_UseOnlyIfYouKnowWhatYouareDoing(); } // The character reference lab (--demo lookdev), or null. Owns its camera // (frozen framings are the point), so the Viewer routes OnUpdate to it. - LookdevDemo* lookdev() { return lookdev_.get(); } + LookdevDemo* lookdev() { return lookdev_.Get_UseOnlyIfYouKnowWhatYouareDoing(); } // The locomotion puppet (--demo puppet), or null. Keeps the free-fly camera; // the Viewer forwards raw keys to it (1/2/3) without an early return. - PuppetDemo* puppet() { return puppet_.get(); } + PuppetDemo* puppet() { return puppet_.Get_UseOnlyIfYouKnowWhatYouareDoing(); } // The driving gym (--demo drive), or null for any other scene. Like the gym it // owns its camera + input, so the Viewer routes OnUpdate to it. - DriveDemo* drive() { return drive_.get(); } + DriveDemo* drive() { return drive_.Get_UseOnlyIfYouKnowWhatYouareDoing(); } // Reapplies demo-specific renderer constraints after the debug UI changes // settings. Most demos have none; cloth requires the raster skinning path. void ApplyRenderPolicy(); @@ -168,8 +168,8 @@ class DemoScenes { // --demo fluid: the GPU heightfield solver's world domain. The bed and // initial-water arrays are CPU-authoritative and live in these members (the // FluidDomainDesc holds pointers into them, so they must outlive it). - std::vector fluid_bed_; - std::vector fluid_initial_water_; + base::Vector fluid_bed_; + base::Vector fluid_initial_water_; render::FluidDomainDesc fluid_domain_{}; bool fluid_scene_ = false; bool dam_up_ = true; @@ -179,7 +179,7 @@ class DemoScenes { f32 fluid_dam_box_y0_ = 0; // rest Y of the dam boxes f32 fluid_dam_sink_ = -1.0f; // >=0 once breaking: seconds elapsed // --demo sky: the weather layer scheduling the cloudscape deck. - std::unique_ptr weather_sys_; + base::UniquePointer weather_sys_; bool sky_scene_ = false; bool swamp_scene_ = false; // pins surface wetness (standing water) bool sky_tornado_seen_ = false; // touchdown log edge detector @@ -265,51 +265,51 @@ class DemoScenes { // --demo scenehook: an app-owned raw-Vulkan GPU pass recorded through rx's // scene hooks. Non-null only for that demo. - std::unique_ptr scene_hook_; + base::UniquePointer scene_hook_; // --demo scenehook-rhi: the same acceptance scene driven purely through the // backend-agnostic RHI. Non-null only for that demo. - std::unique_ptr scene_hook_rhi_; + base::UniquePointer scene_hook_rhi_; // --demo ship: the procedural sailing-ship slice (hull/sails/rigging/cannons // on the ocean stack). Non-null only for that demo. - std::unique_ptr ship_; + base::UniquePointer ship_; // --demo nav: the navigation chase slice (cost-aware pathfinding over rough // terrain, porter vs mule pack). Non-null only for that demo. - std::unique_ptr nav_; + base::UniquePointer nav_; // --demo placement: GPU procedural placement (density programs + ordered // dithering streaming a forest around the camera). Non-null only for that // demo. - std::unique_ptr placement_; + base::UniquePointer placement_; // --demo grass: GPU-generated cubic-Bezier blades over semantic hills. - std::unique_ptr grass_; + base::UniquePointer grass_; // --demo gym: the character/inventory reference gym (graybox + tuning panel). // Non-null only for that demo; the Viewer drives its Update from OnUpdate. - std::unique_ptr gym_; + base::UniquePointer gym_; // The character reference lab (--demo lookdev). - std::unique_ptr lookdev_; + base::UniquePointer lookdev_; // --demo shooter: the FPS range over engine/combat (weapons, targets, HUD). // Non-null only for that demo; the Viewer drives its Update from OnUpdate. - std::unique_ptr shooter_; + base::UniquePointer shooter_; // --demo puppet: the physics-first locomotion proving ground (graybox arena + // rx::locomotion ragdoll + debug overlay). Non-null only for that demo. - std::unique_ptr puppet_; + base::UniquePointer puppet_; // --demo drive: the GTA-style driving gym (car/boat/plane on mixed terrain). // Non-null only for that demo; the Viewer drives its Update from OnUpdate. - std::unique_ptr drive_; - std::unique_ptr feature_gym_; + base::UniquePointer drive_; + base::UniquePointer feature_gym_; // --demo bubbles: the streaming-bubble interest map driven locally (no // transport), plus its wire-sphere visualizer. Non-null only for that demo. bool bubbles_enabled_ = false; net::InterestMap bubble_map_; - std::unique_ptr bubble_viz_; + base::UniquePointer bubble_viz_; u64 bubble_tick_ = 0; }; diff --git a/runtime/demo_ship.cc b/runtime/demo_ship.cc index 8cf2050d..24f2e1b6 100644 --- a/runtime/demo_ship.cc +++ b/runtime/demo_ship.cc @@ -1,13 +1,15 @@ #include "demo_ship.h" -#include -#include +#include #include "asset/asset_id.h" #include "asset/material.h" #include "asset/mesh.h" #include "asset/primitives.h" +#include "base/algorithm.h" +#include "base/memory/move.h" #include "core/log.h" +#include "core/scalar.h" #include "render/geometry/water_field.h" #include "scene/components.h" @@ -96,7 +98,7 @@ void AddCylinder(MeshBuild& mb, const Vec3& a, const Vec3& b, f32 r0, f32 r1, u3 f32 r = ring == 0 ? r0 : r1; for (u32 s = 0; s < seg; ++s) { f32 t = 2.0f * kPi * static_cast(s) / seg; - Vec3 local{std::cos(t) * r, 0, std::sin(t) * r}; + Vec3 local{::cos(t) * r, 0, ::sin(t) * r}; mb.Add(center + Rotate(q, local), static_cast(s) / seg, static_cast(ring), color); } } @@ -129,7 +131,7 @@ asset::Mesh Finish(MeshBuild& mb, const char* id, const asset::Material& mat) { // Bounds from the local extent (a loose sphere is enough for cull). f32 r = 0; for (const asset::Vertex& vert : mb.v) { - r = std::max(r, std::sqrt(vert.position[0] * vert.position[0] + + r = rx::Max(r, ::sqrt(vert.position[0] * vert.position[0] + vert.position[1] * vert.position[1] + vert.position[2] * vert.position[2])); } @@ -310,8 +312,8 @@ void ShipDemo::BuildFlagship() { auto width_scale = [](f32 z) -> f32 { // 1 amidships, tapering to a fine stem at the bow (+Z) and a transom aft. f32 tn = z / kHalfLen; // -1..1 - if (tn > 0) return std::max(0.06f, 1.0f - tn * tn * 0.98f); // pointed bow - return std::max(0.55f, 1.0f - tn * tn * 0.45f); // fuller transom + if (tn > 0) return rx::Max(0.06f, 1.0f - tn * tn * 0.98f); // pointed bow + return rx::Max(0.55f, 1.0f - tn * tn * 0.45f); // fuller transom }; auto sheer = [](f32 z) -> f32 { f32 tn = z / kHalfLen; @@ -324,8 +326,8 @@ void ShipDemo::BuildFlagship() { f32 deck = sheer(z); for (u32 pj = 0; pj <= kProfile; ++pj) { f32 th = kPi * static_cast(pj) / kProfile; // 0 = starboard rail .. pi = port rail - f32 x = -kBeam * ws * std::cos(th); - f32 y = deck - (deck - kKeelY) * std::pow(std::sin(th), 0.62f); + f32 x = -kBeam * ws * ::cos(th); + f32 y = deck - (deck - kKeelY) * ::pow(::sin(th), 0.62f); hb.Add({x, y, z}, static_cast(pj) / kProfile, z / 8.0f); } } @@ -412,7 +414,7 @@ void ShipDemo::BuildFlagship() { f32 x = (a - 0.5f) * 2.0f * s.halfw; f32 y = s.top - b * height; // Bulge downwind (+Z), max at centre, fuller toward the foot. - f32 bulge = std::sin(kPi * a) * (0.25f + 0.75f * b) * billow; + f32 bulge = ::sin(kPi * a) * (0.25f + 0.75f * b) * billow; Vec3 p = Vec3{x, y, s.z} + kWindDir * bulge; // uv.y drives the mesh.vs cloth-sway weight (0 = pinned head). Cap the // free-edge weight below 1 so the strong default wind flutters the foot @@ -471,7 +473,7 @@ void ShipDemo::BuildRopes() { r.pos.push_back(p); r.prev.push_back(p); } - ropes_.push_back(std::move(r)); + ropes_.push_back(base::move(r)); }; // Forestays: masthead down to the bowsprit / bow. @@ -525,7 +527,7 @@ void ShipDemo::UpdateShipMotion(f32 dt, render::FrameView& view) { hull_pos_ = pos; hull_rot_ = {rot[0], rot[1], rot[2], rot[3]}; } - hull_vel_ = (hull_pos_ - hull_prev_pos_) * (1.0f / std::max(dt, 1e-4f)); + hull_vel_ = (hull_pos_ - hull_prev_pos_) * (1.0f / rx::Max(dt, 1e-4f)); hull_prev_pos_ = hull_pos_; hull_prev_xform_ = hull_xform_; hull_xform_ = MakeTransform(hull_pos_, hull_rot_, 1.0f); @@ -557,7 +559,7 @@ void ShipDemo::UpdateSailsAndRopes(f32 dt, render::FrameView& view) { // frame; the ship barely yaws so the constant approximation is fine. const f32 h2 = dt * dt; Vec3 gravity{0, -9.8f, 0}; - f32 gust = 0.6f + 0.4f * std::sin(time_ * 2.3f); + f32 gust = 0.6f + 0.4f * ::sin(time_ * 2.3f); Vec3 wind = Rotate(QuatBetween(Rotate(hull_rot_, {0, 0, 1}), {0, 0, 1}), kWindDir) * (4.5f * gust); MeshBuild rm; @@ -710,7 +712,7 @@ void ShipDemo::UpdateParticles(f32 dt, render::FrameView& view) { sp.pos = d.position; f32 ang = Rand() * 2.0f * kPi; f32 spread = Rand() * 2.2f; - sp.vel = {std::cos(ang) * spread, 3.5f + Rand() * 3.5f, std::sin(ang) * spread}; + sp.vel = {::cos(ang) * spread, 3.5f + Rand() * 3.5f, ::sin(ang) * spread}; sp.color = {0.9f, 1.0f, 1.1f}; sp.max_life = sp.life = 0.6f + Rand() * 0.5f; sp.size = 0.14f + Rand() * 0.14f; @@ -718,9 +720,7 @@ void ShipDemo::UpdateParticles(f32 dt, render::FrameView& view) { } } } - projectiles_.erase(std::remove_if(projectiles_.begin(), projectiles_.end(), - [](const Projectile& p) { return !p.alive; }), - projectiles_.end()); + base::EraseIf(projectiles_, [](const Projectile& p) { return !p.alive; }); // Integrate + emit the additive billboard pool. for (size_t i = 0; i < particles_.size();) { @@ -759,7 +759,7 @@ void ShipDemo::UpdateParticles(f32 dt, render::FrameView& view) { // Hull-waterline wake: bow/stern/side impulses scaled by cruise speed, the same // WaterDisturbance path the water-demo cubes use, so the field draws the trail. void ShipDemo::EmitWake(render::FrameView& view) { - f32 speed = std::sqrt(hull_vel_.x * hull_vel_.x + hull_vel_.z * hull_vel_.z); + f32 speed = ::sqrt(hull_vel_.x * hull_vel_.x + hull_vel_.z * hull_vel_.z); const Vec3 local_pts[5] = {{0, 0, kHalfLen * 0.9f}, // bow {0, 0, -kHalfLen * 0.9f}, // stern {kBeam, 0, 0}, // port beam @@ -770,8 +770,8 @@ void ShipDemo::EmitWake(render::FrameView& view) { render::WaterDisturbance d; d.position = {w.x, kWaterY, w.z}; d.radius = k == 4 ? 3.4f : 2.4f; - d.ripple_strength = std::min(speed * 0.5f + (k == 0 ? 0.4f : 0.0f), 2.0f); - d.foam_amount = std::min(speed * (k == 0 ? 0.7f : 0.45f), 1.6f); + d.ripple_strength = rx::Min(speed * 0.5f + (k == 0 ? 0.4f : 0.0f), 2.0f); + d.foam_amount = rx::Min(speed * (k == 0 ? 0.7f : 0.45f), 1.6f); d.velocity_x = hull_vel_.x; d.velocity_z = hull_vel_.z; view.water_disturbances.push_back(d); @@ -781,18 +781,18 @@ void ShipDemo::EmitWake(render::FrameView& view) { void ShipDemo::FollowCamera() { // Unattended framing: a 3/4 stern chase unless the player is steering. if (ctx_.actions) { - f32 mag = std::fabs(ctx_.actions->axis(Axis::kLookX)) + - std::fabs(ctx_.actions->axis(Axis::kLookY)) + - std::fabs(ctx_.actions->axis(Axis::kMoveX)) + - std::fabs(ctx_.actions->axis(Axis::kMoveY)); + f32 mag = ::fabs(ctx_.actions->axis(Axis::kLookX)) + + ::fabs(ctx_.actions->axis(Axis::kLookY)) + + ::fabs(ctx_.actions->axis(Axis::kMoveX)) + + ::fabs(ctx_.actions->axis(Axis::kMoveY)); if (mag > 0.05f) return; // hand control to the fly camera } Vec3 offset = Rotate(hull_rot_, Vec3{-13.0f, 8.5f, -14.0f}); Vec3 cam = hull_pos_ + offset; Vec3 target = hull_pos_ + Vec3{0, 4.0f, 2.0f}; Vec3 dir = Normalize(target - cam); - f32 yaw = std::atan2(dir.x, -dir.z); - f32 pitch = std::asin(std::clamp(dir.y, -1.0f, 1.0f)); + f32 yaw = ::atan2(dir.x, -dir.z); + f32 pitch = ::asin(rx::Clamp(dir.y, -1.0f, 1.0f)); camera_.set_position(cam); camera_.set_yaw_pitch(yaw, pitch); } diff --git a/runtime/demo_shooter.cc b/runtime/demo_shooter.cc index aee69016..f43b8e3e 100644 --- a/runtime/demo_shooter.cc +++ b/runtime/demo_shooter.cc @@ -1,15 +1,16 @@ #include "demo_shooter.h" -#include -#include -#include -#include +#include +#include +#include #include "asset/material.h" #include "asset/primitives.h" +#include "base/containers/span.h" #include "combat/projectile.h" #include "character/character.h" #include "core/log.h" +#include "core/scalar.h" #include "ecs/world.h" #include "scene/camera.h" #include "scene/camera_rig.h" @@ -26,8 +27,8 @@ namespace rx { namespace { f32 ShooterFixedStep() { - if (const char* env = std::getenv("RX_FIXED_DT")) { - const f32 v = std::strtof(env, nullptr); + if (const char* env = ::getenv("RX_FIXED_DT")) { + const f32 v = ::strtof(env, nullptr); if (v > 0.0f) return v; } return 1.0f / 60.0f; @@ -49,7 +50,7 @@ Mat4 BoxTransform(const Vec3& center, const Vec3& half_extent) { Quat HeadingQuat(f32 yaw) { return QuatFromAxisAngle({0, -1, 0}, yaw); } u32 Rgba(u32 rgb, f32 alpha) { - const u32 a = static_cast(std::clamp(alpha, 0.0f, 1.0f) * 255.0f); + const u32 a = static_cast(rx::Clamp(alpha, 0.0f, 1.0f) * 255.0f); return (rgb << 8) | a; } @@ -85,8 +86,8 @@ void ShooterDemo::Create() { BuildWeapons(); BuildPlayer(); - if (const char* autofire = std::getenv("RX_SHOOTER_AUTOFIRE")) { - autofire_ = std::strtol(autofire, nullptr, 10) != 0; + if (const char* autofire = ::getenv("RX_SHOOTER_AUTOFIRE")) { + autofire_ = ::strtol(autofire, nullptr, 10) != 0; if (autofire_) { mouse_captured_ = false; // scripted run: leave the cursor alone RX_INFO("shooter: RX_SHOOTER_AUTOFIRE, holding the trigger on the nearest target"); @@ -275,7 +276,7 @@ void ShooterDemo::BuildPlayer() { character::CharacterShape shape; character::CharacterMovementSettings move; const f32 radius = shape.standing_radius; - const f32 half_height = std::max(shape.standing_height * 0.5f - radius, 0.01f); + const f32 half_height = rx::Max(shape.standing_height * 0.5f - radius, 0.01f); player_ = world.Create(); world.Add(player_, scene::Transform{.position = {spawn_feet_.x, spawn_feet_.y, spawn_feet_.z}}); @@ -469,7 +470,7 @@ void ShooterDemo::StepTargets(f32 dt) { target.strafe_phase += dt * 0.7f; Vec3 position = target.home; if (target.strafe_span > 0) { - position.x += std::sin(target.strafe_phase) * target.strafe_span; + position.x += ::sin(target.strafe_phase) * target.strafe_span; } // A downed target drops through the floor and pops back up on respawn, // which also takes its hitboxes out of the line of fire. @@ -508,7 +509,7 @@ void ShooterDemo::DrainEvents() { ++hits_; hitmarker_ = 0.12f; char text[32]; - std::snprintf(text, sizeof(text), "%d", static_cast(damage.applied + 0.5f)); + ::snprintf(text, sizeof(text), "%d", static_cast(damage.applied + 0.5f)); const u32 color = damage.zone == combat::HitZone::kHead ? 0xffd24a : 0xffffff; popups_.push_back(Popup{damage.position, text, Rgba(color, 1.0f), 1.0f}); } @@ -534,7 +535,7 @@ void ShooterDemo::AgePresentation(f32 dt) { AgeOut(tracers_, dt); AgeOut(marks_, dt); AgeOut(popups_, dt); - hitmarker_ = std::max(0.0f, hitmarker_ - dt); + hitmarker_ = rx::Max(0.0f, hitmarker_ - dt); for (Popup& popup : popups_) popup.position.y += dt * 0.35f; } @@ -555,7 +556,7 @@ void ShooterDemo::Update(f32 dt, const InputState& input, const ActionState& act if (input.key_pressed(Key::kM)) show_panel_ = !show_panel_; if (input.wheel != 0) { if (auto* loadout = world.Get(player_)) { - const int count = std::max(loadout->count, 1); + const int count = rx::Max(loadout->count, 1); const int step = input.wheel > 0 ? 1 : -1; pending_switch_ = static_cast((loadout->active + count + step) % count); } @@ -572,7 +573,7 @@ void ShooterDemo::Update(f32 dt, const InputState& input, const ActionState& act const f32 fixed = ShooterFixedStep(); FillLookAndMove(input, actions, allow_keyboard, allow_mouse, dt); - sim_accum_ += std::min(dt, 0.25f); + sim_accum_ += rx::Min(dt, 0.25f); int steps = 0; while (sim_accum_ >= fixed) { sim_accum_ -= fixed; @@ -711,7 +712,7 @@ void ShooterDemo::Emit(f32 dt, render::FrameView& view) { const f32 wounded = health->max_hp > 0 ? health->hp / health->max_hp : 0.0f; // The torso's red dims as it takes damage, so a half-dead target reads at a // glance; the head stays bright, because it is the thing worth hitting. - const u32 red = static_cast(60.0f + 150.0f * std::clamp(wounded, 0.0f, 1.0f)); + const u32 red = static_cast(60.0f + 150.0f * rx::Clamp(wounded, 0.0f, 1.0f)); const u32 torso_tint = health->dead ? 0x2a2c30 : (red << 16) | 0x1c1c; render::DrawItem torso{}; torso.mesh = cube_mesh_; @@ -733,7 +734,7 @@ void ShooterDemo::Emit(f32 dt, render::FrameView& view) { render::DrawItem draw{}; draw.mesh = sphere_mesh_; draw.transform = MakeTranslation(round.position) * - MakeScale(std::max(round.radius, 0.08f) * 2.0f); + MakeScale(rx::Max(round.radius, 0.08f) * 2.0f); draw.prev_transform = draw.transform; draw.tint = 0x30d040; view.draws.push_back(draw); @@ -754,7 +755,7 @@ void ShooterDemo::Emit(f32 dt, render::FrameView& view) { for (const Tracer& tracer : tracers_) { lines_.push_back(render::DebugLine{tracer.from, tracer.to, Rgba(0xffd070, tracer.life / 0.05f)}); } - view.debug_lines = std::span(lines_.begin(), lines_.size()); + view.debug_lines = base::Span(lines_.begin(), lines_.size()); for (const Popup& popup : popups_) { render::WorldText text; @@ -786,10 +787,10 @@ void ShooterDemo::DrawHud() { const ImVec2 middle{size.x * 0.5f, size.y * 0.5f}; if (def && weapon && cam_fov_ > 0) { const f32 spread = combat::EffectiveSpread(*def, *weapon, *intent); - const f32 pixels = std::clamp( - std::tan(spread) / std::tan(cam_fov_ * 0.5f) * size.y * 0.5f, 3.0f, size.y * 0.45f); + const f32 pixels = rx::Clamp( + ::tan(spread) / ::tan(cam_fov_ * 0.5f) * size.y * 0.5f, 3.0f, size.y * 0.45f); const u32 color = IM_COL32(235, 240, 245, 210); - const f32 arm = std::max(4.0f, pixels * 0.35f); + const f32 arm = rx::Max(4.0f, pixels * 0.35f); draw->AddLine({middle.x - pixels - arm, middle.y}, {middle.x - pixels, middle.y}, color, 1.6f); draw->AddLine({middle.x + pixels, middle.y}, {middle.x + pixels + arm, middle.y}, color, 1.6f); draw->AddLine({middle.x, middle.y - pixels - arm}, {middle.x, middle.y - pixels}, color, 1.6f); diff --git a/runtime/demo_shooter.h b/runtime/demo_shooter.h index 6806e2e6..d2e621e0 100644 --- a/runtime/demo_shooter.h +++ b/runtime/demo_shooter.h @@ -1,10 +1,10 @@ #ifndef RX_RUNTIME_DEMO_SHOOTER_H_ #define RX_RUNTIME_DEMO_SHOOTER_H_ -#include #include +#include "base/strings/xstring.h" #include "combat/damage.h" #include "combat/events.h" #include "combat/weapon.h" @@ -92,7 +92,7 @@ class ShooterDemo { }; struct Popup { Vec3 position; - std::string text; + base::String text; u32 rgba = 0xffffffff; f32 life = 0; }; diff --git a/runtime/demo_world.cc b/runtime/demo_world.cc index b4fcb7ee..ac983bad 100644 --- a/runtime/demo_world.cc +++ b/runtime/demo_world.cc @@ -1,9 +1,13 @@ #include "demo_world.h" -#include +#include #include "asset/pack.h" #include "asset/primitives.h" +#include "base/containers/span.h" +#include "base/memory/move.h" +#include "base/strings/xstring.h" +#include "core/format.h" #include "core/log.h" #include "scene/components.h" #include "world/world_bake.h" @@ -57,8 +61,8 @@ void WorldStreamDemo::RegisterMeshes(render::Renderer* renderer, bool headless) } bool WorldStreamDemo::Init(asset::Vfs& vfs, render::Renderer* renderer, ecs::World& ecs, - bool headless, const std::string& archive_path, - const std::string& world_name) { + bool headless, const base::String& archive_path, + const base::String& world_name) { // The cook's own default, so an archive baked without --name opens without // --world-name. Asking world_bake for it rather than reimplementing the stem // is the point: two answers here would be two answers about where the index @@ -70,10 +74,10 @@ bool WorldStreamDemo::Init(asset::Vfs& vfs, render::Renderer* renderer, ecs::Wor RX_ERROR("--world {}: not a readable .rxp archive", archive_path); return false; } - vfs.Mount("world", std::move(provider)); + vfs.Mount("world", base::move(provider)); - const std::string index_path = "world://" + world_name_ + "/" + world_name_ + ".rxworld"; - std::string error; + const base::String index_path = "world://" + world_name_ + "/" + world_name_ + ".rxworld"; + base::String error; if (!map_.Load(vfs, index_path, &error)) { RX_ERROR("--world {}: {}", archive_path, error); return false; @@ -118,11 +122,11 @@ void WorldStreamDemo::Update(f32 frame_delta, const Vec3& camera_position) { // The world is a plane; height should not decide what is resident. observer.axes = scene::kWorldStreamXZ; - streamer_->Update(std::span(&observer, 1)); + streamer_->Update(base::Span(&observer, 1)); // A refused payload is a cook or archive bug, so say so once rather than // every frame for as long as the cell stays in range. - const std::span errors = streamer_->errors(); + const base::Span errors = streamer_->errors(); for (size_t i = reported_errors_; i < errors.size(); ++i) RX_ERROR("world: {}", errors[i]); reported_errors_ = errors.size(); } @@ -132,15 +136,15 @@ void WorldStreamDemo::EmitToView(render::FrameView& view) { streamer_->ResidentCells(world::Domain::kRepresentation, &resident_scratch_); for (u64 cell : resident_scratch_) { - const std::span instances = streamer_->Instances(cell); + const base::Span instances = streamer_->Instances(cell); if (instances.empty()) continue; // The page stores a prototype index; the names it indexes are the cell's, // and the mesh each resolves to is this host's business. - const std::span prototypes = streamer_->Prototypes(cell); + const base::Span prototypes = streamer_->Prototypes(cell); prototype_ids_scratch_.clear(); prototype_ids_scratch_.reserve(prototypes.size()); - for (const std::string& name : prototypes) { + for (const base::String& name : prototypes) { prototype_ids_scratch_.push_back(asset::MakeAssetId(name).hash); } @@ -166,19 +170,19 @@ void WorldStreamDemo::Shutdown() { loader_.Reset(); } -std::string WorldStreamDemo::StatusLine() const { +base::String WorldStreamDemo::StatusLine() const { if (!streamer_) return {}; const world::WorldStreamerStats stats = streamer_->stats(); // Resident is (cell, domain) pairs, not cells: the whole point is that a // cell's domains come and go independently, so one number for "cells" would // be the wrong shape. - std::string line = "WORLD " + std::to_string(stats.resident) + "/" + - std::to_string(map_.index().cells.size() * 2) + " res"; - if (stats.pending != 0) line += " +" + std::to_string(stats.pending); - line += " " + std::to_string(stats.entities) + "e " + std::to_string(stats.instances) + "i " + - std::to_string(stats.resident_bytes / 1024) + "K"; - if (stats.suppressed != 0) line += " " + std::to_string(stats.suppressed) + " SUPPRESSED"; - if (stats.errors_total != 0) line += " " + std::to_string(stats.errors_total) + " ERR"; + base::String line = "WORLD " + rx::ToString(stats.resident) + "/" + + rx::ToString(map_.index().cells.size() * 2) + " res"; + if (stats.pending != 0) line += " +" + rx::ToString(stats.pending); + line += " " + rx::ToString(stats.entities) + "e " + rx::ToString(stats.instances) + "i " + + rx::ToString(stats.resident_bytes / 1024) + "K"; + if (stats.suppressed != 0) line += " " + rx::ToString(stats.suppressed) + " SUPPRESSED"; + if (stats.errors_total != 0) line += " " + rx::ToString(stats.errors_total) + " ERR"; return line; } diff --git a/runtime/demo_world.h b/runtime/demo_world.h index a739d4c2..4d3cca4e 100644 --- a/runtime/demo_world.h +++ b/runtime/demo_world.h @@ -1,12 +1,12 @@ #ifndef RX_RUNTIME_DEMO_WORLD_H_ #define RX_RUNTIME_DEMO_WORLD_H_ -#include #include #include #include "asset/vfs.h" +#include "base/strings/xstring.h" #include "ecs/world.h" #include "render/core/renderer.h" #include "world/world_claim.h" @@ -38,7 +38,7 @@ class WorldStreamDemo { // will not load. `world_name` is the name the archive was baked under, which // is also the directory the index sits in. bool Init(asset::Vfs& vfs, render::Renderer* renderer, ecs::World& ecs, bool headless, - const std::string& archive_path, const std::string& world_name); + const base::String& archive_path, const base::String& world_name); bool active() const { return streamer_ != nullptr; } @@ -56,7 +56,7 @@ class WorldStreamDemo { void Shutdown(); // Overlay line: what is resident, what is in flight, what failed. - std::string StatusLine() const; + base::String StatusLine() const; private: void RegisterMeshes(render::Renderer* renderer, bool headless); @@ -65,7 +65,7 @@ class WorldStreamDemo { base::UniquePointer loader_; base::UniquePointer streamer_; world::ClaimSet claims_; - std::string world_name_; + base::String world_name_; Vec3 previous_position_; bool have_previous_ = false; diff --git a/runtime/engine_context.h b/runtime/engine_context.h index 9930bab2..61fc9b90 100644 --- a/runtime/engine_context.h +++ b/runtime/engine_context.h @@ -1,7 +1,6 @@ #ifndef RX_RUNTIME_ENGINE_CONTEXT_H_ #define RX_RUNTIME_ENGINE_CONTEXT_H_ -#include #include @@ -9,6 +8,7 @@ #include "asset/asset_database.h" #include "asset/vfs.h" #include "audio/audio_system.h" +#include "base/strings/xstring.h" #include "core/input_actions.h" #include "core/math.h" #include "debug_ui.h" @@ -25,8 +25,8 @@ namespace rx { // The viewer's boot configuration: the app::AppConfig fields plus the // front-door content selection (main.cc maps the overlap into AppConfig). struct EngineConfig { - std::string scene_path; // standalone gltf/glb or usd scene (e.g. sponza) - std::string demo_scene; // builtin demo scene id ("water", "materials", ...) + base::String scene_path; // standalone gltf/glb or usd scene (e.g. sponza) + base::String demo_scene; // builtin demo scene id ("water", "materials", ...) // Prim paths whose authored `visibility` is overridden when loading a usd // stage. Scenes ship alternative configurations (day/night lighting rigs, // set dressing variants) toggled by visibility, and picking one is a @@ -46,7 +46,7 @@ struct EngineConfig { // --shot: write the frame after `shot_frames` as a png and quit. Empty falls // back to the RX_UI_SHOT env var (viewer.cc), which existing capture scripts // drive; 0 frames falls back to RX_UI_SHOT_FRAMES. - std::string shot_path; + base::String shot_path; int shot_frames = 0; // --camera-at / --camera-look / --camera-fov: override the scene's own // viewpoint for this run without editing it. Looking at an authored scene @@ -55,8 +55,8 @@ struct EngineConfig { // the side, a street with nothing in its middle distance - and having to edit // the file to do it means the check costs an edit to undo, so it does not get // made. Empty leaves the scene's Camera alone; `camera_fov` 0 does the same. - std::string camera_at; - std::string camera_look; + base::String camera_at; + base::String camera_look; f32 camera_fov = 0.0f; // --world : mount a baked world and stream it around the camera // instead of loading a scene whole. `world_name` is the name it was cooked @@ -67,13 +67,13 @@ struct EngineConfig { // the editor's Bake World opens with a bare `--world`. Only an archive baked // under a name that is not its filename, or one holding more than one world, // needs --world-name to say which. - std::string world_path; - std::string world_name; + base::String world_path; + base::String world_name; // --authoring-endpoint: serve the script commands on this unix socket path // while the engine runs, so a tool can spawn/move/inspect without a restart. // Empty (the default) means no endpoint is bound at all; see // authoring/command_bridge.h for why that is not merely a convenience. - std::string authoring_socket; + base::String authoring_socket; }; // A dynamic physics body the host mirrors into an ECS transform after each diff --git a/runtime/feature_gym/feature_gym.cc b/runtime/feature_gym/feature_gym.cc index d2afc647..9b075400 100644 --- a/runtime/feature_gym/feature_gym.cc +++ b/runtime/feature_gym/feature_gym.cc @@ -1,17 +1,9 @@ #include "feature_gym.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include "../engine_context.h" #include "../showcase_camera.h" @@ -26,6 +18,8 @@ #include "asset/texture.h" #include "audio/audio_clip.h" #include "audio/mixer.h" +#include "core/file_system.h" +#include "core/shared.h" #include "core/log.h" #include "core/math.h" #include "core/paths.h" @@ -55,6 +49,17 @@ #include #endif +#include "base/algorithm.h" +#include "base/containers/array.h" +#include "base/containers/pair.h" +#include "base/containers/span.h" +#include "base/containers/vector.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/format.h" +#include "core/scalar.h" #include "net/bubble.h" #include "net/bubble_debug.h" @@ -230,21 +235,21 @@ asset::Mesh WithMaterial(asset::Mesh mesh, asset::AssetId material) { return mesh; } -std::filesystem::path FeatureAssetPath(const char* file) { - if (const char* root = std::getenv("RX_FEATURE_GYM_ASSET_DIR")) - return std::filesystem::path(root) / file; - return std::filesystem::path(RX_FEATURE_GYM_ASSET_DIR) / file; +base::String FeatureAssetPath(const char* file) { + if (const char* root = ::getenv("RX_FEATURE_GYM_ASSET_DIR")) + return fs::Join(root, file); + return fs::Join(RX_FEATURE_GYM_ASSET_DIR, file); } -std::vector ReadBytes(const std::filesystem::path& requested_path) { +base::Vector ReadBytes(const base::String& requested_path) { #if defined(__ANDROID__) if (g_feature_gym_asset_manager) { - const std::string name = requested_path.filename().string(); + const base::String name(fs::Filename(requested_path)); AAsset* asset = AAssetManager_open(g_feature_gym_asset_manager, name.c_str(), AASSET_MODE_STREAMING); if (asset) { const off64_t size = AAsset_getLength64(asset); - std::vector bytes(size > 0 ? static_cast(size) : 0); + base::Vector bytes(size > 0 ? static_cast(size) : 0); const int read = bytes.empty() ? 0 : AAsset_read(asset, bytes.data(), bytes.size()); AAsset_close(asset); if (read == static_cast(bytes.size())) return bytes; @@ -252,20 +257,13 @@ std::vector ReadBytes(const std::filesystem::path& requested_path) { } #endif - std::filesystem::path path = requested_path; - std::ifstream input(path, std::ios::binary | std::ios::ate); - if (!input && path.is_relative()) { - path = ExecutableDirectory() / path; - input = std::ifstream(path, std::ios::binary | std::ios::ate); - } - if (!input) return {}; - const std::streamsize size = input.tellg(); - if (size <= 0) return {}; - std::vector bytes(static_cast(size)); - input.seekg(0); - input.read(reinterpret_cast(bytes.data()), size); - if (!input) return {}; - return bytes; + base::Vector bytes; + if (fs::ReadFile(requested_path, &bytes)) return bytes; + if (!fs::IsAbsolute(requested_path) && + fs::ReadFile(fs::Join(ExecutableDirectory(), requested_path), &bytes)) { + return bytes; + } + return {}; } asset::Texture LoadRawTexture(const char* file, const char* id, bool srgb) { @@ -275,11 +273,11 @@ asset::Texture LoadRawTexture(const char* file, const char* id, bool srgb) { texture.width = kTextureSize; texture.height = kTextureSize; texture.is_srgb = srgb; - const std::filesystem::path path = FeatureAssetPath(file); - std::vector bytes = ReadBytes(path); + const base::String path = FeatureAssetPath(file); + base::Vector bytes = ReadBytes(path); const size_t expected = static_cast(kTextureSize) * kTextureSize * 4; if (bytes.size() != expected) { - RX_WARN("feature gym: {} has {} bytes, expected {} (run generate_assets.py)", path.string(), + RX_WARN("feature gym: {} has {} bytes, expected {} (run generate_assets.py)", path, bytes.size(), expected); return texture; } @@ -287,28 +285,28 @@ asset::Texture LoadRawTexture(const char* file, const char* id, bool srgb) { return texture; } -std::vector BuildRgbaMipChain(const asset::Texture& texture) { +base::Vector BuildRgbaMipChain(const asset::Texture& texture) { const size_t expected = static_cast(texture.width) * texture.height * 4; if (texture.width != texture.height || texture.data.size() != expected || texture.width == 0) return {}; u32 extent = texture.width; - std::vector level(texture.data.begin(), texture.data.end()); - std::vector mips; + base::Vector level(texture.data.begin(), texture.data.end()); + base::Vector mips; mips.reserve(expected * 4 / 3); while (true) { mips.insert(mips.end(), level.begin(), level.end()); if (extent == 1) break; - const u32 next_extent = std::max(1u, extent / 2); - std::vector next(static_cast(next_extent) * next_extent * 4); + const u32 next_extent = rx::Max(1u, extent / 2); + base::Vector next(static_cast(next_extent) * next_extent * 4); for (u32 y = 0; y < next_extent; ++y) { for (u32 x = 0; x < next_extent; ++x) { for (u32 channel = 0; channel < 4; ++channel) { u32 sum = 0; for (u32 oy = 0; oy < 2; ++oy) { for (u32 ox = 0; ox < 2; ++ox) { - const u32 sx = std::min(extent - 1, x * 2 + ox); - const u32 sy = std::min(extent - 1, y * 2 + oy); + const u32 sx = rx::Min(extent - 1, x * 2 + ox); + const u32 sy = rx::Min(extent - 1, y * 2 + oy); sum += level[(static_cast(sy) * extent + sx) * 4 + channel]; } } @@ -317,7 +315,7 @@ std::vector BuildRgbaMipChain(const asset::Texture& texture) { } } } - level = std::move(next); + level = base::move(next); extent = next_extent; } return mips; @@ -506,8 +504,8 @@ asset::Mesh MakeTree(asset::AssetId id, asset::AssetId material) { for (int side = 0; side < 10; ++side) { const f32 a0 = side * 2.0f * kPi / 10.0f; const f32 a1 = (side + 1) * 2.0f * kPi / 10.0f; - triangle({std::cos(a0) * radius, base, std::sin(a0) * radius}, {0, tip, 0}, - {std::cos(a1) * radius, base, std::sin(a1) * radius}, + triangle({::cos(a0) * radius, base, ::sin(a0) * radius}, {0, tip, 0}, + {::cos(a1) * radius, base, ::sin(a1) * radius}, side & 1 ? 0xff245a27 : 0xff377a31); } } @@ -580,7 +578,7 @@ struct FeatureGym::Impl { void CreatePost(); void CreateCameraExhibit(); void CreateLabels(); - void AddLabel(const Vec3& anchor, std::string_view text, f32 height, u32 color); + void AddLabel(const Vec3& anchor, base::StringRef text, f32 height, u32 color); void StartAudio(); void AddSimulation(); void Emit(f32 dt, render::FrameView& view); @@ -601,13 +599,13 @@ struct FeatureGym::Impl { void ApplyTourMode(TourMode mode); bool SoftwareGiOnly() const; - asset::AssetId AddMaterial(const std::string& name, + asset::AssetId AddMaterial(const base::String& name, Vec3 color, f32 roughness = 0.7f, f32 metallic = 0.0f); asset::AssetId UploadMesh(asset::Mesh mesh, asset::AssetId material, bool trace = false); ecs::Entity Spawn(asset::AssetId mesh, Vec3 position, f32 scale = 1.0f, Quat rotation = {}); - ecs::Entity SpawnBox(const std::string& name, + ecs::Entity SpawnBox(const base::String& name, Vec3 half_extent, Vec3 position, asset::AssetId material, @@ -630,8 +628,8 @@ struct FeatureGym::Impl { f32 time = 0; TourMode mode = TourMode::kOverview; }; - std::vector activations; - size_t applied_activation = std::numeric_limits::max(); + base::Vector activations; + size_t applied_activation = SIZE_MAX; asset::AssetId checker{}; asset::AssetId albedo{}; @@ -653,14 +651,14 @@ struct FeatureGym::Impl { // transforms cannot leak into any other stop's capture. u64 skew_sphere_mesh = 0; u64 skew_torus_mesh = 0; - std::vector virtual_geometry_albedo; + base::Vector virtual_geometry_albedo; base::Vector lights; base::Vector decals; base::Vector oit; base::Vector gaussians; base::Vector lines; - std::vector labels; + base::Vector labels; u64 morph_mesh = 0; Mat4 morph_transform = Mat4::Identity(); @@ -680,7 +678,7 @@ struct FeatureGym::Impl { base::Vector strand_positions; net::InterestMap bubble_map; - std::unique_ptr bubble_viz; + base::UniquePointer bubble_viz; u64 bubble_tick = 0; ecs::Entity camera_base_mode{}; @@ -734,16 +732,19 @@ struct FeatureGym::Impl { u64 bike_mesh = 0; Mat4 car_previous = Mat4::Identity(); Mat4 bike_previous = Mat4::Identity(); - std::array car_wheel_previous{}; - std::array bike_wheel_previous{}; + base::Array car_wheel_previous{}; + base::Array bike_wheel_previous{}; bool car_previous_valid = false; bool bike_previous_valid = false; - std::array car_wheel_previous_valid{}; - std::array bike_wheel_previous_valid{}; + base::Array car_wheel_previous_valid{}; + base::Array bike_wheel_previous_valid{}; f32 physics_active_time = 0; f32 sim_time = 0; int jump_cycle = -1; - std::shared_ptr simulation_alive = std::make_shared(true); + // Shared, not owned: the scheduler (Host-owned) has no RemoveSystem and keeps + // copies of the gym's system closures after ~Impl, so the flag they check + // must outlive this object. + Shared simulation_alive = Shared::Make(true); base::Vector water_bodies; base::Vector water_previous; @@ -761,14 +762,14 @@ struct FeatureGym::Impl { f32 circuit_time = 0; Mat4 circuit_previous = Mat4::Identity(); bool circuit_previous_valid = false; - std::array circuit_wheel_previous{}; - std::array circuit_wheel_previous_valid{}; + base::Array circuit_wheel_previous{}; + base::Array circuit_wheel_previous_valid{}; // Marina: a force-simulated physics::Boat that floats on the water-district // lake. Update() must run every fixed step to keep it buoyant (the hull is // exempt from the world's generic buoyancy); it idles at its berth and runs a // slow circuit during the boat stop. Meshes are a hull + cabin box. - std::unique_ptr boat; + base::UniquePointer boat; u64 boat_hull_mesh = 0; u64 boat_cabin_mesh = 0; Vec3 boat_berth{}; @@ -787,7 +788,7 @@ struct FeatureGym::Impl { // around a circle at altitude over the circuit (closed-loop heading + altitude // hold), then falls away unseen once the stop ends. Update() runs every step // so the gear holds it on the apron while idle. - std::unique_ptr aircraft; + base::UniquePointer aircraft; u64 plane_body_mesh = 0; u64 plane_wing_mesh = 0; u64 plane_tail_mesh = 0; @@ -807,8 +808,8 @@ FeatureGym::Impl::~Impl() { if (audio_voice && ctx.audio) ctx.audio->Stop(audio_voice, 0); if (camera_activation) scene::ReleaseCameraMode(world, camera_activation, {.duration = 0}); if (cloth_id) physics.RemoveCloth(cloth_id); - boat.reset(); // removes the hull body while `physics` is still alive - aircraft.reset(); // removes the fuselage body while `physics` is still alive + boat.Reset(); // removes the hull body while `physics` is still alive + aircraft.Reset(); // removes the fuselage body while `physics` is still alive if (car) physics.RemoveVehicle(car); if (bike) physics.RemoveVehicle(bike); if (circuit_car) physics.RemoveVehicle(circuit_car); @@ -826,7 +827,7 @@ bool FeatureGym::Impl::SoftwareGiOnly() const { (ctx.config->renderer.software_gi_fallback && !renderer.raytracing_available()); } -asset::AssetId FeatureGym::Impl::AddMaterial(const std::string& name, +asset::AssetId FeatureGym::Impl::AddMaterial(const base::String& name, Vec3 color, f32 roughness, f32 metallic) { @@ -842,7 +843,7 @@ asset::AssetId FeatureGym::Impl::AddMaterial(const std::string& name, } asset::AssetId FeatureGym::Impl::UploadMesh(asset::Mesh mesh, asset::AssetId material, bool trace) { - mesh = WithMaterial(std::move(mesh), material); + mesh = WithMaterial(base::move(mesh), material); if (SoftwareGiOnly() && !trace) mesh.exclude_from_rt = true; const asset::AssetId id = mesh.id; if (!headless) renderer.UploadMesh(mesh); @@ -858,7 +859,7 @@ ecs::Entity FeatureGym::Impl::Spawn(asset::AssetId mesh, Vec3 position, f32 scal return entity; } -ecs::Entity FeatureGym::Impl::SpawnBox(const std::string& name, +ecs::Entity FeatureGym::Impl::SpawnBox(const base::String& name, Vec3 half_extent, Vec3 position, asset::AssetId material, @@ -949,7 +950,7 @@ void FeatureGym::Impl::Create() { ctx.camera->set_position({0, 50, 56}); const Vec3 direction = Normalize(Vec3{0, 0, -14} - ctx.camera->position()); - ctx.camera->set_yaw_pitch(std::atan2(direction.x, -direction.z), std::asin(direction.y)); + ctx.camera->set_yaw_pitch(::atan2(direction.x, -direction.z), ::asin(direction.y)); ctx.camera->speed = 15.0f; RX_INFO( "feature gym: nine self-contained districts ready; use RX_SHOWCASE=1 " @@ -970,14 +971,14 @@ void FeatureGym::Impl::CreateBase() { // Floor spans the 3x3 district grid plus the driving circuit apron to the // south (z down to ~-100). SpawnBox("world_floor", {62, 0.25f, 72}, {0, -2.65f, -31}, floor.id, false, true); - for (size_t i = 0; i < std::size(kAreas); ++i) { + for (size_t i = 0; i < (sizeof(kAreas) / sizeof(kAreas[0])); ++i) { const AreaInfo& info = kAreas[i]; const Vec3 tint{static_cast((info.color >> 24) & 0xff) / 255.0f, static_cast((info.color >> 16) & 0xff) / 255.0f, static_cast((info.color >> 8) & 0xff) / 255.0f}; if (static_cast(i + 1) != Area::kWater) { - asset::AssetId pad = AddMaterial("pad_" + std::to_string(i), tint * 0.22f, 0.88f); - SpawnBox("pad_" + std::to_string(i), {16.0f, 0.08f, 13.5f}, info.center + Vec3{0, 0.02f, 0}, + asset::AssetId pad = AddMaterial("pad_" + rx::ToString(i), tint * 0.22f, 0.88f); + SpawnBox("pad_" + rx::ToString(i), {16.0f, 0.08f, 13.5f}, info.center + Vec3{0, 0.02f, 0}, pad, false, true); physics.AddStaticBox(info.center + Vec3{0, -0.25f, 0}, {16.0f, 0.25f, 13.5f}); } @@ -986,11 +987,11 @@ void FeatureGym::Impl::CreateBase() { const f32 x1 = info.center.x + 15.8f; const f32 z0 = info.center.z - 13.3f; const f32 z1 = info.center.z + 13.3f; - for (const auto& edge : std::array, 4>{ - std::pair{Vec3{x0, 0.14f, z0}, Vec3{x1, 0.14f, z0}}, - std::pair{Vec3{x1, 0.14f, z0}, Vec3{x1, 0.14f, z1}}, - std::pair{Vec3{x1, 0.14f, z1}, Vec3{x0, 0.14f, z1}}, - std::pair{Vec3{x0, 0.14f, z1}, Vec3{x0, 0.14f, z0}}, + for (const auto& edge : base::Array, 4>{ + base::Pair{Vec3{x0, 0.14f, z0}, Vec3{x1, 0.14f, z0}}, + base::Pair{Vec3{x1, 0.14f, z0}, Vec3{x1, 0.14f, z1}}, + base::Pair{Vec3{x1, 0.14f, z1}, Vec3{x0, 0.14f, z1}}, + base::Pair{Vec3{x0, 0.14f, z1}, Vec3{x0, 0.14f, z0}}, }) lines.push_back({edge.first, edge.second, info.color}); } @@ -1002,11 +1003,11 @@ void FeatureGym::Impl::CreateMaterials() { const Vec3 c = Info(Area::kMaterials).center; int index = 0; auto sphere = [&](Vec3 position, asset::Material material) { - const std::string stem = "material_" + std::to_string(index++); + const base::String stem = "material_" + rx::ToString(index++); material.id = asset::MakeAssetId("featuregym/material/" + stem); asset::Mesh mesh = asset::MakeSphere(0.72f, 28, 40, asset::MakeAssetId("featuregym/mesh/" + stem)); - mesh = WithMaterial(std::move(mesh), material.id); + mesh = WithMaterial(base::move(mesh), material.id); if (SoftwareGiOnly()) mesh.exclude_from_rt = true; if (!headless) { renderer.UploadMaterial(material); @@ -1214,12 +1215,12 @@ void FeatureGym::Impl::CreateLighting() { asset::AssetId white = AddMaterial("light_receiver", {0.30f, 0.31f, 0.34f}, 0.48f); for (int i = 0; i < 7; ++i) { asset::Mesh sphere = asset::MakeSphere( - 0.65f, 20, 28, asset::MakeAssetId("featuregym/mesh/light_receiver_" + std::to_string(i))); - const asset::AssetId id = UploadMesh(std::move(sphere), white, true); + 0.65f, 20, 28, asset::MakeAssetId("featuregym/mesh/light_receiver_" + rx::ToString(i))); + const asset::AssetId id = UploadMesh(base::move(sphere), white, true); Spawn(id, c + Vec3{-9 + i * 3.0f, 0.75f, -1.0f + (i & 1) * 2.0f}); } for (int i = 0; i < 6; ++i) - SpawnBox("light_pillar_" + std::to_string(i), {0.22f, 1.2f, 0.35f}, + SpawnBox("light_pillar_" + rx::ToString(i), {0.22f, 1.2f, 0.35f}, c + Vec3{-7.5f + i * 3.0f, 1.2f, -3.0f}, white, false, true); const Vec3 colors[] = {{1, 0.12f, 0.08f}, {0.08f, 1, 0.20f}, {0.12f, 0.35f, 1}, @@ -1250,8 +1251,8 @@ void FeatureGym::Impl::CreateLighting() { spot.direction_type[1] = spot_direction.y; spot.direction_type[2] = spot_direction.z; spot.direction_type[3] = 1; - spot.params[0] = std::cos(0.28f); - spot.params[1] = std::cos(0.48f); + spot.params[0] = ::cos(0.28f); + spot.params[1] = ::cos(0.48f); lights.push_back(spot); render::PointLight sphere; @@ -1287,12 +1288,12 @@ void FeatureGym::Impl::CreateGeometry() { const Vec3 c = Info(Area::kGeometry).center; asset::AssetId blue = AddMaterial("geometry", {0.15f, 0.46f, 0.78f}, 0.38f); asset::Mesh lod = asset::MakeLodSphere(1.15f, asset::MakeAssetId("featuregym/mesh/lod")); - const asset::AssetId lod_id = UploadMesh(std::move(lod), blue); + const asset::AssetId lod_id = UploadMesh(base::move(lod), blue); for (int i = 0; i < 5; ++i) Spawn(lod_id, c + Vec3{-10 + i * 4.2f, 1.25f, 6}); asset::Mesh auto_lod = asset::MakeSphere(1.0f, 60, 90, asset::MakeAssetId("featuregym/mesh/auto_lod")); - auto_lod = WithMaterial(std::move(auto_lod), blue); + auto_lod = WithMaterial(base::move(auto_lod), blue); asset::GenerateLods(&auto_lod); if (SoftwareGiOnly()) auto_lod.exclude_from_rt = true; const asset::AssetId auto_id = auto_lod.id; @@ -1339,7 +1340,7 @@ void FeatureGym::Impl::CreateGeometry() { const f32 pz = (static_cast(z) / kGrid - 0.5f) * kVirtualGeometrySize; asset::Vertex vertex{}; vertex.position[0] = px; - vertex.position[1] = std::sin(px * 1.4f) * std::cos(pz * 1.2f) * 0.6f; + vertex.position[1] = ::sin(px * 1.4f) * ::cos(pz * 1.2f) * 0.6f; vertex.position[2] = pz; vertex.normal[1] = 1; terrain_lod.vertices.push_back(vertex); @@ -1362,7 +1363,7 @@ void FeatureGym::Impl::CreateGeometry() { 1.0f / kVirtualGeometrySize); } const Mat4 instance = MakeTranslation(c + Vec3{0, 0.4f, -6}); - renderer.SetVirtualGeometryInstances(std::span(&instance, 1)); + renderer.SetVirtualGeometryInstances(base::Span(&instance, 1)); } asset::Mesh tree = MakeTree(asset::MakeAssetId("featuregym/mesh/tree"), @@ -1372,7 +1373,7 @@ void FeatureGym::Impl::CreateGeometry() { if (!headless) { asset::Mesh prop = asset::MakeCube(0.36f, asset::MakeAssetId("featuregym/mesh/instance_prop")); - prop = WithMaterial(std::move(prop), + prop = WithMaterial(base::move(prop), AddMaterial("instance_prop", {0.06f, 0.72f, 0.92f}, 0.28f, 0.45f)); renderer.UploadMesh(prop); prop_mesh = prop.id.hash; @@ -1384,7 +1385,7 @@ void FeatureGym::Impl::CreateGeometry() { const Vec3 position = c + Vec3{-10.0f + column * 2.2f, 0.45f, -1.0f - row * 2.1f}; prop_transforms.push_back(MakeTranslation(position) * MakeScale(scale)); prop_updated_transforms.push_back( - MakeTranslation(position + Vec3{0, 0.35f + 0.12f * std::sin(column * 0.7f), -0.45f}) * + MakeTranslation(position + Vec3{0, 0.35f + 0.12f * ::sin(column * 0.7f), -0.45f}) * MakeFromQuat(QuatFromAxisAngle({0, 1, 0}, 0.18f * (row + column))) * MakeScale(scale)); } } @@ -1399,14 +1400,14 @@ void FeatureGym::Impl::CreateGeometry() { if (headless) return; const u32 baked = renderer.BakeImposter(tree); if (baked == render::ImposterPass::kNoMesh) return; - std::vector imposters; + base::Vector imposters; for (int i = 0; i < 80; ++i) { const f32 angle = i * 2.39996323f; const f32 radius = 10.0f + (i % 9) * 0.65f; render::ImposterPass::Instance instance; - instance.position[0] = c.x + std::cos(angle) * radius; + instance.position[0] = c.x + ::cos(angle) * radius; instance.position[1] = 0.1f; - instance.position[2] = c.z + std::sin(angle) * radius; + instance.position[2] = c.z + ::sin(angle) * radius; instance.scale = 0.65f + (i % 5) * 0.08f; instance.mesh = baked; imposters.push_back(instance); @@ -1419,7 +1420,7 @@ void FeatureGym::Impl::CreateAtmosphere() { asset::AssetId tower = AddMaterial("atmosphere_tower", {0.34f, 0.28f, 0.48f}, 0.78f); for (int i = 0; i < 9; ++i) { const f32 height_value = 1.2f + i * 0.65f; - SpawnBox("atmosphere_tower_" + std::to_string(i), {0.65f, height_value, 0.65f}, + SpawnBox("atmosphere_tower_" + rx::ToString(i), {0.65f, height_value, 0.65f}, c + Vec3{-9 + i * 2.25f, height_value, -5.5f}, tower); } asset::Material cloth; @@ -1438,7 +1439,7 @@ void FeatureGym::Impl::CreateAtmosphere() { asset::Mesh banner = MakeBanner(asset::MakeAssetId("featuregym/mesh/weather_banner"), cloth.id); if (!headless) renderer.UploadMesh(banner); for (int i = 0; i < 5; ++i) { - SpawnBox("weather_pole_" + std::to_string(i), {0.07f, 2.7f, 0.07f}, + SpawnBox("weather_pole_" + rx::ToString(i), {0.07f, 2.7f, 0.07f}, c + Vec3{-8 + i * 4.0f, 2.7f, 4.5f}, neutral_material); Spawn(banner.id, c + Vec3{-6.8f + i * 4.0f, 5.1f, 4.5f}); } @@ -1477,7 +1478,7 @@ void FeatureGym::Impl::CreateWater() { const f32 local_x = -kIslandRadius + 2.0f * kIslandRadius * static_cast(x) / kIslandGrid; const f32 local_z = -kIslandRadius + 2.0f * kIslandRadius * static_cast(z) / kIslandGrid; const f32 gaussian = - std::exp(-(local_x * local_x + local_z * local_z) / (2.0f * kIslandSigma * kIslandSigma)); + ::exp(-(local_x * local_x + local_z * local_z) / (2.0f * kIslandSigma * kIslandSigma)); const f32 slope = kIslandPeak * 2.0f * gaussian / (kIslandSigma * kIslandSigma); const Vec3 island_normal = Normalize(Vec3{slope * local_x, 1, slope * local_z}); asset::Vertex vertex{}; @@ -1510,7 +1511,7 @@ void FeatureGym::Impl::CreateWater() { Spawn(island.id, island_center); physics.set_water_height([this, c](const Vec3& p, f32* out, Vec3* flow) { - if (std::abs(p.x - c.x) > 14 || std::abs(p.z - c.z) > 14) return false; + if (::abs(p.x - c.x) > 14 || ::abs(p.z - c.z) > 14) return false; Vec3 orbital_flow{}; *out = water_height + physics::GerstnerWaveHeight(p.x, p.z, sim_time, &orbital_flow); if (flow) *flow = orbital_flow; @@ -1553,7 +1554,7 @@ void FeatureGym::Impl::CreateMarina() { // the cruise runs it gently so a hull that can only turn wide still stays on // the small lake. physics::BoatDesc desc = physics::SpeedboatProfile(); - boat = std::make_unique(physics, desc, boat_berth, 0.0f); + boat = base::MakeUnique(physics, desc, boat_berth, 0.0f); if (!boat->valid()) RX_WARN("feature gym: marina boat is static; rebuild with Jolt for buoyancy"); @@ -1589,9 +1590,9 @@ void FeatureGym::Impl::CreateEffects() { for (u32 i = 0; i < kSplatCount; ++i) { const f32 t = (i + 0.5f) / kSplatCount; const f32 y = 1.0f - 2.0f * t; - const f32 radius = std::sqrt(std::max(0.0f, 1.0f - y * y)); + const f32 radius = ::sqrt(rx::Max(0.0f, 1.0f - y * y)); const f32 angle = i * 2.39996323f; - const Vec3 direction{std::cos(angle) * radius, y, std::sin(angle) * radius}; + const Vec3 direction{::cos(angle) * radius, y, ::sin(angle) * radius}; render::GaussianInstance splat; splat.position[0] = c.x + 5.2f + direction.x * 1.6f; splat.position[1] = 2.0f + direction.y * 1.6f; @@ -1678,8 +1679,8 @@ void FeatureGym::Impl::CreateNetworkBubbles() { } bubble_map.Configure({.hysteresis = 1.15f, .cell_size = 4.0f}); if (!headless) { - bubble_viz = std::make_unique(); - if (!bubble_viz->Init(renderer)) bubble_viz.reset(); + bubble_viz = base::MakeUnique(); + if (!bubble_viz->Init(renderer)) bubble_viz.Reset(); } } @@ -1769,10 +1770,10 @@ void FeatureGym::Impl::CreatePhysics() { bike = physics.CreateMotorcycle(bike_desc, c + Vec3{8, 1.0f, -4}, kPi * 0.5f); constexpr u32 kHeightSamples = 17; - std::array heights{}; + base::Array heights{}; for (u32 z = 0; z < kHeightSamples; ++z) for (u32 x = 0; x < kHeightSamples; ++x) - heights[z * kHeightSamples + x] = 0.25f * std::sin(x * 0.7f) * std::cos(z * 0.55f); + heights[z * kHeightSamples + x] = 0.25f * ::sin(x * 0.7f) * ::cos(z * 0.55f); physics.AddHeightField(c + Vec3{-11, 0, -10}, heights.data(), kHeightSamples, 8.0f); CreateCloth(); } @@ -1902,7 +1903,7 @@ void FeatureGym::Impl::CreateDrivingCircuit() { // full-grip asphalt. constexpr u32 kSamples = 41; constexpr f32 kFieldSize = 46.0f; - std::array flat{}; + base::Array flat{}; physics.AddHeightField(c + Vec3{-kFieldSize * 0.5f, 0.0f, -kFieldSize * 0.5f}, flat.data(), kSamples, kFieldSize); @@ -1917,8 +1918,8 @@ void FeatureGym::Impl::CreateDrivingCircuit() { for (u32 i = 0; i < kOvalSegments; ++i) { const f32 a0 = (static_cast(i) / kOvalSegments) * 2.0f * kPi; const f32 a1 = (static_cast(i + 1) / kOvalSegments) * 2.0f * kPi; - lines.push_back({c + Vec3{rx * std::cos(a0), 0.09f, rz * std::sin(a0)}, - c + Vec3{rx * std::cos(a1), 0.09f, rz * std::sin(a1)}, color}); + lines.push_back({c + Vec3{rx * ::cos(a0), 0.09f, rz * ::sin(a0)}, + c + Vec3{rx * ::cos(a1), 0.09f, rz * ::sin(a1)}, color}); } }; oval(circuit_rx, circuit_rz, 0xf4f4f5ff); @@ -1933,7 +1934,7 @@ void FeatureGym::Impl::CreateDrivingCircuit() { for (u32 i = 0; i < kCones; ++i) { const f32 a = (static_cast(i) / kCones) * 2.0f * kPi; Spawn(cone, - c + Vec3{(circuit_rx + 1.3f) * std::cos(a), 0.36f, (circuit_rz + 1.3f) * std::sin(a)}); + c + Vec3{(circuit_rx + 1.3f) * ::cos(a), 0.36f, (circuit_rz + 1.3f) * ::sin(a)}); } // The circuit car reuses the chassis + wheel meshes uploaded in @@ -1974,7 +1975,7 @@ void FeatureGym::Impl::CreateAircraft() { // settle (zero-input steps in AddSimulation) before the flyby flings it aloft. const Vec3 apron = circuit_center + Vec3{circuit_rx + 6.0f, 1.4f, -circuit_rz - 2.0f}; physics::AircraftDesc desc; - aircraft = std::make_unique(physics, desc, apron, 0.0f); + aircraft = base::MakeUnique(physics, desc, apron, 0.0f); if (!aircraft->valid()) RX_WARN("feature gym: flyover aircraft is static; rebuild with Jolt for flight"); } @@ -1989,7 +1990,7 @@ void FeatureGym::Impl::CreateAnimation() { biped_material.roughness_factor = 0.68f; asset::Mesh biped; asset::MakeSkinnedBiped(asset::MakeAssetId("featuregym/mesh/biped"), &skeleton, &biped); - biped = WithMaterial(std::move(biped), biped_material.id); + biped = WithMaterial(base::move(biped), biped_material.id); skin = biped.skin; biped_mesh = biped.id.hash; if (!headless) { @@ -2004,18 +2005,18 @@ void FeatureGym::Impl::CreateAnimation() { biped_position = c + Vec3{-7, 0.2f, -2}; asset::Mesh morph = asset::MakeSphere(1.35f, 34, 48, asset::MakeAssetId("featuregym/mesh/morph")); - morph = WithMaterial(std::move(morph), AddMaterial("morph", {0.74f, 0.16f, 0.58f}, 0.32f)); + morph = WithMaterial(base::move(morph), AddMaterial("morph", {0.74f, 0.16f, 0.58f}, 0.32f)); asset::MorphTarget target; target.name = "pulse"; target.name_hash = asset::MakeAssetId(target.name).hash; target.position_deltas.reserve(morph.lods[0].vertices.size() * 3); for (const asset::Vertex& vertex : morph.lods[0].vertices) { - const f32 wave = 0.26f * std::sin(vertex.position[1] * 6.0f); + const f32 wave = 0.26f * ::sin(vertex.position[1] * 6.0f); target.position_deltas.push_back(vertex.normal[0] * wave); target.position_deltas.push_back(vertex.normal[1] * wave); target.position_deltas.push_back(vertex.normal[2] * wave); } - morph.morph_targets.push_back(std::move(target)); + morph.morph_targets.push_back(base::move(target)); morph_mesh = morph.id.hash; if (!headless) renderer.UploadMesh(morph); morph_transform = MakeTranslation(c + Vec3{6.5f, 1.7f, 1.0f}); @@ -2024,7 +2025,7 @@ void FeatureGym::Impl::CreateAnimation() { asset::AssetId step = AddMaterial("ik_step", {0.30f, 0.34f, 0.28f}, 0.94f); for (int i = 0; i < 6; ++i) { const f32 h = 0.06f + (i % 3) * 0.07f; - SpawnBox("ik_step_" + std::to_string(i), {0.8f, h, 0.85f}, + SpawnBox("ik_step_" + rx::ToString(i), {0.8f, h, 0.85f}, c + Vec3{-6 + i * 2.1f, h + 0.1f, -1.5f}, step, true); } } @@ -2053,7 +2054,7 @@ void FeatureGym::Impl::CreatePost() { ecs::Entity moving = Spawn( i & 1 ? glow_mesh : UploadMesh(asset::MakeCube(0.65f, asset::MakeAssetId("featuregym/mesh/post_cube_" + - std::to_string(i))), + rx::ToString(i))), i & 2 ? white : black), c + Vec3{-9 + i * 3.0f, 1.1f, 1.0f + (i & 1) * 2.0f}); world.Add(moving, GymMotion{.origin = c + Vec3{-9 + i * 3.0f, 1.1f, 1.0f + (i & 1) * 2.0f}, @@ -2065,8 +2066,8 @@ void FeatureGym::Impl::CreatePost() { for (int i = 0; i < 9; ++i) { const f32 value = static_cast(i) / 8.0f; asset::AssetId ramp = - AddMaterial("post_ramp_" + std::to_string(i), {value, value, value}, 0.65f); - SpawnBox("post_ramp_" + std::to_string(i), {0.6f, 1.8f, 0.35f}, + AddMaterial("post_ramp_" + rx::ToString(i), {value, value, value}, 0.65f); + SpawnBox("post_ramp_" + rx::ToString(i), {0.6f, 1.8f, 0.35f}, c + Vec3{-8 + i * 2.0f, 1.9f, -4.5f}, ramp); } } @@ -2106,13 +2107,13 @@ void FeatureGym::Impl::CreateCameraExhibit() { } } -void FeatureGym::Impl::AddLabel(const Vec3& anchor, std::string_view text, f32 height, u32 color) { +void FeatureGym::Impl::AddLabel(const Vec3& anchor, base::StringRef text, f32 height, u32 color) { render::WorldText label; label.position = anchor; - label.text = std::string(text); + label.text = base::String(text); label.size = height; label.rgba = color; - labels.push_back(std::move(label)); + labels.push_back(base::move(label)); } void FeatureGym::Impl::CreateLabels() { @@ -2143,13 +2144,13 @@ void FeatureGym::Impl::CreateLabels() { void FeatureGym::Impl::StartAudio() { if (headless || !ctx.audio || !ctx.audio->active()) return; - const std::filesystem::path path = FeatureAssetPath("spatial_tone.wav"); - std::vector bytes = ReadBytes(path); + const base::String path = FeatureAssetPath("spatial_tone.wav"); + base::Vector bytes = ReadBytes(path); if (bytes.empty()) { - RX_WARN("feature gym: generated audio missing at {}", path.string()); + RX_WARN("feature gym: generated audio missing at {}", path); return; } - std::unique_ptr decoder = + base::UniquePointer decoder = audio::OpenDecoder(ByteSpan(bytes.data(), bytes.size()), ".wav"); audio::PlayParams params; params.loop = true; @@ -2157,7 +2158,7 @@ void FeatureGym::Impl::StartAudio() { params.position = Info(Area::kAnimation).center + Vec3{6, 1.5f, -5}; params.gain = 0.18f; params.fade_in = 0.5f; - audio_voice = ctx.audio->mixer().Play(std::move(decoder), params); + audio_voice = ctx.audio->mixer().Play(base::move(decoder), params); } void FeatureGym::Impl::AddSimulation() { @@ -2170,7 +2171,7 @@ void FeatureGym::Impl::AddSimulation() { [this](ecs::Entity, GymMotion& motion, scene::Transform& transform) { const f32 angle = sim_time * motion.speed + motion.phase; transform.position[0] = motion.origin.x; - transform.position[1] = motion.origin.y + std::sin(angle * 1.7f) * motion.amplitude; + transform.position[1] = motion.origin.y + ::sin(angle * 1.7f) * motion.amplitude; transform.position[2] = motion.origin.z; const Quat rotation = QuatFromAxisAngle(motion.axis, angle); transform.rotation[0] = rotation.x; @@ -2183,25 +2184,25 @@ void FeatureGym::Impl::AddSimulation() { [dt](ecs::Entity, BubbleAgent& agent, scene::Transform& transform) { agent.time += dt; transform.position[0] = - agent.center.x + std::sin(agent.time * agent.rate_x) * agent.extent; + agent.center.x + ::sin(agent.time * agent.rate_x) * agent.extent; transform.position[1] = agent.center.y; transform.position[2] = - agent.center.z + std::cos(agent.time * agent.rate_z) * agent.extent; + agent.center.z + ::cos(agent.time * agent.rate_z) * agent.extent; }); if (strand_sim) { const f32 angle = sim_time * 0.85f; const Vec3 position = - strand_center + Vec3{0.08f * std::sin(angle), 0.10f * std::sin(angle * 1.7f), 0}; + strand_center + Vec3{0.08f * ::sin(angle), 0.10f * ::sin(angle * 1.7f), 0}; strand_transform = MakeTranslation(position) * - MakeFromQuat(QuatFromAxisAngle({0, 1, 0}, 0.7f * std::sin(angle))); + MakeFromQuat(QuatFromAxisAngle({0, 1, 0}, 0.7f * ::sin(angle))); physics.SetStrandGroomTransform(strand_sim, strand_transform, dt); physics.SetStrandGroomWind(strand_sim, - {0.8f + 0.45f * std::sin(sim_time * 2.1f), 0.12f, -0.35f}); + {0.8f + 0.45f * ::sin(sim_time * 2.1f), 0.12f, -0.35f}); } const f32 identity[4] = {0, 0, 0, 1}; if (platform_body) { - const Vec3 target = platform_origin + Vec3{std::sin(sim_time * 0.7f) * 3.5f, 0, 0}; + const Vec3 target = platform_origin + Vec3{::sin(sim_time * 0.7f) * 3.5f, 0, 0}; physics.MoveBodyKinematic(platform_body, target, identity, dt); if (scene::Transform* transform = sim_world.Get(platform_entity)) { transform->position[0] = target.x; @@ -2220,7 +2221,7 @@ void FeatureGym::Impl::AddSimulation() { if (transform->position[0] < physics_x - 9.0f) character_direction = 1; } const int cycle = static_cast(sim_time / 5.0f); - const bool jump = cycle != jump_cycle && std::fmod(sim_time, 5.0f) < dt * 1.5f; + const bool jump = cycle != jump_cycle && ::fmod(sim_time, 5.0f) < dt * 1.5f; if (jump) jump_cycle = cycle; physics.MoveCharacter(character, {character_direction * 2.0f, 0, 0}, jump, dt, &position, &grounded); @@ -2236,7 +2237,7 @@ void FeatureGym::Impl::AddSimulation() { if (active == Area::kPhysics) { const bool accelerating = physics_active_time < 1.0f; physics.DriveVehicle(car, accelerating ? 0.10f : 0.0f, - std::sin(physics_active_time * 0.35f) * 0.35f, + ::sin(physics_active_time * 0.35f) * 0.35f, accelerating ? 0.0f : 0.65f, 0.0f); } else { physics.DriveVehicle(car, 0, 0, 1, 0); @@ -2246,7 +2247,7 @@ void FeatureGym::Impl::AddSimulation() { if (active == Area::kPhysics) { const bool accelerating = physics_active_time < 1.0f; physics.DriveVehicle(bike, accelerating ? 0.08f : 0.0f, - std::sin(physics_active_time * 0.42f) * 0.22f, + ::sin(physics_active_time * 0.42f) * 0.22f, accelerating ? 0.0f : 0.55f, 0.0f); } else { physics.DriveVehicle(bike, 0, 0, 1, 0); @@ -2256,18 +2257,18 @@ void FeatureGym::Impl::AddSimulation() { if (active_mode == TourMode::kVehicleCircuit) { circuit_time += dt; const f32 theta = circuit_time * 0.6f + 0.42f; - const Vec3 target = circuit_center + Vec3{circuit_rx * std::cos(theta), 0, - circuit_rz * std::sin(theta)}; + const Vec3 target = circuit_center + Vec3{circuit_rx * ::cos(theta), 0, + circuit_rz * ::sin(theta)}; Vec3 pos; f32 rot[4]; if (physics.GetVehicleTransform(circuit_car, &pos, rot)) { const Vec3 fwd = Rotate(Quat{rot[0], rot[1], rot[2], rot[3]}, Vec3{0, 0, 1}); - f32 err = std::atan2(target.x - pos.x, target.z - pos.z) - std::atan2(fwd.x, fwd.z); + f32 err = ::atan2(target.x - pos.x, target.z - pos.z) - ::atan2(fwd.x, fwd.z); while (err > kPi) err -= 2.0f * kPi; while (err < -kPi) err += 2.0f * kPi; // +Z forward, right = -X, so steer (positive = right) chases the // target against the sign of the yaw error. - physics.DriveVehicle(circuit_car, 0.42f, std::clamp(-err * 1.3f, -1.0f, 1.0f), 0.0f, + physics.DriveVehicle(circuit_car, 0.42f, rx::Clamp(-err * 1.3f, -1.0f, 1.0f), 0.0f, 0.0f); } } else { @@ -2300,8 +2301,8 @@ void FeatureGym::Impl::AddSimulation() { const physics::BoatState& s = boat->state(); const f32 theta = boat_time * 0.5f + 0.6f; Vec3 target = boat_circuit_center + - Vec3{boat_circuit_radius * std::cos(theta), 0, - boat_circuit_radius * std::sin(theta)}; + Vec3{boat_circuit_radius * ::cos(theta), 0, + boat_circuit_radius * ::sin(theta)}; // Safety backstop: a heavy hull turns wide, so if it // drifts past the ring steer straight for the lake // centre. This bounds the path inside the water region @@ -2313,14 +2314,14 @@ void FeatureGym::Impl::AddSimulation() { (boat_circuit_radius + 1.0f) * (boat_circuit_radius + 1.0f); if (outside) target = boat_circuit_center; const Vec3 fwd = Rotate(s.rotation, Vec3{0, 0, 1}); - f32 err = std::atan2(target.x - s.position.x, target.z - s.position.z) - - std::atan2(fwd.x, fwd.z); + f32 err = ::atan2(target.x - s.position.x, target.z - s.position.z) - + ::atan2(fwd.x, fwd.z); while (err > kPi) err -= 2.0f * kPi; while (err < -kPi) err += 2.0f * kPi; // Ease off the throttle when steering hard or recovering // so the hull stays slow enough to answer the helm. - input.steer = std::clamp(-err * 2.2f, -1.0f, 1.0f); - input.throttle = (outside || std::abs(err) > 1.0f) ? 0.12f : 0.22f; + input.steer = rx::Clamp(-err * 2.2f, -1.0f, 1.0f); + input.throttle = (outside || ::abs(err) > 1.0f) ? 0.12f : 0.22f; } else { boat_cruise_active = false; boat_time = 0; @@ -2353,16 +2354,16 @@ void FeatureGym::Impl::AddSimulation() { plane_time += dt; const physics::AircraftState& s = aircraft->state(); const Vec3 fwd = Rotate(s.rotation, Vec3{0, 0, 1}); - f32 herr = (kPi * 0.5f) - std::atan2(fwd.x, fwd.z); + f32 herr = (kPi * 0.5f) - ::atan2(fwd.x, fwd.z); while (herr > kPi) herr -= 2.0f * kPi; while (herr < -kPi) herr += 2.0f * kPi; // Hold heading +X with a gentle bank + rudder, hold the cruise altitude // with the elevator, full power. Wings stay near level so lift stays // up and the pass is sustainable. ai.throttle = 1.0f; - ai.roll = std::clamp(-herr * 0.8f, -0.35f, 0.35f); - ai.yaw = std::clamp(-herr * 0.3f, -0.3f, 0.3f); - ai.pitch = std::clamp((plane_orbit_alt - s.position.y) * 0.08f - + ai.roll = rx::Clamp(-herr * 0.8f, -0.35f, 0.35f); + ai.yaw = rx::Clamp(-herr * 0.3f, -0.3f, 0.3f); + ai.pitch = rx::Clamp((plane_orbit_alt - s.position.y) * 0.08f - s.vertical_speed_mps * 0.06f, -0.6f, 0.6f); } else { @@ -2380,7 +2381,7 @@ void FeatureGym::Impl::EmitCloth(render::FrameView& view) { return; } - std::fill(cloth_normals.begin(), cloth_normals.end(), Vec3{}); + base::Fill(cloth_normals.begin(), cloth_normals.end(), Vec3{}); cloth_lines.clear(); for (size_t i = 0; i < cloth_indices.size(); i += 3) { const u32 a = cloth_indices[i]; @@ -2433,14 +2434,14 @@ void FeatureGym::Impl::EmitCloth(render::FrameView& view) { draw.skin_offset = skin_offset; view.draws.push_back(draw); view.debug_lines_overlay = - std::span(cloth_lines.data(), cloth_lines.size()); + base::Span(cloth_lines.data(), cloth_lines.size()); } void FeatureGym::Impl::EmitAnimation(f32 dt, render::FrameView& view) { if (!graph.valid()) return; - const f32 animation_dt = std::min(dt, 0.05f); + const f32 animation_dt = rx::Min(dt, 0.05f); biped_time += animation_dt; - const f32 phase = std::fmod(biped_time, 8.0f); + const f32 phase = ::fmod(biped_time, 8.0f); const f32 speed = phase < 1.0f ? 0.0f : phase < 3.0f ? 1.6f : 3.8f; rig.SetSpeed(speed); Vec3 root = rig.Update(animation_dt, &pose, [&](const anim::RigPlayer::Event& event) { @@ -2512,7 +2513,7 @@ void FeatureGym::Impl::EmitAnimation(f32 dt, render::FrameView& view) { if (splat_timer > 0.8f) { splat_timer = 0; const f32 angle = static_cast(splat_index) * 2.39996f; // golden angle - const Vec3 dir{std::cos(angle), 0.0f, std::sin(angle)}; + const Vec3 dir{::cos(angle), 0.0f, ::sin(angle)}; const f32 height = 0.55f + 0.28f * static_cast(splat_index % 4); render::DecalStamp splat; splat.receiver = biped_decal_receiver; @@ -2535,14 +2536,14 @@ void FeatureGym::Impl::EmitAnimation(f32 dt, render::FrameView& view) { morph.prev_transform = morph_previous; morph.morph_offset = static_cast(view.morph_weights.size()); morph.morph_count = 1; - view.morph_weights.push_back({0, std::sin(render_time * 1.7f) * 0.5f + 0.5f}); + view.morph_weights.push_back({0, ::sin(render_time * 1.7f) * 0.5f + 0.5f}); view.draws.push_back(morph); morph_previous = morph_transform; } void FeatureGym::Impl::EmitVehicles(render::FrameView& view) { - auto emit = [&](physics::VehicleId vehicle, u64 chassis, std::span wheel_previous, - std::span wheel_previous_valid, Mat4& chassis_previous, + auto emit = [&](physics::VehicleId vehicle, u64 chassis, base::Span wheel_previous, + base::Span wheel_previous_valid, Mat4& chassis_previous, bool& chassis_previous_valid) { if (!vehicle) return; Vec3 position; @@ -2611,11 +2612,11 @@ void FeatureGym::Impl::EmitBoat(f32 dt, render::FrameView& view) { wake.position = {stern.x, water_height + physics::GerstnerWaveHeight(stern.x, stern.z, sim_time), stern.z}; wake.radius = 1.4f; - wake.ripple_strength = std::min(1.0f, speed * 0.2f); - wake.foam_amount = std::min(1.0f, speed * 0.18f); + wake.ripple_strength = rx::Min(1.0f, speed * 0.2f); + wake.foam_amount = rx::Min(1.0f, speed * 0.18f); wake.velocity_x = velocity.x; wake.velocity_z = velocity.z; - wake.elongation = std::min(4.0f, speed * 0.4f); + wake.elongation = rx::Min(4.0f, speed * 0.4f); view.water_disturbances.push_back(wake); } @@ -2740,11 +2741,11 @@ void FeatureGym::Impl::EmitSkewedNormals(render::FrameView& view) { void FeatureGym::Impl::EmitCameraExhibit(f32 dt, render::FrameView& view) { if (active_mode != TourMode::kEcsCameraStackRig || !camera_activation) return; if (scene::CameraOrbit* orbit = world.Get(camera_rig_mode)) - orbit->yaw = 0.22f * std::sin(active_mode_elapsed * 0.7f); + orbit->yaw = 0.22f * ::sin(active_mode_elapsed * 0.7f); if (scene::CameraAnchor* anchor = world.Get(camera_rig_mode)) { const Vec3 c = Info(Area::kPost).center; - anchor->position = c + Vec3{std::sin(active_mode_elapsed * 0.6f) * 1.2f, 0.4f, 0}; - anchor->velocity = {std::cos(active_mode_elapsed * 0.6f) * 0.72f, 0, 0}; + anchor->position = c + Vec3{::sin(active_mode_elapsed * 0.6f) * 1.2f, 0.4f, 0}; + anchor->velocity = {::cos(active_mode_elapsed * 0.6f) * 0.72f, 0, 0}; } scene::BuildCameraRigs(world, dt); scene::PrepareCameraRigConstraints(world, dt); @@ -2758,7 +2759,7 @@ void FeatureGym::Impl::EmitCameraExhibit(f32 dt, render::FrameView& view) { } void FeatureGym::Impl::Emit(f32 dt, render::FrameView& view) { - render_time += std::min(dt, 0.05f); + render_time += rx::Min(dt, 0.05f); if (!activations.empty()) { render::RenderSettings& settings = renderer.settings(); if (active_mode == TourMode::kWeatherRain) { @@ -2777,7 +2778,7 @@ void FeatureGym::Impl::Emit(f32 dt, render::FrameView& view) { Area nearest_area = Area::kOverview; f32 nearest_distance = 18.0f * 18.0f; if (view.camera.eye.y < 16.0f) { - for (size_t i = 0; i < std::size(kAreas); ++i) { + for (size_t i = 0; i < (sizeof(kAreas) / sizeof(kAreas[0])); ++i) { const Vec3 delta = view.camera.eye - kAreas[i].center; const f32 distance = delta.x * delta.x + delta.z * delta.z; if (distance < nearest_distance) { @@ -2829,7 +2830,7 @@ void FeatureGym::Impl::Emit(f32 dt, render::FrameView& view) { if (active == Area::kMaterials) view.decals = decals; EmitSkewedNormals(view); EmitNetworkBubbles(view); - view.debug_lines = std::span(lines.data(), lines.size()); + view.debug_lines = base::Span(lines.data(), lines.size()); view.world_texts = labels; // floating district + exhibit captions // Captions that ride the active vehicle exhibit. auto tag = [&](const Vec3& p, const char* text, f32 h, u32 color) { @@ -2838,7 +2839,7 @@ void FeatureGym::Impl::Emit(f32 dt, render::FrameView& view) { t.text = text; t.size = h; t.rgba = color; - view.world_texts.push_back(std::move(t)); + view.world_texts.push_back(base::move(t)); }; if (active_mode == TourMode::kVehicleCircuit && circuit_car) { Vec3 p; @@ -2865,11 +2866,11 @@ void FeatureGym::Impl::Emit(f32 dt, render::FrameView& view) { position.x, water_height + physics::GerstnerWaveHeight(position.x, position.z, sim_time), position.z}; disturbance.radius = 0.8f; - disturbance.ripple_strength = std::min(1.0f, Length(velocity) * 0.15f); - disturbance.foam_amount = std::min(0.8f, Length(velocity) * 0.08f); + disturbance.ripple_strength = rx::Min(1.0f, Length(velocity) * 0.15f); + disturbance.foam_amount = rx::Min(0.8f, Length(velocity) * 0.08f); disturbance.velocity_x = velocity.x; disturbance.velocity_z = velocity.z; - disturbance.elongation = std::min(3.0f, Length(velocity) * 0.25f); + disturbance.elongation = rx::Min(3.0f, Length(velocity) * 0.25f); view.water_disturbances.push_back(disturbance); } } @@ -2877,7 +2878,7 @@ void FeatureGym::Impl::Emit(f32 dt, render::FrameView& view) { bool FeatureGym::Impl::BuildTour(ShowcaseCamera& camera) { if (!created) return false; activations.clear(); - applied_activation = std::numeric_limits::max(); + applied_activation = SIZE_MAX; auto add = [&](TourMode mode, Vec3 eye, Vec3 look, f32 travel, const char* label) { // A waypoint captures at the end of its segment. Keep its mode through that // boundary, then switch shortly after travel toward the next stop begins. @@ -3002,7 +3003,7 @@ void FeatureGym::Impl::ApplyArea(Area area) { settings.mesh_shader_lod = true; settings.vrs = true; settings.async_compute = true; - settings.dynamic_resolution = std::getenv("RX_SHOWCASE_SHOTS") == nullptr; + settings.dynamic_resolution = ::getenv("RX_SHOWCASE_SHOTS") == nullptr; settings.dynamic_target_ms = 16.6f; settings.texture_budget_mb = 96; break; @@ -3207,16 +3208,16 @@ void FeatureGym::Impl::SetTourTime(f32 seconds) { applied_activation = selected; ApplyTourMode(activations[selected].mode); } - active_mode_elapsed = std::max(0.0f, seconds - activations[selected].time); + active_mode_elapsed = rx::Max(0.0f, seconds - activations[selected].time); } -FeatureGym::FeatureGym(EngineContext& ctx) : impl_(std::make_unique(ctx)) {} +FeatureGym::FeatureGym(EngineContext& ctx) : impl_(base::MakeUnique(ctx)) {} FeatureGym::~FeatureGym() = default; void FeatureGym::Create() { impl_->Create(); } void FeatureGym::Emit(f32 dt, render::FrameView& view) { impl_->Emit(dt, view); } bool FeatureGym::BuildTour(ShowcaseCamera& camera) { return impl_->BuildTour(camera); } void FeatureGym::SetTourTime(f32 seconds) { impl_->SetTourTime(seconds); } -std::string_view FeatureGym::active_area() const { return Info(impl_->active).name; } +base::StringRef FeatureGym::active_area() const { return Info(impl_->active).name; } } // namespace rx diff --git a/runtime/feature_gym/feature_gym.h b/runtime/feature_gym/feature_gym.h index 4c5cb6e7..05336297 100644 --- a/runtime/feature_gym/feature_gym.h +++ b/runtime/feature_gym/feature_gym.h @@ -1,9 +1,9 @@ #ifndef RX_RUNTIME_FEATURE_GYM_FEATURE_GYM_H_ #define RX_RUNTIME_FEATURE_GYM_FEATURE_GYM_H_ -#include -#include +#include "base/memory/unique_pointer.h" +#include "base/strings/string_ref.h" #include "core/types.h" #if defined(__ANDROID__) @@ -43,11 +43,11 @@ class FeatureGym { bool BuildTour(ShowcaseCamera& camera); void SetTourTime(f32 seconds); - std::string_view active_area() const; + base::StringRef active_area() const; private: struct Impl; - std::unique_ptr impl_; + base::UniquePointer impl_; }; } // namespace rx diff --git a/runtime/fly_camera.cc b/runtime/fly_camera.cc index bc7523dd..19e6a611 100644 --- a/runtime/fly_camera.cc +++ b/runtime/fly_camera.cc @@ -1,12 +1,12 @@ +#include "core/scalar.h" #include "fly_camera.h" -#include -#include +#include namespace rx { Vec3 FlyCamera::forward() const { - return {std::cos(pitch_) * std::sin(yaw_), std::sin(pitch_), -std::cos(pitch_) * std::cos(yaw_)}; + return {::cos(pitch_) * ::sin(yaw_), ::sin(pitch_), -::cos(pitch_) * ::cos(yaw_)}; } void FlyCamera::Update(const InputState& input, const ActionState& actions, bool allow_mouse, @@ -17,8 +17,8 @@ void FlyCamera::Update(const InputState& input, const ActionState& actions, bool yaw_ += input.mouse_dx * sensitivity; pitch_ -= input.mouse_dy * sensitivity; if (input.wheel != 0) { - speed *= std::pow(1.2f, input.wheel); - speed = std::clamp(speed, 0.1f, 200.0f); + speed *= ::pow(1.2f, input.wheel); + speed = rx::Clamp(speed, 0.1f, 200.0f); } } // Right-stick look (rate based), no button needed. @@ -26,7 +26,7 @@ void FlyCamera::Update(const InputState& input, const ActionState& actions, bool yaw_ += actions.axis(Axis::kLookX) * pad_sensitivity * dt; pitch_ -= actions.axis(Axis::kLookY) * pad_sensitivity * dt * (invert_y ? -1.0f : 1.0f); } - pitch_ = std::clamp(pitch_, -1.55f, 1.55f); + pitch_ = rx::Clamp(pitch_, -1.55f, 1.55f); if (!allow_keyboard) return; @@ -39,7 +39,7 @@ void FlyCamera::Update(const InputState& input, const ActionState& actions, bool if (actions.down(Action::kCamUp) || actions.down(Action::kJump)) move += Vec3{0, 1, 0}; if (actions.down(Action::kCamDown) || actions.down(Action::kSneak)) move += Vec3{0, -1, 0}; - f32 length = std::sqrt(Dot(move, move)); + f32 length = ::sqrt(Dot(move, move)); if (length > 0) { f32 boost = actions.down(Action::kSprint) ? 4.0f : 1.0f; position_ += move * (speed * boost * dt / length); diff --git a/runtime/main.cc b/runtime/main.cc index 4c566084..a1a41ae4 100644 --- a/runtime/main.cc +++ b/runtime/main.cc @@ -1,11 +1,10 @@ -#include -#include -#include -#include +#include +#include #include #include "app/host.h" +#include "base/strings/xstring.h" #include "core/log.h" #include "edit/reflect.h" #include "material_palette.h" @@ -15,6 +14,7 @@ #include "script/handler_registry.h" #include "script/script_value.h" #include "viewer.h" +#include "core/file_system.h" namespace { @@ -63,7 +63,7 @@ void PrintUsage() { RX_INFO(" --validation enable vulkan validation layers"); } -rx::render::UpscalerKind ParseUpscaler(const std::string& id) { +rx::render::UpscalerKind ParseUpscaler(const base::String& id) { if (id == "fsr3") return rx::render::UpscalerKind::kFsr3; if (id == "dlss") return rx::render::UpscalerKind::kDlss; if (id == "xess") return rx::render::UpscalerKind::kXess; @@ -71,12 +71,12 @@ rx::render::UpscalerKind ParseUpscaler(const std::string& id) { } void PrintJsonString(const char* s) { - std::putchar('"'); + ::putchar('"'); for (const char* c = s; *c; ++c) { - if (*c == '"' || *c == '\\') std::putchar('\\'); - std::putchar(*c); + if (*c == '"' || *c == '\\') ::putchar('\\'); + ::putchar(*c); } - std::putchar('"'); + ::putchar('"'); } // The .rxscene authoring surface, generated from the live reflection registry @@ -84,32 +84,32 @@ void PrintJsonString(const char* s) { // anything writing scene files by hand. void DumpSchema() { rx::RegisterSceneComponents(); - std::printf("{\n \"components\": [\n"); + ::printf("{\n \"components\": [\n"); const auto components = rx::edit::AllComponents(); for (size_t i = 0; i < components.size(); ++i) { const rx::edit::ComponentDesc& comp = *components[i]; - std::printf(" {\n \"name\": "); + ::printf(" {\n \"name\": "); PrintJsonString(comp.name); - std::printf(",\n \"props\": ["); + ::printf(",\n \"props\": ["); for (rx::u32 p = 0; p < comp.prop_count; ++p) { const rx::edit::PropDesc& prop = comp.props[p]; - std::printf("%s\n {\"name\": ", p ? "," : ""); + ::printf("%s\n {\"name\": ", p ? "," : ""); PrintJsonString(prop.name); - std::printf(", \"type\": "); + ::printf(", \"type\": "); PrintJsonString(rx::edit::PropTypeName(prop.type)); // 0/0 is "unbounded" (PropDesc), so only a real Range prints. if (prop.min != 0.0f || prop.max != 0.0f) - std::printf(", \"min\": %g, \"max\": %g", prop.min, prop.max); + ::printf(", \"min\": %g, \"max\": %g", prop.min, prop.max); if (prop.hint) { - std::printf(", \"hint\": "); + ::printf(", \"hint\": "); PrintJsonString(prop.hint); } - std::printf("}"); + ::printf("}"); } - std::printf("%s]\n }%s\n", comp.prop_count ? "\n " : "", + ::printf("%s]\n }%s\n", comp.prop_count ? "\n " : "", i + 1 < components.size() ? "," : ""); } - std::printf(" ]\n}\n"); + ::printf(" ]\n}\n"); } // The live command surface (--authoring-endpoint), generated from the registry @@ -119,24 +119,24 @@ void DumpSchema() { void DumpCommands() { rx::script::HandlerRegistry commands; rx::scene::SetupSceneCommands(commands); - std::printf("{\n \"commands\": [\n"); + ::printf("{\n \"commands\": [\n"); for (size_t i = 0; i < commands.size(); ++i) { const rx::script::HandlerDesc& desc = commands.at(i); - std::printf(" {\"name\": "); - PrintJsonString(std::string(desc.name.view()).c_str()); - std::printf(", \"params\": ["); + ::printf(" {\"name\": "); + PrintJsonString(base::String(desc.name.view()).c_str()); + ::printf(", \"params\": ["); rx::u32 wire_args = 0; for (rx::u32 p = 0; p < desc.sig.count; ++p) { const rx::script::ScriptType type = desc.sig.params[p]; wire_args += type == rx::script::ScriptType::kVec3 ? 3 : 1; - std::printf("%s", p ? ", " : ""); + ::printf("%s", p ? ", " : ""); PrintJsonString(rx::script::ScriptTypeName(type)); } - std::printf("], \"wire_args\": %u, \"returns\": ", wire_args); + ::printf("], \"wire_args\": %u, \"returns\": ", wire_args); PrintJsonString(rx::script::ScriptTypeName(desc.sig.ret)); - std::printf("}%s\n", i + 1 < commands.size() ? "," : ""); + ::printf("}%s\n", i + 1 < commands.size() ? "," : ""); } - std::printf(" ]\n}\n"); + ::printf(" ]\n}\n"); } } // namespace @@ -151,13 +151,13 @@ int main(int argc, char** argv) { // Where this repo's palette lives, so asking what materials exist is one word // from the source root; the optional argument is for a project that ships its // own directory of presets. - std::string materials_dir = "runtime/scenes/materials"; - std::string validate_path; + base::String materials_dir = "runtime/scenes/materials"; + base::String validate_path; bool json = false; for (int i = 1; i < argc; ++i) { - std::string arg = argv[i]; - auto next = [&]() -> std::string { return i + 1 < argc ? argv[++i] : ""; }; + base::String arg = argv[i]; + auto next = [&]() -> base::String { return i + 1 < argc ? argv[++i] : ""; }; if (arg == "--gltf" || arg == "--usd" || arg == "--scene") config.scene_path = next(); else if (arg == "--demo") config.demo_scene = next(); @@ -180,12 +180,12 @@ int main(int argc, char** argv) { else if (arg == "--authoring-endpoint") config.authoring_socket = next(); else if (arg == "--headless") no_window = true; else if (arg == "--shot") config.shot_path = next(); - else if (arg == "--shot-frames") config.shot_frames = std::atoi(next().c_str()); + else if (arg == "--shot-frames") config.shot_frames = ::atoi(next().c_str()); else if (arg == "--camera-at") config.camera_at = next(); else if (arg == "--camera-look") config.camera_look = next(); - else if (arg == "--camera-fov") config.camera_fov = std::strtof(next().c_str(), nullptr); - else if (arg == "--width") app_config.width = static_cast(std::atoi(next().c_str())); - else if (arg == "--height") app_config.height = static_cast(std::atoi(next().c_str())); + else if (arg == "--camera-fov") config.camera_fov = ::strtof(next().c_str(), nullptr); + else if (arg == "--width") app_config.width = static_cast(::atoi(next().c_str())); + else if (arg == "--height") app_config.height = static_cast(::atoi(next().c_str())); else if (arg == "--preset") config.preset = rx::render::ParsePreset(next()); else if (arg == "--no-taa") config.renderer.aa_mode = rx::render::AntiAliasingMode::kNone; else if (arg == "--upscaler") config.renderer.upscaler = ParseUpscaler(next()); @@ -220,11 +220,11 @@ int main(int argc, char** argv) { // The env var is the older spelling of --shot and still drives existing // capture scripts; resolve both here because whether a capture is armed decides // whether a windowless run needs the gpu at all. - std::string shot = config.shot_path; + base::String shot = config.shot_path; if (shot.empty()) { - if (const char* env = std::getenv("RX_UI_SHOT")) shot = env; + if (const char* env = ::getenv("RX_UI_SHOT")) shot = env; } - const char* showcase_shots = std::getenv("RX_SHOWCASE_SHOTS"); + const char* showcase_shots = ::getenv("RX_SHOWCASE_SHOTS"); const bool capture = !shot.empty() || (showcase_shots && showcase_shots[0]); config.offscreen = no_window && capture; config.headless = no_window && !config.offscreen; @@ -243,8 +243,8 @@ int main(int argc, char** argv) { if (capture) app_config.fixed_delta = kCaptureDelta; // A stale png from an earlier run would otherwise pass the check below. - const bool verify_shot = !config.shot_path.empty() && !std::getenv("RX_UI_SHOT_SEQ"); - if (verify_shot) std::filesystem::remove(config.shot_path); + const bool verify_shot = !config.shot_path.empty() && !::getenv("RX_UI_SHOT_SEQ"); + if (verify_shot) rx::fs::Remove(config.shot_path); #if defined(RX_SHARED_BUILD) // The viewer's own base::Option knobs (viewer.cc, camera_input.cc, @@ -267,7 +267,7 @@ int main(int argc, char** argv) { // The capture is the whole point of a --shot run, so its absence (no device, // a scene that rendered nothing, a quit before the write landed) has to be // visible in the exit code rather than only in the log. - if (rc == 0 && verify_shot && !std::filesystem::exists(config.shot_path)) { + if (rc == 0 && verify_shot && !rx::fs::Exists(config.shot_path)) { RX_ERROR("no screenshot written to '{}'", config.shot_path); return 1; } diff --git a/runtime/material_palette.cc b/runtime/material_palette.cc index 84707bc3..c26320fc 100644 --- a/runtime/material_palette.cc +++ b/runtime/material_palette.cc @@ -1,21 +1,22 @@ #include "material_palette.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include #include "asset/asset_database.h" #include "asset/vfs.h" +#include "base/containers/vector.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/log.h" #include "ecs/world.h" #include "edit/reflect.h" #include "edit/scene_io.h" #include "scene_authoring.h" +#include "base/algorithm.h" +#include "core/file_system.h" +#include "core/text_reader.h" namespace rx { namespace { @@ -29,13 +30,13 @@ struct Preset { ecs::World world; }; -void PrintJsonString(std::string_view s) { - std::putchar('"'); +void PrintJsonString(base::StringRef s) { + ::putchar('"'); for (char c : s) { - if (c == '"' || c == '\\') std::putchar('\\'); - std::putchar(c); + if (c == '"' || c == '\\') ::putchar('\\'); + ::putchar(c); } - std::putchar('"'); + ::putchar('"'); } // The file's first comment paragraph, joined into one line: the lines from the @@ -43,24 +44,26 @@ void PrintJsonString(std::string_view s) { // preset opens with, and it stops before the blank line ahead of `entity`, so // the summary is what the author wrote about the material and not the whole // header of a file that happens to carry more. -std::string LeadingComment(const std::filesystem::path& path) { - std::ifstream in(path, std::ios::binary); - std::string line; - std::string summary; +base::String LeadingComment(const base::String& path) { + base::String contents; + fs::ReadTextFile(path, &contents); + LineReader in(contents); + base::StringRef line; + base::String summary; bool started = false; - while (std::getline(in, line)) { + while (in.Next(&line)) { const size_t a = line.find_first_not_of(" \t\r\n"); - const bool comment = a != std::string::npos && line[a] == '#'; + const bool comment = a != base::StringRef::npos && line[a] == '#'; if (!comment) { if (started) break; continue; } started = true; size_t text = line.find_first_not_of("# \t", a); - if (text == std::string::npos) continue; // a '#' on its own separates paragraphs + if (text == base::StringRef::npos) continue; // a '#' on its own separates paragraphs const size_t end = line.find_last_not_of(" \t\r\n"); if (!summary.empty()) summary += ' '; - summary.append(line, text, end - text + 1); + summary.append(line.data() + text, end - text + 1); } return summary; } @@ -105,32 +108,32 @@ void PrintValue(const edit::PropValue& value) { return; } if (value.type == edit::PropType::kBool) { - std::printf("%s", value.b ? "true" : "false"); + ::printf("%s", value.b ? "true" : "false"); return; } if (const u32 lanes = FloatLanes(value.type); lanes != 0) { if (lanes == 1) { - std::printf("%g", value.f[0]); + ::printf("%g", value.f[0]); return; } - std::printf("["); - for (u32 lane = 0; lane < lanes; ++lane) std::printf("%s%g", lane ? ", " : "", value.f[lane]); - std::printf("]"); + ::printf("["); + for (u32 lane = 0; lane < lanes; ++lane) ::printf("%s%g", lane ? ", " : "", value.f[lane]); + ::printf("]"); return; } // i32 is the one integer the reader signs, and it lives in a different field. if (value.type == edit::PropType::kI32) { - std::printf("%lld", static_cast(value.i)); + ::printf("%lld", static_cast(value.i)); return; } - std::printf("%llu", static_cast(value.u)); + ::printf("%llu", static_cast(value.u)); } // One preset's components, as `{"Surface": {"roughness": 0.35, ...}, ...}`. // `defaults` is a scratch entity of the same world, borrowed one component at a // time to read what an unset prop would have been. void PrintComponents(ecs::World& world, ecs::Entity entity, ecs::Entity defaults) { - std::printf("{"); + ::printf("{"); bool first_comp = true; for (const edit::ComponentDesc* comp : edit::ComponentsOn(world, entity)) { // A component the registry cannot default-construct has no "unset" to @@ -139,9 +142,9 @@ void PrintComponents(ecs::World& world, ecs::Entity entity, ecs::Entity defaults // worse than a verbose one. const bool has_defaults = edit::AddComponentByDesc(world, defaults, *comp); bool first_prop = true; - std::printf("%s\n ", first_comp ? "" : ","); + ::printf("%s\n ", first_comp ? "" : ","); PrintJsonString(comp->name); - std::printf(": {"); + ::printf(": {"); for (u32 p = 0; p < comp->prop_count; ++p) { const edit::PropDesc& prop = comp->props[p]; edit::PropValue value; @@ -153,32 +156,32 @@ void PrintComponents(ecs::World& world, ecs::Entity entity, ecs::Entity defaults // default, and a listing that dropped it would read as no kind at all. const bool named = prop.type == edit::PropType::kString && !value.s.empty(); if (!named && compare && SameAsDefault(value, fallback)) continue; - std::printf("%s", first_prop ? "" : ", "); + ::printf("%s", first_prop ? "" : ", "); first_prop = false; PrintJsonString(prop.name); - std::printf(": "); + ::printf(": "); PrintValue(value); } - std::printf("}"); + ::printf("}"); first_comp = false; if (has_defaults) edit::RemoveComponentByDesc(world, defaults, *comp); } - std::printf("%s}", first_comp ? "" : "\n "); + ::printf("%s}", first_comp ? "" : "\n "); } } // namespace -bool DumpMaterialPalette(const std::string& dir) { - std::error_code ec; - if (!std::filesystem::is_directory(dir, ec)) { +bool DumpMaterialPalette(const base::String& dir) { + if (!fs::IsDirectory(dir)) { RX_ERROR("no material palette at '{}' (the path is relative to the working directory)", dir); return false; } - std::vector files; - for (const std::filesystem::directory_entry& entry : - std::filesystem::directory_iterator(dir, ec)) { - if (entry.is_regular_file() && entry.path().extension() == ".rxscene") { - files.push_back(entry.path()); + base::Vector files; + base::Vector entries; + fs::ListDirectory(dir, &entries); + for (const fs::DirEntry& entry : entries) { + if (entry.is_regular && fs::Extension(entry.path) == ".rxscene") { + files.push_back(entry.path); } } if (files.empty()) { @@ -187,50 +190,52 @@ bool DumpMaterialPalette(const std::string& dir) { } // Directory order is whatever the filesystem hands back; sorting is what // makes two dumps of the same palette diffable. - std::sort(files.begin(), files.end()); + // One directory, so the paths differ only in their unique file names and a + // byte-wise sort orders them as path comparison did. + base::Sort(files.data(), files.data() + files.size()); // Every preset is loaded before anything is printed, so a palette with a bad // entry in it fails with no output rather than with half a json document a // caller then has to parse to find out it is half. RegisterSceneComponents(); - std::vector> presets; - for (const std::filesystem::path& file : files) { + base::Vector> presets; + for (const base::String& file : files) { // One world per preset: the first entity of the file is the preset (the // same INVARIANT Prefab.path merges by - World::Create hands out ascending // indices and LoadScene calls it once per `entity` block), and a shared // world would make "first" mean the first entity of the first file. - auto preset = std::make_unique(); - std::string error; - if (!edit::LoadScene(preset->world, preset->db, file.string(), &error, /*strict=*/true)) { - RX_ERROR("material preset '{}' does not load: {}", file.string(), error); + auto preset = base::MakeUnique(); + base::String error; + if (!edit::LoadScene(preset->world, preset->db, file, &error, /*strict=*/true)) { + RX_ERROR("material preset '{}' does not load: {}", file, error); return false; } if (!preset->world.IsAlive(ecs::Entity{0, 0})) { - RX_ERROR("material preset '{}' declares no entity", file.string()); + RX_ERROR("material preset '{}' declares no entity", file); return false; } - presets.push_back(std::move(preset)); + presets.push_back(base::move(preset)); } - std::printf("{\n \"directory\": "); + ::printf("{\n \"directory\": "); PrintJsonString(dir); - std::printf(",\n \"materials\": [\n"); + ::printf(",\n \"materials\": [\n"); for (size_t i = 0; i < files.size(); ++i) { ecs::World& world = presets[i]->world; - std::printf(" {\"name\": "); - PrintJsonString(files[i].stem().string()); - std::printf(", \"path\": "); - PrintJsonString(files[i].generic_string()); - std::printf(", \"summary\": "); + ::printf(" {\"name\": "); + PrintJsonString(base::String(fs::Stem(files[i]))); + ::printf(", \"path\": "); + PrintJsonString(fs::GenericString(files[i])); + ::printf(", \"summary\": "); PrintJsonString(LeadingComment(files[i])); - std::printf(", \"sets\": "); + ::printf(", \"sets\": "); // A scratch entity to default-construct each component onto, which is where // "the author did not set this" comes from: the registry knows the // defaults, so nothing here has to repeat them. PrintComponents(world, ecs::Entity{0, 0}, world.Create()); - std::printf("}%s\n", i + 1 < files.size() ? "," : ""); + ::printf("}%s\n", i + 1 < files.size() ? "," : ""); } - std::printf(" ]\n}\n"); + ::printf(" ]\n}\n"); return true; } diff --git a/runtime/material_palette.h b/runtime/material_palette.h index f71bf3bc..5f69c114 100644 --- a/runtime/material_palette.h +++ b/runtime/material_palette.h @@ -1,7 +1,8 @@ #ifndef RX_RUNTIME_MATERIAL_PALETTE_H_ #define RX_RUNTIME_MATERIAL_PALETTE_H_ -#include +#include "base/strings/xstring.h" + namespace rx { @@ -20,7 +21,7 @@ namespace rx { // // False (having said which file and why) on a directory that is not there, a // directory with no presets in it, and a preset that does not load. -bool DumpMaterialPalette(const std::string& dir); +bool DumpMaterialPalette(const base::String& dir); } // namespace rx diff --git a/runtime/placement_demo_assets.cc b/runtime/placement_demo_assets.cc index fc6050fb..8fa0eff5 100644 --- a/runtime/placement_demo_assets.cc +++ b/runtime/placement_demo_assets.cc @@ -1,9 +1,10 @@ #include "placement_demo_assets.h" -#include -#include +#include +#include #include "core/math.h" +#include "core/scalar.h" namespace rx { namespace { @@ -41,7 +42,7 @@ u32 PushVertex(asset::MeshLod* lod, const Vec3& p, const Vec3& n) { v.normal[2] = n.z; // A stable tangent roughly perpendicular to the normal keeps the tangent // frame finite for normal-mapped materials; exact orientation is unimportant. - Vec3 t = std::abs(n.y) < 0.99f ? Normalize(Cross(Vec3{0, 1, 0}, n)) : Vec3{1, 0, 0}; + Vec3 t = ::abs(n.y) < 0.99f ? Normalize(Cross(Vec3{0, 1, 0}, n)) : Vec3{1, 0, 0}; v.tangent[0] = t.x; v.tangent[1] = t.y; v.tangent[2] = t.z; @@ -55,7 +56,7 @@ u32 PushVertex(asset::MeshLod* lod, const Vec3& p, const Vec3& n) { // An orthonormal basis (t, b) spanning the plane perpendicular to axis `d`. void Basis(const Vec3& d, Vec3* t, Vec3* b) { - Vec3 up = std::abs(d.y) < 0.99f ? Vec3{0, 1, 0} : Vec3{1, 0, 0}; + Vec3 up = ::abs(d.y) < 0.99f ? Vec3{0, 1, 0} : Vec3{1, 0, 0}; *t = Normalize(Cross(up, d)); *b = Cross(d, *t); } @@ -71,7 +72,7 @@ void AddTube(asset::MeshLod* lod, const Vec3& p0, f32 r0, const Vec3& p1, f32 r1 u32 base = static_cast(lod->vertices.size()); for (u32 k = 0; k < sides; ++k) { f32 a = kTau * static_cast(k) / static_cast(sides); - Vec3 radial = t * std::cos(a) + b * std::sin(a); + Vec3 radial = t * ::cos(a) + b * ::sin(a); PushVertex(lod, p0 + radial * r0, radial); PushVertex(lod, p1 + radial * r1, radial); } @@ -92,8 +93,8 @@ void AddCone(asset::MeshLod* lod, const Vec3& base_center, f32 radius, f32 heigh for (u32 k = 0; k < sides; ++k) { f32 a0 = kTau * static_cast(k) / static_cast(sides); f32 a1 = kTau * static_cast(k + 1) / static_cast(sides); - Vec3 p0 = base_center + Vec3{std::cos(a0) * radius, 0, std::sin(a0) * radius}; - Vec3 p1 = base_center + Vec3{std::cos(a1) * radius, 0, std::sin(a1) * radius}; + Vec3 p0 = base_center + Vec3{::cos(a0) * radius, 0, ::sin(a0) * radius}; + Vec3 p1 = base_center + Vec3{::cos(a1) * radius, 0, ::sin(a1) * radius}; Vec3 n = Normalize(Cross(p1 - p0, apex - p0)); u32 i0 = PushVertex(lod, p0, n); u32 i1 = PushVertex(lod, p1, n); @@ -112,10 +113,10 @@ void AddEllipsoid(asset::MeshLod* lod, const Vec3& center, const Vec3& scale, u3 u32 base = static_cast(lod->vertices.size()); for (u32 y = 0; y <= rings; ++y) { f32 phi = kPi * static_cast(y) / static_cast(rings); - f32 sp = std::sin(phi), cp = std::cos(phi); + f32 sp = ::sin(phi), cp = ::cos(phi); for (u32 x = 0; x <= segments; ++x) { f32 theta = kTau * static_cast(x) / static_cast(segments); - Vec3 u{sp * std::cos(theta), cp, sp * std::sin(theta)}; + Vec3 u{sp * ::cos(theta), cp, sp * ::sin(theta)}; Vec3 p{center.x + u.x * scale.x, center.y + u.y * scale.y, center.z + u.z * scale.z}; // Ellipsoid surface normal: unit-sphere normal divided by the scale. Vec3 n = Normalize(Vec3{u.x / scale.x, u.y / scale.y, u.z / scale.z}); @@ -207,18 +208,18 @@ void Finalize(asset::Mesh* mesh, asset::AssetId id) { const asset::MeshLod& lod = mesh->lods[0]; Vec3 lo{1e30f, 1e30f, 1e30f}, hi{-1e30f, -1e30f, -1e30f}; for (const asset::Vertex& v : lod.vertices) { - lo.x = std::min(lo.x, v.position[0]); - lo.y = std::min(lo.y, v.position[1]); - lo.z = std::min(lo.z, v.position[2]); - hi.x = std::max(hi.x, v.position[0]); - hi.y = std::max(hi.y, v.position[1]); - hi.z = std::max(hi.z, v.position[2]); + lo.x = rx::Min(lo.x, v.position[0]); + lo.y = rx::Min(lo.y, v.position[1]); + lo.z = rx::Min(lo.z, v.position[2]); + hi.x = rx::Max(hi.x, v.position[0]); + hi.y = rx::Max(hi.y, v.position[1]); + hi.z = rx::Max(hi.z, v.position[2]); } Vec3 center = (lo + hi) * 0.5f; f32 r = 0; for (const asset::Vertex& v : lod.vertices) { Vec3 p{v.position[0], v.position[1], v.position[2]}; - r = std::max(r, Length(p - center)); + r = rx::Max(r, Length(p - center)); } mesh->bounds_center[0] = center.x; mesh->bounds_center[1] = center.y; @@ -334,10 +335,10 @@ asset::Mesh MakeRock(f32 radius, u32 seed, u64 material, asset::AssetId id) { for (u32 y = 0; y <= rings; ++y) { f32 phi = kPi * static_cast(y) / static_cast(rings); - f32 sp = std::sin(phi), cp = std::cos(phi); + f32 sp = ::sin(phi), cp = ::cos(phi); for (u32 x = 0; x <= segments; ++x) { f32 theta = kTau * static_cast(x) / static_cast(segments); - Vec3 u{sp * std::cos(theta), cp, sp * std::sin(theta)}; + Vec3 u{sp * ::cos(theta), cp, sp * ::sin(theta)}; f32 lump; if (y == 0) { lump = pole_top; @@ -361,7 +362,7 @@ asset::Mesh MakeRock(f32 radius, u32 seed, u64 material, asset::AssetId id) { } // Lift so the lowest point sits at y = 0 (rests on the ground). f32 min_y = 1e30f; - for (const asset::Vertex& v : lod.vertices) min_y = std::min(min_y, v.position[1]); + for (const asset::Vertex& v : lod.vertices) min_y = rx::Min(min_y, v.position[1]); for (asset::Vertex& v : lod.vertices) v.position[1] -= min_y; RecomputeNormals(&lod, vbegin, static_cast(lod.vertices.size()), ibegin, @@ -382,7 +383,7 @@ asset::Mesh MakeGrassTuft(f32 height, u64 material, asset::AssetId id) { for (u32 i = 0; i < kBlades; ++i) { // Deterministic per-blade angle, height and lean. f32 angle = kTau * (static_cast(i) / kBlades) + Hash11(i * 31u + 1u) * 0.4f; - Vec3 dir{std::cos(angle), 0, std::sin(angle)}; + Vec3 dir{::cos(angle), 0, ::sin(angle)}; Vec3 base{Hash11(i * 7u + 3u) * width * 0.3f, 0, Hash11(i * 13u + 5u) * width * 0.3f}; f32 h = height * (0.7f + 0.3f * Hash01(i * 17u + 9u)); Vec3 lean{Hash11(i * 23u + 2u) * height * 0.25f, 0, Hash11(i * 29u + 4u) * height * 0.25f}; @@ -411,7 +412,7 @@ asset::Mesh MakeDeadTree(f32 height, u64 material, asset::AssetId id) { f32 f = static_cast(b) / kBranches; f32 y = height * (0.5f + 0.32f * f); f32 angle = kTau * f + Hash11(static_cast(b) * 41u + 7u) * 0.5f; - Vec3 dir = Normalize(Vec3{std::cos(angle), 0.8f, std::sin(angle)}); + Vec3 dir = Normalize(Vec3{::cos(angle), 0.8f, ::sin(angle)}); f32 len = height * (0.28f - 0.06f * f); Vec3 root{0, y, 0}; f32 r = trunk_r * (0.55f - 0.12f * f); diff --git a/runtime/scene_authoring.cc b/runtime/scene_authoring.cc index 7606b1ce..3701c76d 100644 --- a/runtime/scene_authoring.cc +++ b/runtime/scene_authoring.cc @@ -1,19 +1,10 @@ #include "scene_authoring.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include "asset/gltf_loader.h" #include "asset/image_file.h" @@ -23,7 +14,21 @@ #include "asset/scene_import.h" #include "asset/texture_compress.h" #include "asset/vfs.h" +#include "base/algorithm.h" +#include "base/containers/pair.h" +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "base/containers/unordered_map.h" +#include "base/containers/unordered_set.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" +#include "core/format.h" #include "core/math.h" +#include "core/scalar.h" +#include "core/text_reader.h" #include "edit/hierarchy.h" #include "edit/reflect.h" #include "edit/scene_io.h" @@ -49,7 +54,7 @@ constexpr size_t kPatternKeyBytes = offsetof(ScenePattern, roughness_b) + sizeof(f32) - kPatternKeyOffset; // The layout notices nothing when that invariant is broken: Shape, Surface and -// Pattern end in tail padding (the std::string forces 8-byte alignment) that an +// Pattern end in tail padding (the base::String forces 8-byte alignment) that an // appended f32 lands in, leaving every offsetof and sizeof exactly as they were, // and Stretch has no padding to hide in but no sizeof anything reads either. // Counting the fields is what notices. Braces elide into the f32[N] members, so @@ -87,7 +92,7 @@ static_assert(FieldCount() == 15, // one list and cannot come to disagree about which props are maps. struct SurfaceMap { const char* prop; - std::string SceneSurface::*path; + base::String SceneSurface::*path; asset::AssetId asset::Material::*slot; bool srgb; // Which block format the map compresses to at import. It rides this table @@ -115,7 +120,7 @@ constexpr SurfaceMap kSurfaceMaps[] = { // Numbers go into the key as raw bytes rather than formatted: MakeAssetId // hashes the whole span, and a human-readable float would collide across values // that happen to print the same. -void AppendBytes(std::string& key, const void* data, size_t bytes) { +void AppendBytes(base::String& key, const void* data, size_t bytes) { key.append(static_cast(data), bytes); } @@ -123,9 +128,9 @@ void AppendBytes(std::string& key, const void* data, size_t bytes) { // material exactly when sharing them is correct. The stretch is in here because // it is baked into the vertices (see SceneStretch): nothing downstream can tell // two proportions apart once they have collided onto one mesh. -std::string ShapeKey(const SceneShape& shape, const SceneStretch& stretch, +base::String ShapeKey(const SceneShape& shape, const SceneStretch& stretch, const SceneSurface& surface, const ScenePattern* pattern) { - std::string key = "rxscene/" + shape.kind + "/" + surface.materialx + "/"; + base::String key = "rxscene/" + shape.kind + "/" + surface.materialx + "/"; // By hand, per the INVARIANT above: a string cannot ride the byte range, so // two surfaces differing only in which concrete they name would otherwise // collide onto one material and the second would draw the first one's maps. @@ -174,22 +179,22 @@ void ApplySurface(const SceneSurface& surface, asset::Material* material) { // nothing to say so. Role is part of the key for the same reason srgb is: it // decides how the file decodes, so one file bound as colour and as data stays // two textures. -asset::AssetId SurfaceMapId(const std::string& path, bool srgb, asset::TextureRole role) { - return asset::MakeAssetId("rxscene/map/" + std::to_string(static_cast(role)) + "/" + - std::string(srgb ? "srgb/" : "linear/") + +asset::AssetId SurfaceMapId(const base::String& path, bool srgb, asset::TextureRole role) { + return asset::MakeAssetId("rxscene/map/" + rx::ToString(static_cast(role)) + "/" + + base::String(srgb ? "srgb/" : "linear/") + asset::NormalizePath(path)); } // Decodes one image file, binds it to `slot` and hands it to the db and the // gpu. Empty on success, else the clause saying why the file is not an image. -std::string BindSurfaceTexture(const std::string& file, bool srgb, asset::TextureRole role, +base::String BindSurfaceTexture(const base::String& file, bool srgb, asset::TextureRole role, asset::AssetId asset::Material::*slot, asset::AssetDatabase& db, render::Renderer* renderer, asset::Material* material) { const asset::AssetId id = SurfaceMapId(file, srgb, role); if (!db.FindTexture(id)) { asset::Texture texture; if (!asset::LoadImageFile(file, srgb, id, &texture)) { - std::string problem = asset::ImageFileProblem(file); + base::String problem = asset::ImageFileProblem(file); // The header read and the decode disagree only on a truncated file. if (problem.empty()) problem = "has a readable header and then fails to decode"; return problem; @@ -202,7 +207,7 @@ std::string BindSurfaceTexture(const std::string& file, bool srgb, asset::Textur // 1x1 defaults for anything not uploaded yet, so every map has to reach the // gpu before the material naming it does. if (renderer) renderer->UploadTexture(texture); - db.AddTexture(std::move(texture)); + db.AddTexture(base::move(texture)); } material->*slot = id; return {}; @@ -224,16 +229,16 @@ void MarkSeparateMetallic(bool roughness, bool metallic, asset::Material* materi // not be resolved, which the caller turns into a `path:line:`. bool ApplySurfaceMaps(const SceneSurface& surface, asset::AssetDatabase& db, render::Renderer* renderer, asset::Material* material, const char** prop, - std::string* path, std::string* problem) { + base::String* path, base::String* problem) { for (const SurfaceMap& map : kSurfaceMaps) { - const std::string& file = surface.*map.path; + const base::String& file = surface.*map.path; if (file.empty()) continue; - std::string why = + base::String why = BindSurfaceTexture(file, map.srgb, map.role, map.slot, db, renderer, material); if (!why.empty()) { *prop = map.prop; *path = file; - *problem = std::move(why); + *problem = base::move(why); return false; } } @@ -245,10 +250,10 @@ bool ApplySurfaceMaps(const SceneSurface& surface, asset::AssetDatabase& db, // Empty on success, else the clause naming the file inside the document that // did not resolve; the caller puts it behind the Surface.materialx assignment, // since that is the line the author actually wrote. -std::string ApplyMaterialXMaps(const asset::MaterialXMaps& maps, asset::AssetDatabase& db, +base::String ApplyMaterialXMaps(const asset::MaterialXMaps& maps, asset::AssetDatabase& db, render::Renderer* renderer, asset::Material* material) { const struct { - const std::string& file; + const base::String& file; asset::AssetId asset::Material::*slot; bool srgb; asset::TextureRole role; @@ -262,10 +267,10 @@ std::string ApplyMaterialXMaps(const asset::MaterialXMaps& maps, asset::AssetDat }; for (const auto& binding : bindings) { if (binding.file.empty()) continue; - std::string why = BindSurfaceTexture(binding.file, binding.srgb, binding.role, binding.slot, + base::String why = BindSurfaceTexture(binding.file, binding.srgb, binding.role, binding.slot, db, renderer, material); if (!why.empty()) { - return std::format("names an image '{}' that {} (a MaterialX filename resolves against the " + return rx::StrFormat("names an image '{}' that {} (a MaterialX filename resolves against the " "document's own directory)", binding.file, why); } } @@ -275,8 +280,8 @@ std::string ApplyMaterialXMaps(const asset::MaterialXMaps& maps, asset::AssetDat // Synthesizes the pattern's maps, binds them to `material` and hands them to // the db and the gpu. False + *error on a pattern name nothing generates. -bool ApplyPattern(const ScenePattern& pattern, const std::string& key, asset::AssetDatabase& db, - render::Renderer* renderer, asset::Material* material, std::string* error) { +bool ApplyPattern(const ScenePattern& pattern, const base::String& key, asset::AssetDatabase& db, + render::Renderer* renderer, asset::Material* material, base::String* error) { asset::PatternDesc desc; if (!asset::ParsePatternKind(pattern.kind, &desc.kind)) { if (error) @@ -284,7 +289,7 @@ bool ApplyPattern(const ScenePattern& pattern, const std::string& key, asset::As "' (checker | grid | brick | gradient | noise)"; return false; } - desc.width = desc.height = std::clamp(pattern.resolution, 4u, 2048u); + desc.width = desc.height = rx::Clamp(pattern.resolution, 4u, 2048u); desc.scale[0] = pattern.scale[0]; desc.scale[1] = pattern.scale[1]; desc.line_width = pattern.line_width; @@ -301,27 +306,27 @@ bool ApplyPattern(const ScenePattern& pattern, const std::string& key, asset::As auto publish = [&](asset::Texture texture, asset::TextureRole role, const char* suffix) { asset::CompressTexture(&texture, role, key + suffix); if (renderer) renderer->UploadTexture(texture); - db.AddTexture(std::move(texture)); + db.AddTexture(base::move(texture)); }; asset::Texture color = asset::MakePatternTexture(desc, pattern.color_a, pattern.color_b, /*srgb=*/true, asset::MakeAssetId(key + "/base_color")); material->base_color = color.id; - publish(std::move(color), asset::TextureRole::kColor, "/base_color"); + publish(base::move(color), asset::TextureRole::kColor, "/base_color"); if (pattern.relief > 0.0f) { asset::Texture normal = asset::MakePatternNormalMap(desc, pattern.relief, asset::MakeAssetId(key + "/normal")); material->normal = normal.id; - publish(std::move(normal), asset::TextureRole::kNormalTangent, "/normal"); + publish(base::move(normal), asset::TextureRole::kNormalTangent, "/normal"); } if (pattern.roughness_a != pattern.roughness_b) { asset::Texture mr = asset::MakePatternRoughnessMap(desc, pattern.roughness_a, pattern.roughness_b, asset::MakeAssetId(key + "/roughness")); material->metallic_roughness = mr.id; - publish(std::move(mr), asset::TextureRole::kData, "/roughness"); + publish(base::move(mr), asset::TextureRole::kData, "/roughness"); } return true; } @@ -374,13 +379,13 @@ void BakeStretch(const f32 stretch[3], asset::Mesh* mesh) { // gpu's RxMaxAxisScale, for the same reason: a sphere that shrank below the // geometry would have the culls discard something that is on screen. for (u32 axis = 0; axis < 3; ++axis) mesh->bounds_center[axis] *= stretch[axis]; - mesh->bounds_radius *= std::max({stretch[0], stretch[1], stretch[2]}); + mesh->bounds_radius *= rx::Max({stretch[0], stretch[1], stretch[2]}); } // False + *error on a kind no primitive builds, which would otherwise put a box // where the author asked for something else. bool BuildShapeMesh(const SceneShape& shape, const SceneStretch& stretch, asset::AssetId id, - asset::Mesh* out, std::string* error) { + asset::Mesh* out, base::String* error) { // The table gates the chain rather than the other way round, so a kind added // below but not above fails loudly here instead of building a shape // --validate would then reject as unknown. @@ -428,8 +433,8 @@ bool MeshBounds(const asset::Mesh& mesh, SceneBounds* out) { } for (const asset::Vertex& vertex : vertices) { for (u32 axis = 0; axis < 3; ++axis) { - out->min[axis] = std::min(out->min[axis], vertex.position[axis]); - out->max[axis] = std::max(out->max[axis], vertex.position[axis]); + out->min[axis] = rx::Min(out->min[axis], vertex.position[axis]); + out->max[axis] = rx::Max(out->max[axis], vertex.position[axis]); } } return true; @@ -440,28 +445,28 @@ bool MeshBounds(const asset::Mesh& mesh, SceneBounds* out) { // ("#mesh"), so what a scene writes and what the importer names // its assets are the same convention rather than two that have to be kept in // step. Returns the clause saying why the path is not addressable, or empty. -std::string SplitModelPath(const std::string& path, std::string* file, i32* index) { +base::String SplitModelPath(const base::String& path, base::String* file, i32* index) { *file = path; *index = -1; if (path.empty()) return "is empty; there is nothing to place"; const size_t hash = path.rfind('#'); - if (hash != std::string::npos) { - const std::string_view fragment = std::string_view(path).substr(hash); - const std::string_view digits = fragment.substr(std::min(fragment.size(), 5)); + if (hash != base::String::npos) { + const base::StringRef fragment = base::StringRef(path).substr(hash); + const base::StringRef digits = fragment.substr(rx::Min(fragment.size(), 5)); if (fragment.compare(0, 5, "#mesh") != 0 || digits.empty() || - digits.find_first_not_of("0123456789") != std::string_view::npos) { - return std::format("has fragment '{}'; only '#mesh' selects one mesh of a file", + digits.find_first_not_of("0123456789") != base::StringRef::npos) { + return rx::StrFormat("has fragment '{}'; only '#mesh' selects one mesh of a file", fragment); } - *index = static_cast(std::strtol(std::string(digits).c_str(), nullptr, 10)); + *index = static_cast(::strtol(base::String(digits).c_str(), nullptr, 10)); *file = path.substr(0, hash); } - std::string extension; - if (const size_t dot = file->rfind('.'); dot != std::string::npos) { - for (char c : std::string_view(*file).substr(dot)) - extension += static_cast(std::tolower(static_cast(c))); + base::String extension; + if (const size_t dot = file->rfind('.'); dot != base::String::npos) { + for (char c : base::StringRef(*file).substr(dot)) + extension += static_cast(::tolower(static_cast(c))); } if (extension != ".gltf" && extension != ".glb") { return "is not a .gltf or .glb; a usd stage loads through --usd, which has its own importer"; @@ -472,10 +477,10 @@ std::string SplitModelPath(const std::string& path, std::string* file, i32* inde // Why the selection a Model.path makes cannot be honoured by the file that was // actually imported, or empty. Split out so the message is written once and // both a fresh import and an already-imported file are judged by it. -std::string SelectionProblem(i32 index, size_t meshes, size_t instances) { +base::String SelectionProblem(i32 index, size_t meshes, size_t instances) { if (index >= 0) { if (static_cast(index) >= meshes) { - return std::format("selects mesh {} of a file that has {}", index, meshes); + return rx::StrFormat("selects mesh {} of a file that has {}", index, meshes); } return {}; } @@ -492,9 +497,9 @@ std::string SelectionProblem(i32 index, size_t meshes, size_t instances) { // external buffers and images and yields N meshes, N materials, N textures and // the node instances that place them. The assets go into the database through // the Add* side channel below, which is what it is for. -std::string ImportModel(const std::string& path, asset::ImportedScene* scene, i32* index) { - std::string file; - if (std::string problem = SplitModelPath(path, &file, index); !problem.empty()) return problem; +base::String ImportModel(const base::String& path, asset::ImportedScene* scene, i32* index) { + base::String file; + if (base::String problem = SplitModelPath(path, &file, index); !problem.empty()) return problem; if (!asset::LoadGltfScene(file, scene)) { return "does not import (the path is relative to the working directory)"; } @@ -522,9 +527,9 @@ void EulerDegreesToQuat(const f32 euler[3], f32 out[4]) { // The Name.value of an entity, or empty. What Located narrows a finding by and // what an anchor message names a loop with. -std::string EntityName(ecs::World& world, ecs::Entity entity) { +base::String EntityName(ecs::World& world, ecs::Entity entity) { const scene::Name* name = world.Get(entity); - return name ? name->value : std::string(); + return name ? name->value : base::String(); } // Puts a build failure on the line that authored it. The pass runs on the @@ -538,33 +543,36 @@ std::string EntityName(ecs::World& world, ecs::Entity entity) { // assignment that is not there verbatim (an unreadable file, an edit since the // load, an entity a prefab expanded into) falls back to the first line that // matches at all, and then to no line, rather than pointing at the wrong one. -std::string Located(const std::string& scene_path, const std::string& owner, std::string_view key, - const std::string& value, const std::string& problem) { - std::ifstream in(scene_path, std::ios::binary); - std::string line; +base::String Located(const base::String& scene_path, const base::String& owner, base::StringRef key, + const base::String& value, const base::String& problem) { + // An unreadable file reads as empty, which falls through to no line. + base::String contents; + fs::ReadTextFile(scene_path, &contents); + LineReader in(contents); + base::StringRef line; int line_no = 0; int anywhere = 0; bool inside = owner.empty(); - while (std::getline(in, line)) { + while (in.Next(&line)) { ++line_no; const size_t a = line.find_first_not_of(" \t\r\n"); - if (a == std::string::npos || line[a] == '#' || line[a] == ';') continue; + if (a == base::StringRef::npos || line[a] == '#' || line[a] == ';') continue; const size_t b = line.find_last_not_of(" \t\r\n"); - const std::string_view text(line.data() + a, b - a + 1); + const base::StringRef text(line.data() + a, b - a + 1); if (!owner.empty()) { if (text == "entity") inside = false; - if (text.starts_with("Name.value")) inside = text.find(owner) != std::string_view::npos; + if (text.starts_with("Name.value")) inside = text.find(owner) != base::StringRef::npos; } - if (!text.starts_with(key) || text.find(value) == std::string_view::npos) continue; + if (!text.starts_with(key) || text.find(value) == base::StringRef::npos) continue; if (inside) { - return std::format("{}:{}: {} = \"{}\" {}", scene_path, line_no, key, value, problem); + return rx::StrFormat("{}:{}: {} = \"{}\" {}", scene_path, line_no, key, value, problem); } if (anywhere == 0) anywhere = line_no; } if (anywhere != 0) { - return std::format("{}:{}: {} = \"{}\" {}", scene_path, anywhere, key, value, problem); + return rx::StrFormat("{}:{}: {} = \"{}\" {}", scene_path, anywhere, key, value, problem); } - return std::format("{}: {} = \"{}\" {}", scene_path, key, value, problem); + return rx::StrFormat("{}: {} = \"{}\" {}", scene_path, key, value, problem); } u64 PackKey(ecs::Entity entity) { @@ -574,21 +582,20 @@ u64 PackKey(ecs::Entity entity) { // File order. World::Create hands out ascending indices and LoadScene calls it // once per `entity` block, so this is the order the author wrote, which is what // decides which grid cell a member lands in. -void SortByDeclaration(std::vector& entities) { - std::sort(entities.begin(), entities.end(), [](ecs::Entity a, ecs::Entity b) { - return a.index < b.index; - }); +void SortByDeclaration(base::Vector& entities) { + // Live entities never share an index, so any correct sort agrees. + base::Sort(entities.data(), entities.data() + entities.size(), + [](ecs::Entity a, ecs::Entity b) { return a.index < b.index; }); } // A prefab path is relative to the file that names it, not to the working // directory (see ScenePrefab), so that a scene and the prefabs it instances // move together. -std::string ResolveAgainst(const std::string& base, const std::string& path) { - const std::filesystem::path relative(path); - if (path.empty() || relative.is_absolute()) return path; - const std::filesystem::path dir = std::filesystem::path(base).parent_path(); +base::String ResolveAgainst(const base::String& base, const base::String& path) { + if (path.empty() || fs::IsAbsolute(path)) return path; + const base::StringRef dir = fs::ParentPath(base); if (dir.empty()) return path; - return (dir / relative).lexically_normal().string(); + return fs::LexicallyNormal(fs::Join(dir, path)); } // What an Anchor.target or a Grid.of resolves through. A name two entities @@ -599,27 +606,27 @@ class NameIndex { explicit NameIndex(ecs::World& world) { world.Each([&](ecs::Entity entity, scene::Name& name) { if (name.value.empty()) return; - auto [it, inserted] = by_name_.emplace(name.value, entity); - if (!inserted) it->second = ecs::kInvalidEntity; + auto [found, inserted] = by_name_.emplace(name.value, entity); + if (!inserted) *found = ecs::kInvalidEntity; }); } // The entity called `name`, kInvalidEntity when no entity or two do. `*shared` // tells those apart, which is the difference between "you misspelt it" and // "you named it twice". - ecs::Entity Find(const std::string& name, bool* shared) const { - const auto it = by_name_.find(name); - *shared = it != by_name_.end() && !it->second; - return it == by_name_.end() ? ecs::kInvalidEntity : it->second; + ecs::Entity Find(const base::String& name, bool* shared) const { + const ecs::Entity* found = by_name_.find(name); + *shared = found && !*found; + return found ? *found : ecs::kInvalidEntity; } private: - std::unordered_map by_name_; + base::UnorderedMap by_name_; }; // Why `name` names no single entity, or empty. Written once so a grid and an // anchor explain a bad reference the same way. -std::string TargetProblem(const std::string& name, ecs::Entity resolved, bool shared) { +base::String TargetProblem(const base::String& name, ecs::Entity resolved, bool shared) { if (name.empty()) return "is empty; a reference needs the target's Name.value"; if (shared) return "names two entities; one Name has to mean one entity for a reference to work"; if (!resolved) return "names no entity in this scene (references are by Name.value)"; @@ -628,7 +635,7 @@ std::string TargetProblem(const std::string& name, ecs::Entity resolved, bool sh } // namespace -u32 ShapeRequiredSizeAxes(std::string_view kind) { +u32 ShapeRequiredSizeAxes(base::StringRef kind) { for (const ShapeKind& entry : kShapeKinds) { if (kind == entry.name) return entry.required_size_axes; } @@ -856,9 +863,9 @@ bool ApplySceneEnvironment(ecs::World& world, render::RenderSettings* settings) // its negation. Azimuth turns +z toward +x, matching what a yaw of 90 does // to an entity, so "the sun is where that building is facing" is the same // number in both components. - const f32 horizontal = std::cos(elevation); - const Vec3 to_sun{horizontal * std::sin(azimuth), std::sin(elevation), - horizontal * std::cos(azimuth)}; + const f32 horizontal = ::cos(elevation); + const Vec3 to_sun{horizontal * ::sin(azimuth), ::sin(elevation), + horizontal * ::cos(azimuth)}; settings->sun_direction = {-to_sun.x, -to_sun.y, -to_sun.z}; settings->sun_color = {sun.color[0], sun.color[1], sun.color[2]}; settings->sun_intensity = sun.intensity; @@ -882,14 +889,14 @@ bool ApplySceneEnvironment(ecs::World& world, render::RenderSettings* settings) } bool BuildSceneShapes(ecs::World& world, asset::AssetDatabase& db, render::Renderer* renderer, - const std::string& scene_path, std::string* error) { + const base::String& scene_path, base::String* error) { // The Renderable is added after the walk: adding a component moves the entity // between archetypes, which must not happen under Each. - std::vector> renderables; - std::unordered_set built; + base::Vector> renderables; + base::UnorderedSet built; // Keyed by mesh, not by entity: shapes that agree on every field share one // mesh, so the second entity onto a mesh never reaches the builder below. - std::unordered_map bounds; + base::UnorderedMap bounds; bool ok = true; world.Each([&](ecs::Entity e, SceneShape& shape) { @@ -906,9 +913,9 @@ bool BuildSceneShapes(ecs::World& world, asset::AssetDatabase& db, render::Rende if (stretch.scale[axis] > 0.0f) continue; if (error) { *error = Located(scene_path, EntityName(world, e), "Stretch.scale", - std::format("{} {} {}", stretch.scale[0], stretch.scale[1], + rx::StrFormat("{} {} {}", stretch.scale[0], stretch.scale[1], stretch.scale[2]), - std::format("is {} on {}; every axis has to be positive, because the " + rx::StrFormat("is {} on {}; every axis has to be positive, because the " "mesh bake divides the normals by it (1 1 1 is no stretch)", stretch.scale[axis], "xyz"[axis])); } @@ -927,8 +934,8 @@ bool BuildSceneShapes(ecs::World& world, asset::AssetDatabase& db, render::Rende if (pattern->scale[axis] > 0.0f) continue; if (error) { *error = Located(scene_path, EntityName(world, e), "Pattern.scale", - std::format("{} {}", pattern->scale[0], pattern->scale[1]), - std::format("is {} along {}; the prop is cells across BY cells up, and " + rx::StrFormat("{} {}", pattern->scale[0], pattern->scale[1]), + rx::StrFormat("is {} along {}; the prop is cells across BY cells up, and " "both have to be positive (one number pads with a zero)", pattern->scale[axis], axis == 0 ? "u" : "v")); } @@ -944,7 +951,7 @@ bool BuildSceneShapes(ecs::World& world, asset::AssetDatabase& db, render::Rende for (const SceneSurfaceMapRef& map : SceneSurfaceMaps(surface)) { if (!pattern || map.path->empty()) continue; if (error) { - *error = Located(scene_path, EntityName(world, e), std::string("Surface.") + map.prop, + *error = Located(scene_path, EntityName(world, e), base::String("Surface.") + map.prop, *map.path, "is on an entity that also declares a Pattern; both bind the base " "colour, normal and roughness of one material, so one would silently " @@ -954,10 +961,10 @@ bool BuildSceneShapes(ecs::World& world, asset::AssetDatabase& db, render::Rende return; } - const std::string key = ShapeKey(shape, stretch, surface, pattern); + const base::String key = ShapeKey(shape, stretch, surface, pattern); const asset::AssetId mesh_id = asset::MakeAssetId(key); renderables.emplace_back(e, mesh_id); - if (!built.insert(mesh_id.hash).second) return; + if (!built.insert(mesh_id.hash)) return; asset::Material material; asset::MaterialXMaps document_maps; @@ -971,7 +978,7 @@ bool BuildSceneShapes(ecs::World& world, asset::AssetDatabase& db, render::Rende material.id = asset::MakeAssetId(key + "/material"); // Before the Surface's own maps, so an explicitly named one replaces the // slot the document filled rather than the other way round. - if (std::string why = ApplyMaterialXMaps(document_maps, db, renderer, &material); + if (base::String why = ApplyMaterialXMaps(document_maps, db, renderer, &material); !why.empty()) { if (error) { *error = Located(scene_path, EntityName(world, e), "Surface.materialx", surface.materialx, @@ -983,11 +990,11 @@ bool BuildSceneShapes(ecs::World& world, asset::AssetDatabase& db, render::Rende // After the materialx load, so a map named beside a document replaces that // one slot of it, which is the precedence a Pattern already has over one. const char* prop = nullptr; - std::string offender; - std::string problem; + base::String offender; + base::String problem; if (!ApplySurfaceMaps(surface, db, renderer, &material, &prop, &offender, &problem)) { if (error) { - *error = Located(scene_path, EntityName(world, e), std::string("Surface.") + prop, + *error = Located(scene_path, EntityName(world, e), base::String("Surface.") + prop, offender, problem); } ok = false; @@ -1026,42 +1033,42 @@ bool BuildSceneShapes(ecs::World& world, asset::AssetDatabase& db, render::Rende // would upload fine and then never draw. if (!world.Has(entity)) world.Add(entity, scene::Transform{}); world.Add(entity, scene::Renderable{mesh}); - if (const auto it = bounds.find(mesh.hash); it != bounds.end()) { - world.Add(entity, it->second); + if (const SceneBounds* box = bounds.find(mesh.hash)) { + world.Add(entity, *box); } } return true; } -std::vector SceneSurfaceMaps(const SceneSurface& surface) { - std::vector maps; - maps.reserve(std::size(kSurfaceMaps)); +base::Vector SceneSurfaceMaps(const SceneSurface& surface) { + base::Vector maps; + maps.reserve(sizeof(kSurfaceMaps) / sizeof(kSurfaceMaps[0])); for (const SurfaceMap& map : kSurfaceMaps) maps.push_back({map.prop, &(surface.*map.path)}); return maps; } -std::string SceneSurfaceMapProblem(const std::string& path) { +base::String SceneSurfaceMapProblem(const base::String& path) { return asset::ImageFileProblem(path); } -std::string SceneModelProblem(const std::string& path) { +base::String SceneModelProblem(const base::String& path) { asset::ImportedScene discarded; i32 index = -1; return ImportModel(path, &discarded, &index); } bool BuildSceneModels(ecs::World& world, asset::AssetDatabase& db, render::Renderer* renderer, - const std::string& scene_path, std::string* error) { + const base::String& scene_path, base::String* error) { // What one imported file leaves behind, so a second entity naming the same // file places it again without re-parsing it and, more importantly, without // uploading the same mesh ids to the gpu twice. struct Imported { - std::vector meshes; // by source index, which is what #mesh counts - std::vector bounds; // parallel to meshes; empty entries stay unmeasurable - std::vector measured; - std::vector instances; + base::Vector meshes; // by source index, which is what #mesh counts + base::Vector bounds; // parallel to meshes; empty entries stay unmeasurable + base::Vector measured; + base::Vector instances; }; - std::unordered_map by_file; + base::UnorderedMap by_file; // Both are deferred out of the walk: adding a component moves the entity // between archetypes and creating one appends to them, neither of which may @@ -1072,20 +1079,20 @@ bool BuildSceneModels(ecs::World& world, asset::AssetDatabase& db, render::Rende SceneBounds bounds; bool measured = false; }; - std::vector renderables; + base::Vector renderables; struct Child : Placed { asset::ImportedScene::Instance placement; }; - std::vector children; + base::Vector children; bool ok = true; world.Each([&](ecs::Entity e, SceneModel& model) { if (!ok) return; - std::string file; + base::String file; i32 index = -1; - std::string problem = SplitModelPath(model.path, &file, &index); - auto imported = by_file.find(file); - if (problem.empty() && imported == by_file.end()) { + base::String problem = SplitModelPath(model.path, &file, &index); + Imported* imported = by_file.find(file); + if (problem.empty() && !imported) { asset::ImportedScene scene; problem = ImportModel(model.path, &scene, &index); if (problem.empty()) { @@ -1098,7 +1105,7 @@ bool BuildSceneModels(ecs::World& world, asset::AssetDatabase& db, render::Rende for (asset::Texture& texture : scene.textures) { if (!texture.id) continue; // a decode the importer already warned about if (renderer) renderer->UploadTexture(texture); - db.AddTexture(std::move(texture)); + db.AddTexture(base::move(texture)); } for (const asset::Material& material : scene.materials) { if (renderer) renderer->UploadMaterial(material); @@ -1110,15 +1117,15 @@ bool BuildSceneModels(ecs::World& world, asset::AssetDatabase& db, render::Rende SceneBounds box; entry.measured.push_back(MeshBounds(mesh, &box)); entry.bounds.push_back(box); - db.AddMesh(std::move(mesh)); + db.AddMesh(base::move(mesh)); } entry.instances.assign(scene.instances.begin(), scene.instances.end()); imported = by_file.find(file); } } else if (problem.empty()) { // The file is known good; only this entity's own selection is not. - problem = SelectionProblem(index, imported->second.meshes.size(), - imported->second.instances.size()); + problem = SelectionProblem(index, imported->meshes.size(), + imported->instances.size()); } if (!problem.empty()) { if (error) @@ -1127,7 +1134,7 @@ bool BuildSceneModels(ecs::World& world, asset::AssetDatabase& db, render::Rende return; } - const Imported& file_assets = imported->second; + const Imported& file_assets = *imported; auto placed = [&](ecs::Entity entity, size_t mesh_index) { return Placed{entity, file_assets.meshes[mesh_index], file_assets.bounds[mesh_index], file_assets.measured[mesh_index]}; @@ -1163,7 +1170,7 @@ bool BuildSceneModels(ecs::World& world, asset::AssetDatabase& db, render::Rende transform.position[0] = child.placement.position.x; transform.position[1] = child.placement.position.y; transform.position[2] = child.placement.position.z; - std::memcpy(transform.rotation, child.placement.rotation, sizeof(transform.rotation)); + base::MemCopy(transform.rotation, child.placement.rotation, sizeof(transform.rotation)); transform.scale = child.placement.scale; world.Add(entity, transform); world.Add(entity, scene::Renderable{child.mesh}); @@ -1191,16 +1198,16 @@ struct PrefabFile { asset::Vfs vfs; asset::AssetDatabase db{vfs}; ecs::World world; - std::vector entities; // file order; entities[0] is the prefab root + base::Vector entities; // file order; entities[0] is the prefab root }; // Loads a prefab file, or the clause saying why it is not instanceable. Strict, // like every other .rxscene the runtime loads: a prefab that silently dropped a // misspelt component would place a subtly wrong thing everywhere it is used. -std::string OpenPrefab(const std::string& resolved, PrefabFile* file) { - std::string error; +base::String OpenPrefab(const base::String& resolved, PrefabFile* file) { + base::String error; if (!edit::LoadScene(file->world, file->db, resolved, &error, /*strict=*/true)) { - return std::format("does not load as '{}': {}", resolved, error); + return rx::StrFormat("does not load as '{}': {}", resolved, error); } const size_t count = file->world.entity_count(); for (size_t index = 0; index < count; ++index) { @@ -1222,7 +1229,7 @@ std::string OpenPrefab(const std::string& resolved, PrefabFile* file) { // so it cannot be copied either, which is correct - the copy's bounds come from // the geometry the copy builds. void CopyComponents(ecs::World& src, ecs::Entity from, ecs::World& dst, ecs::Entity to, - bool keep_existing, const std::unordered_map& remap) { + bool keep_existing, const base::UnorderedMap& remap) { for (const edit::ComponentDesc* comp : edit::ComponentsOn(src, from)) { if (comp->id == ecs::GetComponentId()) continue; if (dst.HasRaw(to, comp->id)) { @@ -1235,9 +1242,8 @@ void CopyComponents(ecs::World& src, ecs::Entity from, ecs::World& dst, ecs::Ent edit::PropValue value; if (!edit::GetProp(src, from, *comp, prop, &value)) continue; if (prop.type == edit::PropType::kEntity) { - const auto found = remap.find(PackKey(value.e)); - value = edit::PropValue::EntityV(found == remap.end() ? ecs::kInvalidEntity - : found->second); + const ecs::Entity* found = remap.find(PackKey(value.e)); + value = edit::PropValue::EntityV(found ? *found : ecs::kInvalidEntity); } edit::SetProp(dst, to, *comp, prop, value); } @@ -1258,7 +1264,7 @@ constexpr AnchorMode kAnchorModes[] = { {"left", 0, false}, {"front", 2, true}, {"behind", 2, false}, }; -const AnchorMode* FindAnchorMode(const std::string& name) { +const AnchorMode* FindAnchorMode(const base::String& name) { for (const AnchorMode& mode : kAnchorModes) { if (name == mode.name) return &mode; } @@ -1274,13 +1280,13 @@ struct Aabb { void Include(Aabb* box, const Vec3& point) { const f32 p[3] = {point.x, point.y, point.z}; for (u32 axis = 0; axis < 3; ++axis) { - box->min[axis] = box->empty ? p[axis] : std::min(box->min[axis], p[axis]); - box->max[axis] = box->empty ? p[axis] : std::max(box->max[axis], p[axis]); + box->min[axis] = box->empty ? p[axis] : rx::Min(box->min[axis], p[axis]); + box->max[axis] = box->empty ? p[axis] : rx::Max(box->max[axis], p[axis]); } box->empty = false; } -using ChildMap = std::unordered_map>; +using ChildMap = base::UnorderedMap>; ChildMap MapChildren(ecs::World& world) { ChildMap children; @@ -1311,9 +1317,9 @@ void AccumulateBounds(ecs::World& world, const ChildMap& children, ecs::Entity e Include(out, origin + Rotate(rotation, local * at.scale)); } } - const auto found = children.find(PackKey(entity)); - if (found == children.end()) return; - for (ecs::Entity child : found->second) { + const base::Vector* found = children.find(PackKey(entity)); + if (!found) return; + for (ecs::Entity child : *found) { const scene::Transform* local = world.Get(child); AccumulateBounds(world, children, child, edit::ComposeTransform(at, local ? *local : scene::Transform{}), out, @@ -1341,9 +1347,8 @@ class AnchorOrder { AnchorOrder(ecs::World& world, const NameIndex& names) : world_(world), names_(names) {} bool Visit(ecs::Entity entity) { - const auto seen = state_.find(PackKey(entity)); - if (seen != state_.end()) { - if (seen->second == 2) return true; + if (const int* seen = state_.find(PackKey(entity))) { + if (*seen == 2) return true; ReportCycle(entity); return false; } @@ -1351,13 +1356,13 @@ class AnchorOrder { stack_.push_back(entity); if (const SceneAnchor* anchor = world_.Get(entity)) { - const std::string target = anchor->target; + const base::String target = anchor->target; bool shared = false; const ecs::Entity to = names_.Find(target, &shared); - if (std::string why = TargetProblem(target, to, shared); !why.empty()) { + if (base::String why = TargetProblem(target, to, shared); !why.empty()) { owner_ = EntityName(world_, entity); offender_ = target; - problem_ = std::move(why); + problem_ = base::move(why); return false; } if (!Visit(to)) return false; @@ -1374,45 +1379,45 @@ class AnchorOrder { return true; } - const std::vector& order() const { return order_; } + const base::Vector& order() const { return order_; } // The entity carrying the assignment that failed, and the value it named. - const std::string& owner() const { return owner_; } - const std::string& offender() const { return offender_; } - const std::string& problem() const { return problem_; } + const base::String& owner() const { return owner_; } + const base::String& offender() const { return offender_; } + const base::String& problem() const { return problem_; } private: void ReportCycle(ecs::Entity closed) { if (!problem_.empty()) return; // the first failure is the one worth naming - std::string loop; + base::String loop; bool inside = false; for (ecs::Entity step : stack_) { inside = inside || step == closed; if (!inside) continue; - const std::string step_name = EntityName(world_, step); + const base::String step_name = EntityName(world_, step); loop += (step_name.empty() ? "" : step_name) + " -> "; } // The link that closed the loop is the one on top of the stack, so that is // the Anchor.target line to put the finding on. - owner_ = stack_.empty() ? std::string() : EntityName(world_, stack_.back()); + owner_ = stack_.empty() ? base::String() : EntityName(world_, stack_.back()); offender_ = EntityName(world_, closed); - problem_ = std::format("closes a cycle ({}{}); anchors resolve in dependency order, which a " + problem_ = rx::StrFormat("closes a cycle ({}{}); anchors resolve in dependency order, which a " "loop has none of", loop, offender_); } ecs::World& world_; const NameIndex& names_; - std::unordered_map state_; - std::vector stack_; - std::vector order_; - std::string owner_; - std::string offender_; - std::string problem_; + base::UnorderedMap state_; + base::Vector stack_; + base::Vector order_; + base::String owner_; + base::String offender_; + base::String problem_; }; } // namespace -bool BuildSceneGrids(ecs::World& world, const std::string& scene_path, std::string* error) { - std::vector members; +bool BuildSceneGrids(ecs::World& world, const base::String& scene_path, base::String* error) { + base::Vector members; world.Each([&](ecs::Entity entity, SceneGrid& grid) { if (!grid.of.empty()) members.push_back(entity); }); @@ -1420,7 +1425,7 @@ bool BuildSceneGrids(ecs::World& world, const std::string& scene_path, std::stri SortByDeclaration(members); const NameIndex names(world); - std::unordered_map taken; // container -> cells already claimed + base::UnorderedMap taken; // container -> cells already claimed // Deferred out of the loop below for the usual reason: adding Parent or // Transform moves an entity between archetypes and invalidates every pointer // into the storage the loop is reading. @@ -1428,16 +1433,16 @@ bool BuildSceneGrids(ecs::World& world, const std::string& scene_path, std::stri ecs::Entity entity; ecs::Entity container; f32 offset[3]; - std::string cell; + base::String cell; }; - std::vector placements; + base::Vector placements; for (ecs::Entity entity : members) { - const std::string of = world.Get(entity)->of; - const std::string owner = EntityName(world, entity); + const base::String of = world.Get(entity)->of; + const base::String owner = EntityName(world, entity); bool shared = false; const ecs::Entity container = names.Find(of, &shared); - if (std::string why = TargetProblem(of, container, shared); !why.empty()) { + if (base::String why = TargetProblem(of, container, shared); !why.empty()) { if (error) *error = Located(scene_path, owner, "Grid.of", of, why); return false; } @@ -1456,7 +1461,7 @@ bool BuildSceneGrids(ecs::World& world, const std::string& scene_path, std::stri if (extent[0] == 0 || extent[1] == 0 || extent[2] == 0) { if (error) *error = Located(scene_path, owner, "Grid.of", of, - std::format("names a grid whose Grid.count is {} {} {}; an axis with no " + rx::StrFormat("names a grid whose Grid.count is {} {} {}; an axis with no " "cells leaves it nowhere to put anything", grid.count[0], grid.count[1], grid.count[2])); return false; @@ -1467,7 +1472,7 @@ bool BuildSceneGrids(ecs::World& world, const std::string& scene_path, std::stri if (cell >= cells) { if (error) *error = Located(scene_path, owner, "Grid.of", of, - std::format("names a grid of {} cells and this is its member {}; two " + rx::StrFormat("names a grid of {} cells and this is its member {}; two " "members on one coordinate read as a missing object", cells, cell + 1)); return false; @@ -1478,7 +1483,7 @@ bool BuildSceneGrids(ecs::World& world, const std::string& scene_path, std::stri for (u32 axis = 0; axis < 3; ++axis) { placement.offset[axis] = static_cast(index[axis]) * grid.step[axis]; } - placements.push_back(std::move(placement)); + placements.push_back(base::move(placement)); } for (const Placement& placement : placements) { @@ -1486,10 +1491,10 @@ bool BuildSceneGrids(ecs::World& world, const std::string& scene_path, std::stri // alongside the Grid.of that gave it, so a layout that added would walk the // member one cell further along on every save/load round trip. if (scene::Transform* authored = world.Get(placement.entity)) { - std::memcpy(authored->position, placement.offset, sizeof(authored->position)); + base::MemCopy(authored->position, placement.offset, sizeof(authored->position)); } else { scene::Transform cell; - std::memcpy(cell.position, placement.offset, sizeof(cell.position)); + base::MemCopy(cell.position, placement.offset, sizeof(cell.position)); world.Add(placement.entity, cell); } // Parented rather than resolved to a world position, so the container's @@ -1537,8 +1542,8 @@ bool TurnedOffAxis(ecs::World& world, ecs::Entity entity) { // whatever Stretch the prefab already authored, not replacing it. Refused // rather than approximated when a non-uniform stretch meets a turned child // (TurnedOffAxis); uniform is a similarity and always safe. -std::string StretchPrefabChildren(ecs::World& world, ecs::Entity instance, - const std::vector& children) { +base::String StretchPrefabChildren(ecs::World& world, ecs::Entity instance, + const base::Vector& children) { const SceneStretch* authored = world.Get(instance); if (!authored) return {}; const f32 scale[3] = {authored->scale[0], authored->scale[1], authored->scale[2]}; @@ -1554,7 +1559,7 @@ std::string StretchPrefabChildren(ecs::World& world, ecs::Entity instance, if (!uniform) { for (ecs::Entity child : children) { if (!TurnedOffAxis(world, child)) continue; - return std::format( + return rx::StrFormat( "stretches a prefab by {} {} {} whose part '{}' is turned; a per-axis stretch of a " "rotated shape is a shear, which no mesh, bound or transform here can carry. Stretch " "it uniformly, turn the whole instance instead of the part, or author the part " @@ -1566,7 +1571,7 @@ std::string StretchPrefabChildren(ecs::World& world, ecs::Entity instance, // Deferred out of the walk for the usual reason: adding a Stretch or a // Transform moves the entity between archetypes and invalidates every pointer // into the storage above. - std::vector> add_stretch; + base::Vector> add_stretch; for (ecs::Entity child : children) { if (scene::Transform* transform = world.Get(child)) { for (u32 axis = 0; axis < 3; ++axis) transform->position[axis] *= scale[axis]; @@ -1589,20 +1594,20 @@ std::string StretchPrefabChildren(ecs::World& world, ecs::Entity instance, return {}; } -bool BuildScenePrefabs(ecs::World& world, const std::string& scene_path, std::string* error) { - std::unordered_map> files; +bool BuildScenePrefabs(ecs::World& world, const base::String& scene_path, base::String* error) { + base::UnorderedMap> files; // The file an entity came from, so a prefab instancing another resolves it // relative to itself, and the chain of files that produced it, so a prefab // reaching itself is caught on the second visit instead of by expanding until // memory runs out. - std::unordered_map origin; - std::unordered_map> chain; - std::unordered_set expanded; + base::UnorderedMap origin; + base::UnorderedMap> chain; + base::UnorderedSet expanded; // A pass at a time, because expanding one prefab can introduce another: a // prefab file whose own entities instance further prefabs. for (;;) { - std::vector pending; + base::Vector pending; world.Each([&](ecs::Entity entity, ScenePrefab&) { if (!expanded.contains(PackKey(entity))) pending.push_back(entity); }); @@ -1614,42 +1619,43 @@ bool BuildScenePrefabs(ecs::World& world, const std::string& scene_path, std::st expanded.insert(key); // Copied out before anything is added to the entity: an added component // moves it between archetypes and leaves the pointer dangling. - const std::string path = world.Get(entity)->path; - const auto from = origin.find(key); - const std::string base = from == origin.end() ? scene_path : from->second; - const std::string resolved = ResolveAgainst(base, path); - const std::vector reached = chain[key]; - const std::string owner = EntityName(world, entity); - - if (std::find(reached.begin(), reached.end(), resolved) != reached.end()) { + const base::String path = world.Get(entity)->path; + const base::String* from = origin.find(key); + const base::String base = from ? *from : scene_path; + const base::String resolved = ResolveAgainst(base, path); + const base::Vector reached = chain[key]; + const base::String owner = EntityName(world, entity); + + if (base::Find(reached.begin(), reached.end(), resolved) != reached.end()) { if (error) { - std::string loop; - for (const std::string& step : reached) loop += step + " -> "; + base::String loop; + for (const base::String& step : reached) loop += step + " -> "; *error = Located(base, owner, "Prefab.path", path, - std::format("instances itself ({}{}); the expansion would not " + rx::StrFormat("instances itself ({}{}); the expansion would not " "terminate", loop, resolved)); } return false; } - auto file = files.find(resolved); - if (file == files.end()) { - auto opened = std::make_unique(); - if (std::string why = OpenPrefab(resolved, opened.get()); !why.empty()) { + base::UniquePointer* file = files.find(resolved); + if (!file) { + auto opened = base::MakeUnique(); + if (base::String why = OpenPrefab(resolved, &*opened); !why.empty()) { if (error) *error = Located(base, owner, "Prefab.path", path, why); return false; } - file = files.emplace(resolved, std::move(opened)).first; + file = files.emplace(resolved, base::move(opened)).first; } - PrefabFile& prefab = *file->second; + // Boxed, so this survives the map growing on a later insert. + PrefabFile& prefab = **file; // Every copy exists before anything is copied into it, so a Parent inside // the prefab has a destination entity to be remapped onto. The root maps // onto the instance itself, which is what turns "Parent = the root" in the // file into "Parent = this instance" here. - std::unordered_map remap; + base::UnorderedMap remap; remap.emplace(PackKey(prefab.entities[0]), entity); - std::vector copies; + base::Vector copies; for (size_t i = 1; i < prefab.entities.size(); ++i) { copies.push_back(world.Create()); remap.emplace(PackKey(prefab.entities[i]), copies.back()); @@ -1657,7 +1663,7 @@ bool BuildScenePrefabs(ecs::World& world, const std::string& scene_path, std::st CopyComponents(prefab.world, prefab.entities[0], world, entity, /*keep_existing=*/true, remap); - std::vector descend = reached; + base::Vector descend = reached; descend.push_back(resolved); for (size_t i = 1; i < prefab.entities.size(); ++i) { const ecs::Entity copy = copies[i - 1]; @@ -1676,7 +1682,7 @@ bool BuildScenePrefabs(ecs::World& world, const std::string& scene_path, std::st // After every child exists and carries its authored components, so the // stretch multiplies into the prefab's own proportions rather than racing // the copy that establishes them. - if (std::string why = StretchPrefabChildren(world, entity, copies); !why.empty()) { + if (base::String why = StretchPrefabChildren(world, entity, copies); !why.empty()) { if (error) *error = Located(base, owner, "Prefab.path", path, why); return false; } @@ -1691,7 +1697,7 @@ void BuildSceneRotations(ecs::World& world) { // Writing through the pointer moves nothing between archetypes, so this half // is done in place; the entities needing a Transform ADDED are deferred, for // the usual reason. - std::vector untransformed; + base::Vector untransformed; world.Each([&](ecs::Entity entity, SceneRotation& rotation) { if (scene::Transform* transform = world.Get(entity)) { EulerDegreesToQuat(rotation.euler, transform->rotation); @@ -1710,8 +1716,8 @@ void BuildSceneRotations(ecs::World& world) { } } -bool BuildSceneAnchors(ecs::World& world, const std::string& scene_path, std::string* error) { - std::vector anchored; +bool BuildSceneAnchors(ecs::World& world, const base::String& scene_path, base::String* error) { + base::Vector anchored; world.Each([&](ecs::Entity entity, SceneAnchor&) { anchored.push_back(entity); }); if (anchored.empty()) return true; SortByDeclaration(anchored); @@ -1729,12 +1735,12 @@ bool BuildSceneAnchors(ecs::World& world, const std::string& scene_path, std::st const ChildMap children = MapChildren(world); for (ecs::Entity entity : order.order()) { const SceneAnchor& anchor = *world.Get(entity); - const std::string target_name = anchor.target; + const base::String target_name = anchor.target; // Copied out for the same reason target_name is: the placement below adds a // Transform to entities that had none, which moves them between archetypes // and leaves this reference dangling. const f32 offset[3] = {anchor.offset[0], anchor.offset[1], anchor.offset[2]}; - const std::string owner = EntityName(world, entity); + const base::String owner = EntityName(world, entity); const AnchorMode* mode = FindAnchorMode(anchor.mode); if (!mode) { if (error) @@ -1773,7 +1779,7 @@ bool BuildSceneAnchors(ecs::World& world, const std::string& scene_path, std::st // marker at the target's surface rather than refusing it - only the TARGET // has to have an extent for "on top of" to mean anything. scene::Transform upright = local; - std::memset(upright.position, 0, sizeof(upright.position)); + base::MemSet(upright.position, 0, sizeof(upright.position)); const Aabb self = SubtreeBounds(world, children, entity, upright); // Replaced rather than offset: a SaveScene writes the resolved position diff --git a/runtime/scene_authoring.h b/runtime/scene_authoring.h index 9893d6f0..6f9453d4 100644 --- a/runtime/scene_authoring.h +++ b/runtime/scene_authoring.h @@ -1,11 +1,11 @@ #ifndef RX_RUNTIME_SCENE_AUTHORING_H_ #define RX_RUNTIME_SCENE_AUTHORING_H_ -#include -#include -#include #include "asset/asset_database.h" +#include "base/containers/vector.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "core/types.h" #include "ecs/world.h" #include "render/core/renderer.h" @@ -30,7 +30,7 @@ namespace rx { // every other kind lacks is SceneStretch, which is its own component so that it // composes with a prefab's Shape instead of replacing it. struct SceneShape { - std::string kind = "box"; + base::String kind = "box"; f32 size[3] = {0.5f, 0.5f, 0.5f}; }; @@ -82,7 +82,7 @@ struct SceneSurface { // A MaterialX document to take the whole material from instead of the fields // above. Relative to the working directory; a document that will not load // fails the scene load rather than falling back to the authored values. - std::string materialx; + base::String materialx; // Image maps, each a path RELATIVE TO THE WORKING DIRECTORY (like materialx // and Model.path: a scene points at external art rather than owning it). // They LAYER ON the constants above, exactly as the glTF equivalent does: @@ -94,21 +94,21 @@ struct SceneSurface { // no default texture, because a substituted checkerboard is a render that // looks authored and is not. Refused together with a Pattern on one entity // (BuildSceneShapes): both bind the same three material slots. - std::string base_color_map; + base::String base_color_map; // Tangent space, OpenGL green-up (+y is +v), which is the convention the // engine's shaders and its own generated normal maps use. A DirectX-style map // (ambientCG ships both, _NormalDX and _NormalGL) lights inverted along v and // nothing here can tell the two apart, so pick the GL one. - std::string normal_map; + base::String normal_map; // Greyscale. Roughness and metallic are separate maps rather than one packed // ORM because that is how a CC0 texture set ships them; a scene wanting glTF // packing has the file's own material through Model. - std::string roughness_map; - std::string metallic_map; + base::String roughness_map; + base::String metallic_map; // Greyscale ambient occlusion, multiplying the INDIRECT light only. Direct // sun is unaffected, which is what keeps it from reading as painted-on dirt. - std::string occlusion_map; - std::string emissive_map; + base::String occlusion_map; + base::String emissive_map; }; // A procedural texture bound to the entity's SceneSurface, generated at load. @@ -116,7 +116,7 @@ struct SceneSurface { // roughness maps, so a surface's relief always describes the same bricks its // albedo does. Without one the surface is a flat colour. struct ScenePattern { - std::string kind = "checker"; + base::String kind = "checker"; // Cells across the shape's uv square, per axis: [0] along u, [1] along v. // Every primitive's uv covers 0..1 once, so this is the only thing that // decides how often the pattern repeats. @@ -160,7 +160,7 @@ struct ScenePattern { // covered: skinning and animation (bind pose at the origin) and the file's own // lights and cameras, which lose to the scene's. struct SceneModel { - std::string path; + base::String path; }; // Another .rxscene instanced here: the only reuse mechanism. `path` resolves @@ -177,7 +177,7 @@ struct SceneModel { // this Prefab line instead of the expansion (same treatment as a glTF's // instances). struct ScenePrefab { - std::string path; + base::String path; }; // Orientation in degrees, because a quaternion is not something an author @@ -243,8 +243,8 @@ struct SceneStretch { // Anchors resolve in dependency order, so anchoring to something itself // anchored works; a cycle fails the load naming the loop. struct SceneAnchor { - std::string target; - std::string mode = "on"; + base::String target; + base::String mode = "on"; f32 offset[3] = {0, 0, 0}; }; @@ -260,8 +260,8 @@ struct SceneAnchor { // Members are laid out before prefabs expand, so expanded entities cannot be // members: a grid lays out what the file declares. struct SceneGrid { - std::string of; - std::string cell; + base::String of; + base::String cell; // Cells along x, y and z. Fractional values truncate; the format has no // integer vector. f32 count[3] = {1, 1, 1}; @@ -339,7 +339,7 @@ void RegisterSceneComponents(); // a capsule of half height 0 is a legal sphere, so only its radius is required. // BuildSceneShapes and --validate both go through here, so neither can accept a // kind, or condemn a size, the other would not. -u32 ShapeRequiredSizeAxes(std::string_view kind); +u32 ShapeRequiredSizeAxes(base::StringRef kind); // Parents every SceneGrid member to the container it names, at the cell its // declaration order earns it, and hands it the container's `cell` prefab when @@ -351,7 +351,7 @@ u32 ShapeRequiredSizeAxes(std::string_view kind); // non-positive cell count, a member that is its own container, and a grid asked // to hold more members than it has cells - the last of which would otherwise // stack two cells on one coordinate, which reads as a missing object. -bool BuildSceneGrids(ecs::World& world, const std::string& scene_path, std::string* error); +bool BuildSceneGrids(ecs::World& world, const base::String& scene_path, base::String* error); // Expands every ScenePrefab entity: merges the prefab root's components into it // and adds one scene::Transient child per further entity of the prefab file @@ -362,7 +362,7 @@ bool BuildSceneGrids(ecs::World& world, const std::string& scene_path, std::stri // // False + *error on a prefab that does not load and on a prefab that reaches // itself, which would otherwise expand until memory ran out. -bool BuildScenePrefabs(ecs::World& world, const std::string& scene_path, std::string* error); +bool BuildScenePrefabs(ecs::World& world, const base::String& scene_path, base::String* error); // Resolves every SceneRotation into its entity's Transform.rotation, adding a // Transform to an entity that has none. Runs after BuildScenePrefabs, so a @@ -382,7 +382,7 @@ void BuildSceneRotations(ecs::World& world); // agree on (two entities of that Name), a target with no geometry to measure, // an unknown mode, and a cycle - each of which would otherwise leave an object // at the origin, which reads as a scene that failed to place it. -bool BuildSceneAnchors(ecs::World& world, const std::string& scene_path, std::string* error); +bool BuildSceneAnchors(ecs::World& world, const base::String& scene_path, base::String* error); // Builds one mesh + material per SceneShape entity, uploads them (`renderer` // null skips the GPU side) and points each entity's Renderable at the result. @@ -401,19 +401,19 @@ bool BuildSceneAnchors(ecs::World& world, const std::string& scene_path, std::st // Also on a Surface texture map that names no image, and on an entity carrying // both a Pattern and a Surface texture map (see SceneSurface). bool BuildSceneShapes(ecs::World& world, asset::AssetDatabase& db, render::Renderer* renderer, - const std::string& scene_path, std::string* error); + const base::String& scene_path, base::String* error); // Why a Surface texture-map path names no image that can be bound, or empty // when it does. Only the header is read, so this costs no decode; the point is // that BuildSceneShapes and --validate go through one function, and neither can // accept a map the other would reject. Same role SceneModelProblem plays for // Model.path. -std::string SceneSurfaceMapProblem(const std::string& path); +base::String SceneSurfaceMapProblem(const base::String& path); // One Surface prop that names an image, and what this surface writes in it. struct SceneSurfaceMapRef { const char* prop; // "base_color_map", "normal_map", ... - const std::string* path; // the authored value, empty when the prop is unset + const base::String* path; // the authored value, empty when the prop is unset }; // Every texture-map prop of `surface`, set or not, in declaration order. One @@ -421,14 +421,14 @@ struct SceneSurfaceMapRef { // disagree about which props are maps: a map added to SceneSurface and not to // the table behind this drops out of ShapeKey, and two surfaces differing only // in it then collide onto one material. -std::vector SceneSurfaceMaps(const SceneSurface& surface); +base::Vector SceneSurfaceMaps(const SceneSurface& surface); // Why a Model.path names no geometry that can be placed, or empty when it does: // the clause a caller puts behind a `path:line:`. Imports the file to answer, // because nothing short of that can tell a real file from a plausible name, or // know how many meshes are in it. BuildSceneModels and --validate both go // through here, so neither can accept a reference the other would reject. -std::string SceneModelProblem(const std::string& path); +base::String SceneModelProblem(const base::String& path); // Imports every Model entity's file (once per file, however many entities name // it), publishes its meshes, materials and textures into `db` and onto the gpu @@ -438,7 +438,7 @@ std::string SceneModelProblem(const std::string& path); // it. False + *error on a reference that resolves to nothing, which would // otherwise leave a hole exactly where the author asked for a model. bool BuildSceneModels(ecs::World& world, asset::AssetDatabase& db, render::Renderer* renderer, - const std::string& scene_path, std::string* error); + const base::String& scene_path, base::String* error); // Writes the first SceneSun and SceneAtmosphere the scene declares into // `settings`, and returns whether a Sun was among them - which the caller has diff --git a/runtime/scene_hook_demo.cc b/runtime/scene_hook_demo.cc index 331b9665..f968fc09 100644 --- a/runtime/scene_hook_demo.cc +++ b/runtime/scene_hook_demo.cc @@ -1,6 +1,6 @@ #include "scene_hook_demo.h" -#include +#include #include "core/log.h" #include "render/rhi/vulkan_interop.h" @@ -211,7 +211,7 @@ void SceneHookDemo::Record(const render::SceneHookContext& ctx) { colors[1] = {.view = ctx.depth_export_view, .load = render::LoadOp::kLoad}; render::DepthAttachment depth{.view = ctx.depth_view, .load = render::LoadOp::kLoad}; ctx.cmd->BeginRendering( - {.extent = ctx.extent, .colors = {colors, 2}, .depth = &depth}); + {.extent = ctx.extent, .colors = base::Span(colors, 2), .depth = &depth}); vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_); vkCmdPushConstants(cb, layout_, kPushStages, 0, sizeof(push), &push); vkCmdDraw(cb, 36, instance_count_, 0, 0); // 12 tris per box, instanced diff --git a/runtime/scene_hook_rhi_demo.cc b/runtime/scene_hook_rhi_demo.cc index 4a183609..e087f99f 100644 --- a/runtime/scene_hook_rhi_demo.cc +++ b/runtime/scene_hook_rhi_demo.cc @@ -1,11 +1,12 @@ #include "scene_hook_rhi_demo.h" -#include -#include +#include +#include #include "core/log.h" #include "render/rhi/bindings.h" +#include "base/memory/mem_ops.h" #include "shaders/scenehook_rhi_cull_cs_hlsl.h" #include "shaders/scenehook_rhi_ms_hlsl.h" #include "shaders/scenehook_rhi_ps_hlsl.h" @@ -77,7 +78,7 @@ bool SceneHookRhiDemo::CreateTextureArray() { RX_ERROR("scenehook-rhi: texture staging buffer failed"); return false; } - std::memcpy(staging.mapped, pixels.data(), pixels.size()); + base::MemCopy(staging.mapped, pixels.data(), pixels.size()); device_->ImmediateSubmit([&](render::CommandList& cmd) { cmd.Barrier(render::Transition(tex_array_, render::ResourceState::kUndefined, @@ -89,7 +90,7 @@ bool SceneHookRhiDemo::CreateTextureArray() { .array_layer = l, .extent = {kTexSize, kTexSize}}; } - cmd.CopyBufferToTexture(staging, tex_array_, {regions.data(), regions.size()}); + cmd.CopyBufferToTexture(staging, tex_array_, base::Span(regions.data(), regions.size())); cmd.Barrier(render::Transition(tex_array_, render::ResourceState::kCopyDst, render::ResourceState::kShaderReadFragment)); }); @@ -237,7 +238,7 @@ void SceneHookRhiDemo::Record(const render::SceneHookContext& ctx) { colors[0] = {.view = ctx.color_view, .load = render::LoadOp::kLoad}; colors[1] = {.view = ctx.depth_export_view, .load = render::LoadOp::kLoad}; render::DepthAttachment depth{.view = ctx.depth_view, .load = render::LoadOp::kLoad}; - ctx.cmd->BeginRendering({.extent = ctx.extent, .colors = {colors, 2}, .depth = &depth}); + ctx.cmd->BeginRendering({.extent = ctx.extent, .colors = base::Span(colors, 2), .depth = &depth}); ctx.cmd->BindPipeline(draw_pipeline_); ctx.cmd->BindTransient(0, {render::Bind::Combined(0, tex_array_.view, sampler_)}); diff --git a/runtime/scene_validate.cc b/runtime/scene_validate.cc index 3a29bf6f..5b894157 100644 --- a/runtime/scene_validate.cc +++ b/runtime/scene_validate.cc @@ -1,31 +1,31 @@ #include "scene_validate.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include "asset/asset_database.h" #include "asset/asset_id.h" #include "asset/materialx.h" #include "asset/procedural_texture.h" #include "asset/vfs.h" +#include "base/algorithm.h" +#include "base/containers/unordered_map.h" +#include "base/containers/vector.h" +#include "base/memory/move.h" +#include "base/optional.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/format.h" #include "core/log.h" #include "ecs/world.h" #include "edit/reflect.h" #include "edit/scene_io.h" #include "scene/components.h" #include "scene_authoring.h" +#include "core/file_system.h" +#include "core/text_reader.h" namespace rx { namespace { @@ -33,23 +33,23 @@ namespace { struct Finding { bool error = true; const char* check = ""; - std::string entity; // Name.value, empty when the entity is unnamed + base::String entity; // Name.value, empty when the entity is unnamed int line = 0; // source line of the entity's `entity` keyword, 0 = file-wide - std::string message; + base::String message; }; // One `Component.prop = value` assignment as it was written. struct SourceAssign { u32 entity_index = 0; int line = 0; - std::string comp; - std::string prop; - std::string raw; + base::String comp; + base::String prop; + base::String raw; }; struct Source { - std::vector entity_lines; // source line of each `entity` keyword, in order - std::vector assigns; + base::Vector entity_lines; // source line of each `entity` keyword, in order + base::Vector assigns; }; // Re-reads the file the way the loader tokenizes it (trim, skip blank and #/; @@ -57,17 +57,19 @@ struct Source { // LoadScene throws away: pointing a finding at a line rather than an entity // ordinal, and seeing what the value parser silently DID with a literal, which // is not recoverable from the value it produced. -Source ScanSource(const std::string& path) { +Source ScanSource(const base::String& path) { Source source; - std::ifstream in(path, std::ios::binary); - std::string line; + base::String contents; + fs::ReadTextFile(path, &contents); + LineReader in(contents); + base::StringRef line; int line_no = 0; - while (std::getline(in, line)) { + while (in.Next(&line)) { ++line_no; const size_t a = line.find_first_not_of(" \t\r\n"); - if (a == std::string::npos) continue; + if (a == base::StringRef::npos) continue; const size_t b = line.find_last_not_of(" \t\r\n"); - const std::string_view t(line.data() + a, b - a + 1); + const base::StringRef t(line.data() + a, b - a + 1); if (t[0] == '#' || t[0] == ';') continue; if (t == "entity") { source.entity_lines.push_back(line_no); @@ -75,17 +77,17 @@ Source ScanSource(const std::string& path) { } if (source.entity_lines.empty()) continue; // stray line before the first entity const size_t eq = t.find('='); - if (eq == std::string_view::npos) continue; // tag component - std::string_view key = t.substr(0, eq); + if (eq == base::StringRef::npos) continue; // tag component + base::StringRef key = t.substr(0, eq); while (!key.empty() && (key.back() == ' ' || key.back() == '\t')) key.remove_suffix(1); const size_t dot = key.find('.'); - if (dot == std::string_view::npos) continue; - std::string_view raw = t.substr(eq + 1); + if (dot == base::StringRef::npos) continue; + base::StringRef raw = t.substr(eq + 1); const size_t raw_start = raw.find_first_not_of(" \t"); - raw = raw_start == std::string_view::npos ? std::string_view{} : raw.substr(raw_start); + raw = raw_start == base::StringRef::npos ? base::StringRef{} : raw.substr(raw_start); source.assigns.push_back({static_cast(source.entity_lines.size() - 1), line_no, - std::string(key.substr(0, dot)), std::string(key.substr(dot + 1)), - std::string(raw)}); + base::String(key.substr(0, dot)), base::String(key.substr(dot + 1)), + base::String(raw)}); } return source; } @@ -97,8 +99,8 @@ Source ScanSource(const std::string& path) { // ordinal, which is what turns a finding into a file:line, what lets a source // assignment name the entity it belongs to, and what lets the walk below reach // every entity at all (the ecs has no all-entities iterator, only Each). -std::vector AllEntities(ecs::World& world) { - std::vector out; +base::Vector AllEntities(ecs::World& world) { + base::Vector out; const size_t count = world.entity_count(); for (size_t index = 0; index < count; ++index) { const ecs::Entity entity{static_cast(index), 0}; @@ -109,46 +111,57 @@ std::vector AllEntities(ecs::World& world) { class Report { public: - Report(ecs::World& world, std::vector entity_lines) - : world_(world), entity_lines_(std::move(entity_lines)) {} + Report(ecs::World& world, base::Vector entity_lines) + : world_(world), entity_lines_(base::move(entity_lines)) {} - void Error(ecs::Entity entity, const char* check, std::string message) { - Add(true, entity, 0, check, std::move(message)); + void Error(ecs::Entity entity, const char* check, base::String message) { + Add(true, entity, 0, check, base::move(message)); } - void Warn(ecs::Entity entity, const char* check, std::string message) { - Add(false, entity, 0, check, std::move(message)); + void Warn(ecs::Entity entity, const char* check, base::String message) { + Add(false, entity, 0, check, base::move(message)); } // For the checks that read the source text and so know the exact assignment, // not just the entity block it sits in. - void ErrorAt(ecs::Entity entity, int line, const char* check, std::string message) { - Add(true, entity, line, check, std::move(message)); + void ErrorAt(ecs::Entity entity, int line, const char* check, base::String message) { + Add(true, entity, line, check, base::move(message)); } - void WarnAt(ecs::Entity entity, int line, const char* check, std::string message) { - Add(false, entity, line, check, std::move(message)); + void WarnAt(ecs::Entity entity, int line, const char* check, base::String message) { + Add(false, entity, line, check, base::move(message)); } // A finding about the file as a whole rather than about one entity. - void FileError(const char* check, std::string message) { - findings_.push_back({true, check, {}, 0, std::move(message)}); + void FileError(const char* check, base::String message) { + findings_.push_back({true, check, {}, 0, base::move(message)}); ++errors_; } - void FileWarn(const char* check, std::string message) { - findings_.push_back({false, check, {}, 0, std::move(message)}); + void FileWarn(const char* check, base::String message) { + findings_.push_back({false, check, {}, 0, base::move(message)}); } // File order, so the report reads down the scene the way the author wrote it. // File-wide findings carry no line and go last. void SortByLine() { - std::stable_sort(findings_.begin(), findings_.end(), [](const Finding& a, const Finding& b) { - return (a.line ? a.line : INT_MAX) < (b.line ? b.line : INT_MAX); + // A stable sort: ties on line keep insertion order. The index tiebreak + // makes every key unique, so any correct sort yields the stable order. + auto key = [](const Finding& f) { return f.line ? f.line : INT_MAX; }; + base::Vector order(findings_.size()); + base::Iota(order.begin(), order.end(), 0u); + base::Sort(order.data(), order.data() + order.size(), [&](u32 a, u32 b) { + const int ka = key(findings_[a]); + const int kb = key(findings_[b]); + return ka < kb || (ka == kb && a < b); }); + base::Vector sorted; + sorted.reserve(findings_.size()); + for (u32 i : order) sorted.push_back(base::move(findings_[i])); + findings_ = base::move(sorted); } - const std::vector& findings() const { return findings_; } + const base::Vector& findings() const { return findings_; } u32 errors() const { return errors_; } u32 warnings() const { return static_cast(findings_.size()) - errors_; } private: - void Add(bool error, ecs::Entity entity, int line, const char* check, std::string message) { + void Add(bool error, ecs::Entity entity, int line, const char* check, base::String message) { Finding finding; finding.error = error; finding.check = check; @@ -156,14 +169,14 @@ class Report { finding.line = line; if (line == 0 && entity.index < entity_lines_.size()) finding.line = entity_lines_[entity.index]; - finding.message = std::move(message); - findings_.push_back(std::move(finding)); + finding.message = base::move(message); + findings_.push_back(base::move(finding)); if (error) ++errors_; } ecs::World& world_; - std::vector entity_lines_; - std::vector findings_; + base::Vector entity_lines_; + base::Vector findings_; u32 errors_ = 0; }; @@ -186,9 +199,9 @@ u32 FloatLanes(edit::PropType type) { // is the only way to tell a number from something that merely starts with one: // strtoll stops at the 'x' of "12x" and hands back 12 without complaint. Base 0, // matching the loader: an 0x-prefixed guid is hex. -bool FullyParsedInteger(const std::string& text) { +bool FullyParsedInteger(const base::String& text) { char* end = nullptr; - std::strtoll(text.c_str(), &end, 0); + ::strtoll(text.c_str(), &end, 0); return end != text.c_str() && *end == '\0'; } @@ -217,7 +230,7 @@ void CheckNumberLiterals(ecs::World& world, const Source& source, Report& report prop->type == edit::PropType::kU64) { if (!FullyParsedInteger(assign.raw)) { report.ErrorAt(entity, assign.line, "unparsed_number", - std::format("{}.{} = {} is not a number; the loader keeps whatever prefix " + rx::StrFormat("{}.{} = {} is not a number; the loader keeps whatever prefix " "parsed and discards the rest", comp->name, prop->name, assign.raw)); } @@ -225,17 +238,17 @@ void CheckNumberLiterals(ecs::World& world, const Source& source, Report& report } const u32 lanes = FloatLanes(prop->type); if (lanes == 0) continue; - std::istringstream in{assign.raw}; - std::string token; - for (u32 lane = 0; lane < lanes && (in >> token); ++lane) { + TokenReader in(assign.raw); + base::String token; + for (u32 lane = 0; lane < lanes && in.Next(&token); ++lane) { char* end = nullptr; // strtof, not strtod: 1e40 is a perfectly good double and an inf f32, and // it is the f32 the loader keeps. - const f32 value = std::strtof(token.c_str(), &end); - if (end != token.c_str() && *end == '\0' && std::isfinite(value)) continue; + const f32 value = ::strtof(token.c_str(), &end); + if (end != token.c_str() && *end == '\0' && ::isfinite(value)) continue; const bool number = end != token.c_str() && *end == '\0'; report.ErrorAt(entity, assign.line, number ? "non_finite" : "unparsed_number", - std::format("{}.{} = {}: '{}' {}; a strict load refuses the file and a " + rx::StrFormat("{}.{} = {}: '{}' {}; a strict load refuses the file and a " "lenient one reads 0 from here on", comp->name, prop->name, assign.raw, token, number ? "is not finite (nan, inf, or past the f32 range)" @@ -249,24 +262,24 @@ void CheckNumberLiterals(ecs::World& world, const Source& source, Report& report // token, zero-padding a short list. False means a lane its own reader refuses, // which CheckNumberLiterals has already condemned by line, so the caller drops // the assignment rather than reporting it twice under a second name. -bool ReadLanes(const std::string& raw, u32 lanes, f32* out) { - std::istringstream in{raw}; - std::string token; +bool ReadLanes(const base::String& raw, u32 lanes, f32* out) { + TokenReader in(raw); + base::String token; for (u32 lane = 0; lane < lanes; ++lane) { - if (!(in >> token)) return true; // the legal short form; the rest stay 0 + if (!in.Next(&token)) return true; // the legal short form; the rest stay 0 char* end = nullptr; - const f32 value = std::strtof(token.c_str(), &end); - if (end == token.c_str() || *end != '\0' || !std::isfinite(value)) return false; + const f32 value = ::strtof(token.c_str(), &end); + if (end == token.c_str() || *end != '\0' || !::isfinite(value)) return false; out[lane] = value; } return true; } -std::string LaneList(const f32* v, u32 lanes) { - std::string out; +base::String LaneList(const f32* v, u32 lanes) { + base::String out; for (u32 lane = 0; lane < lanes; ++lane) { if (lane) out += ' '; - out += std::format("{}", v[lane]); + out += rx::StrFormat("{}", v[lane]); } return out; } @@ -298,21 +311,21 @@ void CheckDiscardedTransform(ecs::World& world, const Source& source, bool grids // What replaced it, and the assignment to reach for instead. A pass that // failed left the field alone, so its finding is the one to read and a // disagreement here means nothing. - std::string by; - std::string instead; + base::String by; + base::String instead; if (rotation) { if (!world.Has(entity)) continue; const SceneRotation& turn = *world.Get(entity); - by = std::format("Rotation.euler = {}", LaneList(turn.euler, 3)); + by = rx::StrFormat("Rotation.euler = {}", LaneList(turn.euler, 3)); instead = "author the angle there and drop the quaternion"; } else if (world.Has(entity)) { if (!anchors_built) continue; const SceneAnchor& anchor = *world.Get(entity); - by = std::format("Anchor.target = \"{}\"", anchor.target); + by = rx::StrFormat("Anchor.target = \"{}\"", anchor.target); instead = "Anchor.offset is the displacement the solve reads"; } else if (const SceneGrid* grid = world.Get(entity); grid && !grid->of.empty()) { if (!grids_built) continue; - by = std::format("Grid.of = \"{}\"", grid->of); + by = rx::StrFormat("Grid.of = \"{}\"", grid->of); instead = "a member's cell comes from the order it is declared in, not from a coordinate"; } else { continue; @@ -324,12 +337,12 @@ void CheckDiscardedTransform(ecs::World& world, const Source& source, bool grids if (!ReadLanes(assign.raw, lanes, authored)) continue; bool differs = false; for (u32 lane = 0; lane < lanes; ++lane) { - differs = differs || std::abs(authored[lane] - solved[lane]) > kDiscardedEpsilon; + differs = differs || ::abs(authored[lane] - solved[lane]) > kDiscardedEpsilon; } if (!differs) continue; report.WarnAt(entity, assign.line, "discarded_transform", - std::format("Transform.{} = {} is discarded: {} replaces it with {}. It is not " + rx::StrFormat("Transform.{} = {} is discarded: {} replaces it with {}. It is not " "added to, so this line moves nothing ({})", assign.prop, assign.raw, by, LaneList(solved, lanes), instead)); } @@ -340,7 +353,7 @@ void CheckTransform(ecs::World& world, ecs::Entity entity, Report& report) { if (!transform) return; if (transform->scale <= 0.0f) { report.Error(entity, "degenerate_scale", - std::format("Transform.scale is {}; the mesh collapses to a point (negative " + rx::StrFormat("Transform.scale is {}; the mesh collapses to a point (negative " "also turns it inside out)", transform->scale)); } // MakeFromQuat is the raw quaternion-to-matrix form with no normalize, so the @@ -356,12 +369,12 @@ void CheckTransform(ecs::World& world, ecs::Entity entity, Report& report) { report.Error(entity, "degenerate_rotation", "Transform.rotation is the zero quaternion; the mesh collapses to a point " "(identity is 0 0 0 1)"); - } else if (std::abs(std::sqrt(length_sq) - 1.0f) > 0.05f) { + } else if (::abs(::sqrt(length_sq) - 1.0f) > 0.05f) { // 5% is far outside anything hand-rounding a unit quaternion produces // (0.7 0 0 0.7 is only 1% short) and well inside a visible mis-scale. report.Warn(entity, "non_unit_rotation", - std::format("Transform.rotation has length {}, so it scales the mesh by that " - "on top of Transform.scale", std::sqrt(length_sq))); + rx::StrFormat("Transform.rotation has length {}, so it scales the mesh by that " + "on top of Transform.scale", ::sqrt(length_sq))); } } @@ -381,7 +394,7 @@ void CheckShape(ecs::World& world, ecs::Entity entity, Report& report) { if ((required & (1u << axis)) == 0) continue; if (shape->size[axis] > 0.0f) continue; report.Error(entity, "degenerate_shape_size", - std::format("Shape.size {} is {} for kind '{}', which needs it positive " + rx::StrFormat("Shape.size {} is {} for kind '{}', which needs it positive " "(--dump-schema documents the axes per kind)", "xyz"[axis], shape->size[axis], shape->kind)); } @@ -399,7 +412,7 @@ void CheckShape(ecs::World& world, ecs::Entity entity, Report& report) { // it was going to render with, so it is not a fragment and still gets both // findings. The blind spot is a file that has nothing at all but a Surface, // which is indistinguishable from a preset by construction. -bool IsFragment(ecs::World& world, const std::vector& entities) { +bool IsFragment(ecs::World& world, const base::Vector& entities) { if (entities.empty()) return false; // an empty file is a broken scene, not a fragment for (ecs::Entity entity : entities) { if (world.Has(entity) || world.Has(entity) || @@ -414,7 +427,7 @@ bool IsFragment(ecs::World& world, const std::vector& entities) { void CheckSurface(ecs::World& world, ecs::Entity entity, bool fragment, Report& report) { const SceneSurface* surface = world.Get(entity); if (!surface) return; - const std::vector maps = SceneSurfaceMaps(*surface); + const base::Vector maps = SceneSurfaceMaps(*surface); bool any_map = false; for (const SceneSurfaceMapRef& map : maps) any_map = any_map || !map.path->empty(); if (!fragment && !world.Has(entity)) { @@ -429,17 +442,17 @@ void CheckSurface(ecs::World& world, ecs::Entity entity, bool fragment, Report& // here is one that binds there. It touches the filesystem, which makes this // (with materialx and Model.path) one of the checks whose answer depends on // where the tool is run from. - const std::string problem = SceneSurfaceMapProblem(*map.path); + const base::String problem = SceneSurfaceMapProblem(*map.path); if (!problem.empty()) { report.Error(entity, "map_not_loaded", - std::format("Surface.{} '{}' {}", map.prop, *map.path, problem)); + rx::StrFormat("Surface.{} '{}' {}", map.prop, *map.path, problem)); } // Both write the base colour, normal and roughness of one material; the // loader refuses the pair rather than picking a winner, so a report that // stayed quiet would leave a failed load with no finding behind it. if (world.Has(entity)) { report.Error(entity, "pattern_and_map", - std::format("Surface.{} is on an entity that also declares a Pattern; both " + rx::StrFormat("Surface.{} is on an entity that also declares a Pattern; both " "bind the same material slots, so the load refuses the pair. " "Author the maps or the Pattern, not both", map.prop)); } @@ -459,14 +472,14 @@ void CheckSurface(ecs::World& world, ecs::Entity entity, bool fragment, Report& // A document resolves its image nodes to files beside itself, and the load // fails on one that is not there. Reported per file, since a set is usually // missing one map rather than all of them. - for (const std::string* file : {&document_maps.base_color, &document_maps.normal, + for (const base::String* file : {&document_maps.base_color, &document_maps.normal, &document_maps.roughness, &document_maps.metallic, &document_maps.occlusion, &document_maps.emissive}) { if (file->empty()) continue; - const std::string problem = SceneSurfaceMapProblem(*file); + const base::String problem = SceneSurfaceMapProblem(*file); if (problem.empty()) continue; report.Error(entity, "materialx_map_not_loaded", - std::format("Surface.materialx '{}' names an image '{}' that {}", + rx::StrFormat("Surface.materialx '{}' names an image '{}' that {}", surface->materialx, *file, problem)); } } @@ -491,7 +504,7 @@ void CheckPattern(ecs::World& world, ecs::Entity entity, bool fragment, Report& for (u32 axis = 0; axis < 2; ++axis) { if (pattern->scale[axis] > 0.0f) continue; report.Error(entity, "degenerate_pattern_scale", - std::format("Pattern.scale {} is {}; the prop is cells across BY cells up and " + rx::StrFormat("Pattern.scale {} is {}; the prop is cells across BY cells up and " "both have to be positive (one number pads with a zero)", axis == 0 ? "u" : "v", pattern->scale[axis])); } @@ -508,7 +521,7 @@ void CheckStretch(ecs::World& world, ecs::Entity entity, bool fragment, Report& for (u32 axis = 0; axis < 3; ++axis) { if (stretch->scale[axis] > 0.0f) continue; report.Error(entity, "degenerate_stretch", - std::format("Stretch.scale {} is {}, which the mesh bake divides the normals " + rx::StrFormat("Stretch.scale {} is {}, which the mesh bake divides the normals " "by; every axis has to be positive (1 1 1 is no stretch)", "xyz"[axis], stretch->scale[axis])); } @@ -526,10 +539,10 @@ void CheckModel(ecs::World& world, ecs::Entity entity, Report& report) { // validates here is one that places geometry there. It imports the file, // which makes this (with Surface.materialx) one of the two checks whose // answer depends on where the tool is run from. - const std::string problem = SceneModelProblem(model->path); + const base::String problem = SceneModelProblem(model->path); if (!problem.empty()) { report.Error(entity, "unresolved_mesh", - std::format("Model.path '{}' {}", model->path, problem)); + rx::StrFormat("Model.path '{}' {}", model->path, problem)); } } @@ -545,12 +558,12 @@ void CheckLight(ecs::World& world, ecs::Entity entity, Report& report) { } if (light->intensity <= 0.0f) { report.Error(entity, "light_cannot_contribute", - std::format("Light.intensity is {}; the light contributes nothing", + rx::StrFormat("Light.intensity is {}; the light contributes nothing", light->intensity)); } if (light->radius <= 0.0f) { report.Error(entity, "light_cannot_contribute", - std::format("Light.radius is {}; the influence cutoff excludes every point", + rx::StrFormat("Light.radius is {}; the influence cutoff excludes every point", light->radius)); } } @@ -574,7 +587,7 @@ void CheckCamera(ecs::World& world, ecs::Entity entity, Report& report) { } if (camera->fov_degrees <= 0.0f || camera->fov_degrees >= 180.0f) { report.Error(entity, "degenerate_camera_fov", - std::format("Camera.fov_degrees is {}; a projection needs it in (0, 180)", + rx::StrFormat("Camera.fov_degrees is {}; a projection needs it in (0, 180)", camera->fov_degrees)); } } @@ -588,7 +601,7 @@ void CheckSun(ecs::World& world, ecs::Entity entity, Report& report) { if (!sun) return; if (sun->intensity <= 0.0f) { report.Warn(entity, "dark_sun", - std::format("Sun.intensity is {}; the scene keeps the sun it declares and gets " + rx::StrFormat("Sun.intensity is {}; the scene keeps the sun it declares and gets " "no light from it, leaving only Sun.ambient and any Lights", sun->intensity)); } @@ -623,12 +636,12 @@ void CheckRenderable(ecs::World& world, asset::AssetDatabase& db, ecs::Entity en // text scene reaches real geometry through Model instead, which imports the // file itself and hands the meshes to the database; a hand-written // Renderable path still resolves to nothing. - const std::optional path = asset::LookupAssetPath(renderable->mesh); + const base::Optional path = asset::LookupAssetPath(renderable->mesh); report.Error(entity, "unresolved_mesh", - std::format("Renderable.mesh '{}' resolves to no uploaded mesh; a .rxscene has " + rx::StrFormat("Renderable.mesh '{}' resolves to no uploaded mesh; a .rxscene has " "no mesh converters, so name the file from a Model (or author a " "Shape) instead", - path ? *path : std::format("hash:0x{:016x}", renderable->mesh.hash))); + path ? *path : rx::StrFormat("hash:0x{:016x}", renderable->mesh.hash))); } void CheckParent(ecs::World& world, ecs::Entity entity, Report& report) { @@ -660,69 +673,69 @@ void CheckParent(ecs::World& world, ecs::Entity entity, Report& report) { } } -void CheckDuplicateGuids(ecs::World& world, const std::vector& entities, +void CheckDuplicateGuids(ecs::World& world, const base::Vector& entities, Report& report) { // A guid is how a scene names an entity across a save/load, so two entities // sharing one make every reference to it resolve to whichever the loader // mapped last, and make a re-save drop the other. - std::unordered_map by_guid; + base::UnorderedMap by_guid; for (ecs::Entity entity : entities) { const scene::Guid* guid = world.Get(entity); if (!guid || guid->value == 0) continue; - auto [it, inserted] = by_guid.emplace(guid->value, entity); + auto [first, inserted] = by_guid.emplace(guid->value, entity); if (inserted) continue; report.Error(entity, "duplicate_guid", - std::format("Guid.value 0x{:016x} is already used by entity index {}; " + rx::StrFormat("Guid.value 0x{:016x} is already used by entity index {}; " "references to it resolve to only one of the two", - guid->value, it->second.index)); + guid->value, first->index)); } } -void PrintJsonString(std::string_view s) { - std::putchar('"'); +void PrintJsonString(base::StringRef s) { + ::putchar('"'); for (char c : s) { - if (c == '"' || c == '\\') std::putchar('\\'); - std::putchar(c); + if (c == '"' || c == '\\') ::putchar('\\'); + ::putchar(c); } - std::putchar('"'); + ::putchar('"'); } -void PrintJson(const std::string& path, const Report& report) { - std::printf("{\n \"scene\": "); +void PrintJson(const base::String& path, const Report& report) { + ::printf("{\n \"scene\": "); PrintJsonString(path); - std::printf(",\n \"errors\": %u,\n \"warnings\": %u,\n \"findings\": [", report.errors(), + ::printf(",\n \"errors\": %u,\n \"warnings\": %u,\n \"findings\": [", report.errors(), report.warnings()); - const std::vector& findings = report.findings(); + const base::Vector& findings = report.findings(); for (size_t i = 0; i < findings.size(); ++i) { const Finding& finding = findings[i]; - std::printf("%s\n {\"severity\": \"%s\", \"check\": ", i ? "," : "", + ::printf("%s\n {\"severity\": \"%s\", \"check\": ", i ? "," : "", finding.error ? "error" : "warning"); PrintJsonString(finding.check); - std::printf(", \"line\": %d, \"entity\": ", finding.line); + ::printf(", \"line\": %d, \"entity\": ", finding.line); PrintJsonString(finding.entity); - std::printf(", \"message\": "); + ::printf(", \"message\": "); PrintJsonString(finding.message); - std::printf("}"); + ::printf("}"); } - std::printf("%s]\n}\n", findings.empty() ? "" : "\n "); + ::printf("%s]\n}\n", findings.empty() ? "" : "\n "); } // Compiler-style, so an editor and a grep both find the offending line. -void PrintHuman(const std::string& path, const Report& report) { +void PrintHuman(const base::String& path, const Report& report) { for (const Finding& finding : report.findings()) { - std::printf("%s:", path.c_str()); - if (finding.line) std::printf("%d:", finding.line); - std::printf(" %s: %s: ", finding.error ? "error" : "warning", finding.check); - if (!finding.entity.empty()) std::printf("'%s': ", finding.entity.c_str()); - std::printf("%s\n", finding.message.c_str()); + ::printf("%s:", path.c_str()); + if (finding.line) ::printf("%d:", finding.line); + ::printf(" %s: %s: ", finding.error ? "error" : "warning", finding.check); + if (!finding.entity.empty()) ::printf("'%s': ", finding.entity.c_str()); + ::printf("%s\n", finding.message.c_str()); } - std::printf("%s: %u error(s), %u warning(s)\n", path.c_str(), report.errors(), + ::printf("%s: %u error(s), %u warning(s)\n", path.c_str(), report.errors(), report.warnings()); } } // namespace -bool ValidateSceneFile(const std::string& path, bool json) { +bool ValidateSceneFile(const base::String& path, bool json) { // RX_INFO writes to stdout, which the json report has to have to itself. if (json) SetLogLevel(LogLevel::kWarn); @@ -738,7 +751,7 @@ bool ValidateSceneFile(const std::string& path, bool json) { // nothing, though, and a report that stopped there would cost the author the // other twenty findings, so fall back to a lenient load of the same file: the // gate says no, and this still explains the whole document. - std::string error; + base::String error; const bool strict_loaded = edit::LoadScene(world, db, path, &error, /*strict=*/true); if (!strict_loaded) edit::LoadScene(world, db, path, nullptr, /*strict=*/false); @@ -761,7 +774,7 @@ bool ValidateSceneFile(const std::string& path, bool json) { // been resolved into yet. BuildSceneRotations(world); - const std::vector entities = AllEntities(world); + const base::Vector entities = AllEntities(world); // Decided once, after the two expansion passes: an instance that took its // Shape from a prefab is not a fragment, and the file it took it from is. const bool fragment = IsFragment(world, entities); @@ -821,7 +834,7 @@ bool ValidateSceneFile(const std::string& path, bool json) { // in archetype order, not file order, so which one wins is not something // the file decides. report.FileWarn("multiple_cameras", - std::format("{} Camera components; which one the viewer takes is " + rx::StrFormat("{} Camera components; which one the viewer takes is " "archetype order, not file order", cameras)); } // Same walk, same ambiguity, and worse to debug: two Suns render as one of @@ -829,12 +842,12 @@ bool ValidateSceneFile(const std::string& path, bool json) { // authored light that did nothing. if (suns > 1) { report.FileWarn("multiple_suns", - std::format("{} Sun components; which one lights the scene is archetype " + rx::StrFormat("{} Sun components; which one lights the scene is archetype " "order, not file order", suns)); } if (atmospheres > 1) { report.FileWarn("multiple_atmospheres", - std::format("{} Atmosphere components; which one the viewer takes is " + rx::StrFormat("{} Atmosphere components; which one the viewer takes is " "archetype order, not file order", atmospheres)); } diff --git a/runtime/scene_validate.h b/runtime/scene_validate.h index 75b9784c..2b6833bf 100644 --- a/runtime/scene_validate.h +++ b/runtime/scene_validate.h @@ -1,7 +1,8 @@ #ifndef RX_RUNTIME_SCENE_VALIDATE_H_ #define RX_RUNTIME_SCENE_VALIDATE_H_ -#include +#include "base/strings/xstring.h" + // Structural validation of a .rxscene with no device and no window: the class // of mistake that loads cleanly and then renders nothing, or renders something @@ -17,7 +18,7 @@ namespace rx { // Loads `path` and reports every structural problem it finds, as a // compiler-style human report or (`json`) as one object on stdout. False when // any error-level finding fired; warnings alone still return true. -bool ValidateSceneFile(const std::string& path, bool json); +bool ValidateSceneFile(const base::String& path, bool json); } // namespace rx diff --git a/runtime/showcase_camera.cc b/runtime/showcase_camera.cc index e05369e7..c1149e7d 100644 --- a/runtime/showcase_camera.cc +++ b/runtime/showcase_camera.cc @@ -1,14 +1,14 @@ +#include "base/memory/move.h" +#include "base/strings/xstring.h" +#include "core/scalar.h" #include "showcase_camera.h" -#include -#include - namespace rx { void ShowcaseCamera::Add(Waypoint wp) { - total_ = keys_.empty() ? 0.0f : total_ + std::max(0.0f, wp.travel); + total_ = keys_.empty() ? 0.0f : total_ + rx::Max(0.0f, wp.travel); arrive_.push_back(total_); - keys_.push_back(std::move(wp)); + keys_.push_back(base::move(wp)); } namespace { @@ -25,7 +25,7 @@ Vec3 CatmullRom(const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec3& p3, ShowcasePose ShowcaseCamera::Sample(f32 t) const { if (keys_.empty()) return {}; if (keys_.size() == 1) return {keys_[0].eye, keys_[0].look}; - t = std::clamp(t, 0.0f, total_); + t = rx::Clamp(t, 0.0f, total_); // Segment [a, b] of the timeline that contains t. size_t b = 1; @@ -43,7 +43,7 @@ ShowcasePose ShowcaseCamera::Sample(f32 t) const { return pose; } -int ShowcaseCamera::CaptureCrossed(f32 prev_t, f32 t, std::string* label) const { +int ShowcaseCamera::CaptureCrossed(f32 prev_t, f32 t, base::String* label) const { for (size_t i = 0; i < keys_.size(); ++i) { if (!keys_[i].capture) continue; if (arrive_[i] > prev_t && arrive_[i] <= t) { @@ -55,3 +55,4 @@ int ShowcaseCamera::CaptureCrossed(f32 prev_t, f32 t, std::string* label) const } } // namespace rx + diff --git a/runtime/showcase_camera.h b/runtime/showcase_camera.h index 7893cc1d..49cfb2e5 100644 --- a/runtime/showcase_camera.h +++ b/runtime/showcase_camera.h @@ -1,10 +1,10 @@ #ifndef RX_RUNTIME_SHOWCASE_CAMERA_H_ #define RX_RUNTIME_SHOWCASE_CAMERA_H_ -#include #include +#include "base/strings/xstring.h" #include "core/math.h" namespace rx { @@ -26,7 +26,7 @@ class ShowcaseCamera { Vec3 look{}; // point the camera aims at f32 travel = 6; // seconds to glide here from the previous waypoint bool capture = false; - std::string label; + base::String label; }; void Add(Waypoint wp); @@ -39,7 +39,7 @@ class ShowcaseCamera { // If a capture waypoint's arrival falls in (prev_t, t], returns its index and // writes its label; otherwise returns -1. - int CaptureCrossed(f32 prev_t, f32 t, std::string* label) const; + int CaptureCrossed(f32 prev_t, f32 t, base::String* label) const; private: base::Vector keys_; diff --git a/runtime/viewer.cc b/runtime/viewer.cc index 3cf0dd9f..eb9a2528 100644 --- a/runtime/viewer.cc +++ b/runtime/viewer.cc @@ -1,11 +1,10 @@ #include "viewer.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +// std::sort stays for the scene light order (see there). #include @@ -21,8 +20,19 @@ #include #include "scene/components.h" +#include "base/algorithm.h" +#include "base/containers/pair.h" +#include "base/containers/span.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/scalar.h" #include "demo_scenes.h" #include "scene_authoring.h" +#include "core/text_reader.h" +#include "core/sort.h" // Viewer lifecycle and per-frame policy: the front-door content dispatch // (glTF scene or builtin demo), the day/night sun, the debug overlay and the @@ -96,7 +106,7 @@ Viewer::Viewer(const EngineConfig& config) : config_(config) {} Viewer::~Viewer() { if (cam_record_) { - std::fclose(cam_record_); + ::fclose(cam_record_); cam_record_ = nullptr; } } @@ -141,7 +151,7 @@ bool Viewer::OnInitialize(app::Services& services) { ctx_.physics_entities = physics_entities_; ctx_.hair_bindings = services.hair_bindings; ctx_.actions = actions_; - demos_ = std::make_unique(ctx_); + demos_ = base::MakeUnique(ctx_); if (physics_->initialized()) CreatePhysicsCubeAsset(); @@ -182,9 +192,9 @@ bool Viewer::OnInitialize(app::Services& services) { // the flag exists to be typed by hand, and a run that quietly used the authored // camera would be read as "the change did nothing". void Viewer::ApplyCameraOverride() { - auto triple = [](const std::string& text, Vec3* out) { - std::istringstream in(text); - return static_cast(in >> out->x >> out->y >> out->z); + auto triple = [](const base::String& text, Vec3* out) { + TokenReader in(text); + return in.Next(&out->x) && in.Next(&out->y) && in.Next(&out->z); }; Vec3 eye = camera_.position(); Vec3 target = camera_.target(); @@ -224,12 +234,12 @@ void Viewer::StartAuthoringEndpoint() { script_ctx_.log_sink = [](void*, script::ScriptStringView message) { RX_INFO("authoring: {}", message.view()); }; - bridge_ = std::make_unique(commands_, script_ctx_); + bridge_ = base::MakeUnique(commands_, script_ctx_); - std::string error; + base::String error; if (!authoring_endpoint_.Start(config_.authoring_socket, &error)) { RX_ERROR("authoring endpoint: {}", error); - bridge_.reset(); + bridge_.Reset(); return; } RX_INFO("authoring endpoint listening on {} ({} command(s))", config_.authoring_socket, @@ -276,7 +286,7 @@ bool Viewer::LoadRxScene() { // Resolves Renderable asset paths (a shape-authored scene has none) and holds // the textures BuildSceneShapes synthesizes for the scene's patterns. asset::AssetDatabase db(*ctx_.vfs); - std::string error; + base::String error; if (!edit::LoadScene(*world_, db, config_.scene_path, &error, /*strict=*/true)) { RX_ERROR("rxscene: {}", error); return false; @@ -379,7 +389,7 @@ bool Viewer::LoadSceneFile() { for (const asset::Mesh& mesh : scene.meshes) renderer_->UploadMesh(mesh); } - base::Vector> instance_entities; + base::Vector> instance_entities; for (const asset::ImportedScene::Instance& instance : scene.instances) { const asset::Mesh& mesh = scene.meshes[instance.mesh_index]; // Morphed instances stay out of the ECS gather; EmitMorphedInstances @@ -397,17 +407,17 @@ bool Viewer::LoadSceneFile() { if (const char* spec = MorphWeights.get()) { // "name=w,name=w": resolve each name against this mesh's targets. morphed.pinned = true; - std::string s(spec); + base::String s(spec); size_t pos = 0; while (pos < s.size()) { size_t comma = s.find(',', pos); - if (comma == std::string::npos) comma = s.size(); - std::string entry = s.substr(pos, comma - pos); + if (comma == base::String::npos) comma = s.size(); + base::String entry = s.substr(pos, comma - pos); pos = comma + 1; size_t eq = entry.find('='); - if (eq == std::string::npos) continue; - std::string name = entry.substr(0, eq); - f32 weight = std::strtof(entry.c_str() + eq + 1, nullptr); + if (eq == base::String::npos) continue; + base::String name = entry.substr(0, eq); + f32 weight = ::strtof(entry.c_str() + eq + 1, nullptr); i32 index = mesh.FindMorphTarget(asset::MakeAssetId(name).hash); if (index >= 0) morphed.weights[static_cast(index)] = weight; } @@ -433,7 +443,7 @@ bool Viewer::LoadSceneFile() { expression_demo_ = true; } } - morphed_.push_back(std::move(morphed)); + morphed_.push_back(base::move(morphed)); continue; } ecs::Entity entity = world_->Create(); @@ -441,7 +451,7 @@ bool Viewer::LoadSceneFile() { transform.position[0] = instance.position.x; transform.position[1] = instance.position.y; transform.position[2] = instance.position.z; - std::memcpy(transform.rotation, instance.rotation, sizeof(transform.rotation)); + base::MemCopy(transform.rotation, instance.rotation, sizeof(transform.rotation)); transform.scale = instance.scale; world_->Add(entity, transform); world_->Add(entity, scene::Renderable{scene.meshes[instance.mesh_index].id}); @@ -471,7 +481,7 @@ bool Viewer::LoadSceneFile() { // tile the receiver is biased onto - a Genesis-style body lays its zones out // across u in [0,7), and only the anchored zone can take the decal. void Viewer::StampTattoos(const asset::ImportedScene& scene, - std::span> instances) { + base::Span> instances) { const char* spec = Tattoo.get(); if (!spec || config_.headless || instances.empty()) return; @@ -505,22 +515,22 @@ void Viewer::StampTattoos(const asset::ImportedScene& scene, Vec3 lo{vertices[0].position[0], vertices[0].position[1], vertices[0].position[2]}; Vec3 hi = lo; for (const asset::Vertex& v : vertices) { - lo = {std::min(lo.x, v.position[0]), std::min(lo.y, v.position[1]), - std::min(lo.z, v.position[2])}; - hi = {std::max(hi.x, v.position[0]), std::max(hi.y, v.position[1]), - std::max(hi.z, v.position[2])}; + lo = {rx::Min(lo.x, v.position[0]), rx::Min(lo.y, v.position[1]), + rx::Min(lo.z, v.position[2])}; + hi = {rx::Max(hi.x, v.position[0]), rx::Max(hi.y, v.position[1]), + rx::Max(hi.z, v.position[2])}; } if (const char* atlas_path = TattooAtlas.get()) { - std::FILE* file = std::fopen(atlas_path, "rb"); + FILE* file = ::fopen(atlas_path, "rb"); if (!file) { RX_WARN("RX_TATTOO_ATLAS: cannot open {}", atlas_path); } else { - std::fseek(file, 0, SEEK_END); - const long bytes = std::ftell(file); - std::fseek(file, 0, SEEK_SET); + ::fseek(file, 0, SEEK_END); + const long bytes = ::ftell(file); + ::fseek(file, 0, SEEK_SET); const u64 texels = bytes > 0 ? static_cast(bytes) / 4 : 0; - const u64 side = static_cast(std::lround(std::sqrt(static_cast(texels)))); + const u64 side = static_cast(::lround(::sqrt(static_cast(texels)))); if (bytes <= 0 || side * side * 4 != static_cast(bytes)) { RX_WARN("RX_TATTOO_ATLAS: {} is {} bytes, not a square rgba8 image", atlas_path, bytes); } else { @@ -531,7 +541,7 @@ void Viewer::StampTattoos(const asset::ImportedScene& scene, ink.height = static_cast(side); ink.is_srgb = true; ink.data.resize(static_cast(bytes)); - if (std::fread(ink.data.data(), 1, static_cast(bytes), file) == + if (::fread(ink.data.data(), 1, static_cast(bytes), file) == static_cast(bytes)) { renderer_->UploadTexture(ink); renderer_->SetDecalAtlas(ink.id); @@ -540,7 +550,7 @@ void Viewer::StampTattoos(const asset::ImportedScene& scene, atlas_path); } } - std::fclose(file); + ::fclose(file); } } @@ -555,12 +565,12 @@ void Viewer::StampTattoos(const asset::ImportedScene& scene, bool tile_chosen = false; i32 tile_u = 0, tile_v = 0; u32 stamped = 0; - std::string s(spec); + base::String s(spec); size_t pos = 0; while (pos < s.size()) { size_t end = s.find(';', pos); - if (end == std::string::npos) end = s.size(); - const std::string entry = s.substr(pos, end - pos); + if (end == base::String::npos) end = s.size(); + const base::String entry = s.substr(pos, end - pos); pos = end + 1; f32 f[4] = {0.5f, 0.5f, 0.5f, 0.08f}; u32 parsed = 0; @@ -570,7 +580,7 @@ void Viewer::StampTattoos(const asset::ImportedScene& scene, // strtof returns 0 on failure, so only commit the value once the parse is // known good: a trailing space would otherwise zero the size and build a // degenerate projector (all-zero rows, NaN facing test). - const f32 value = std::strtof(cursor, &next); + const f32 value = ::strtof(cursor, &next); if (next == cursor) break; f[parsed] = value; ++parsed; @@ -596,8 +606,8 @@ void Viewer::StampTattoos(const asset::ImportedScene& scene, // One draw carries one layer tile, so every tattoo has to live in the zone // the first anchor picked; a later anchor on another zone would bake // outside the tile and shade nothing. - const i32 anchor_tile_u = static_cast(std::floor(nearest->uv[0])); - const i32 anchor_tile_v = static_cast(std::floor(nearest->uv[1])); + const i32 anchor_tile_u = static_cast(::floor(nearest->uv[0])); + const i32 anchor_tile_v = static_cast(::floor(nearest->uv[1])); if (!tile_chosen) { tile_u = anchor_tile_u; tile_v = anchor_tile_v; @@ -615,7 +625,7 @@ void Viewer::StampTattoos(const asset::ImportedScene& scene, const Vec3 normal = Normalize(TransformDir( to_world, {nearest->normal[0], nearest->normal[1], nearest->normal[2]})); Vec3 up{0, 1, 0}; - if (std::abs(Dot(up, normal)) > 0.9f) up = {0, 0, 1}; + if (::abs(Dot(up, normal)) > 0.9f) up = {0, 0, 1}; render::DecalStamp tattoo; tattoo.receiver = receiver; const f32 world_size = f[3] * placement_scale; @@ -668,16 +678,16 @@ void Viewer::ApplySceneLighting(const asset::ImportedScene& scene) { u32 punctual = 0, dropped = 0, distant_count = 0, dome_count = 0; for (const asset::ImportedScene::Light& light : scene.lights) { - const f32 gain = light.intensity * std::exp2(light.exposure); + const f32 gain = light.intensity * ::exp2(light.exposure); if (light.kind == Kind::kDistant) { // Brightest distant light wins: a rig may carry a fill as well as a key. ++distant_count; - if (!sun || gain > sun->intensity * std::exp2(sun->exposure)) sun = &light; + if (!sun || gain > sun->intensity * ::exp2(sun->exposure)) sun = &light; continue; } if (light.kind == Kind::kDome) { ++dome_count; - if (!dome || gain > dome->intensity * std::exp2(dome->exposure)) dome = &light; + if (!dome || gain > dome->intensity * ::exp2(dome->exposure)) dome = &light; continue; } render::PointLight pl; @@ -699,7 +709,7 @@ void Viewer::ApplySceneLighting(const asset::ImportedScene& scene) { } // Influence radius from an inverse-square falloff down to a ~1/255 cutoff, // clamped so a bright practical does not light the entire stage. - pl.pos_radius[3] = std::min(30.0f, std::max(1.0f, std::sqrt(intensity * 255.0f))); + pl.pos_radius[3] = rx::Min(30.0f, rx::Max(1.0f, ::sqrt(intensity * 255.0f))); pl.color_intensity[0] = light.color[0]; pl.color_intensity[1] = light.color[1]; pl.color_intensity[2] = light.color[2]; @@ -717,7 +727,7 @@ void Viewer::ApplySceneLighting(const asset::ImportedScene& scene) { case Kind::kDisk: case Kind::kCylinder: pl.direction_type[3] = 2.0f; // sphere area light - pl.params[0] = std::max(0.01f, light.radius); + pl.params[0] = rx::Max(0.01f, light.radius); break; default: pl.direction_type[3] = 0.0f; @@ -727,9 +737,9 @@ void Viewer::ApplySceneLighting(const asset::ImportedScene& scene) { if (light.cone_angle < 89.9f && light.kind != Kind::kRect) { pl.direction_type[3] = 1.0f; const f32 outer = light.cone_angle * 3.14159265358979f / 180.0f; - const f32 inner = outer * (1.0f - std::clamp(light.cone_softness, 0.0f, 1.0f)); - pl.params[0] = std::cos(inner); - pl.params[1] = std::cos(outer); + const f32 inner = outer * (1.0f - rx::Clamp(light.cone_softness, 0.0f, 1.0f)); + pl.params[0] = ::cos(inner); + pl.params[1] = ::cos(outer); } scene_lights_.push_back(pl); ++punctual; @@ -738,10 +748,12 @@ void Viewer::ApplySceneLighting(const asset::ImportedScene& scene) { // The renderer uploads only the first kMaxFrameLights and clusters only the // first few per cluster, both silently. Ordering brightest-first means what // survives a truncation is what matters most, and the count is reported. - std::sort(scene_lights_.begin(), scene_lights_.end(), - [](const render::PointLight &a, const render::PointLight &b) { - return a.color_intensity[3] > b.color_intensity[3]; - }); + // Stable: equal intensities keep import order, which decides which lights + // survive truncation and the per-cluster light order. + rx::StableSort(scene_lights_.data(), scene_lights_.data() + scene_lights_.size(), + [](const render::PointLight &a, const render::PointLight &b) { + return a.color_intensity[3] > b.color_intensity[3]; + }); if (scene_lights_.size() > 256) { RX_WARN("usd lighting: {} punctual lights imported but the renderer uploads " "256 per frame; the dimmest {} will not light the scene", @@ -752,7 +764,7 @@ void Viewer::ApplySceneLighting(const asset::ImportedScene& scene) { s.sun_direction = sun->direction; s.sun_color = {sun->color[0], sun->color[1], sun->color[2]}; s.sun_intensity = - sun->intensity * std::exp2(sun->exposure) * UsdSunScale.get(); + sun->intensity * ::exp2(sun->exposure) * UsdSunScale.get(); ctx_.scene_owns_sun = true; drive_sun_from_clock_ = false; } @@ -767,7 +779,7 @@ void Viewer::ApplySceneLighting(const asset::ImportedScene& scene) { if (f32* pixels = stbi_loadf(dome->texture.c_str(), &w, &h, &channels, 4)) { const Vec3 tint{dome->color[0], dome->color[1], dome->color[2]}; const f32 gain = - dome->intensity * std::exp2(dome->exposure) * UsdDomeScale.get(); + dome->intensity * ::exp2(dome->exposure) * UsdDomeScale.get(); dome_ibl = renderer_->SetEnvironmentMap(pixels, static_cast(w), static_cast(h), tint, gain, UsdDomeRotation.get()); @@ -815,7 +827,7 @@ void Viewer::ApplySceneLighting(const asset::ImportedScene& scene) { s.clouds = false; s.aerial_perspective = 0.0f; const f32 dome_gain = - dome ? dome->intensity * std::exp2(dome->exposure) * UsdDomeScale.get() + dome ? dome->intensity * ::exp2(dome->exposure) * UsdDomeScale.get() : 0.05f; // The dome's tint multiplies its environment map, so the fill colour is // both together - the tint alone is not the colour of the sky. @@ -906,8 +918,8 @@ void Viewer::ApplySceneCamera(const asset::ImportedScene& scene) { camera.rotation[2], camera.rotation[3]); const Vec3 forward = TransformDir(basis, {0.0f, 0.0f, -1.0f}); camera_.set_position(camera.position); - camera_.set_yaw_pitch(std::atan2(forward.x, -forward.z), - std::asin(std::clamp(forward.y, -1.0f, 1.0f))); + camera_.set_yaw_pitch(::atan2(forward.x, -forward.z), + ::asin(rx::Clamp(forward.y, -1.0f, 1.0f))); camera_.speed = 4.0f; // Framing is the lens as much as the pose: a 18mm wide-angle stage camera // shows a different scene through the engine's default 60 degrees. @@ -922,7 +934,7 @@ void Viewer::DriveSunFromClock() { // frame for sub-degree motion. if (!drive_sun_from_clock_ || ctx_.scene_owns_sun) return; const f32 hour = clock_->hour(); - if (last_sky_hour_ >= -100.0f && std::abs(hour - last_sky_hour_) < 0.02f) return; + if (last_sky_hour_ >= -100.0f && ::abs(hour - last_sky_hour_) < 0.02f) return; last_sky_hour_ = hour; const SkyLighting sky = ComputeSkyLighting(hour); auto& s = renderer_->settings(); @@ -1011,9 +1023,9 @@ void Viewer::EmitMorphedInstances(f32 frame_delta, render::FrameView& view) { // Cycle the stock poses; the life layer keeps blinking through the holds. expression_hold_ -= frame_delta; if (expression_hold_ <= 0) { - static constexpr std::string_view kCycle[] = {"neutral", "smile", "angry", "surprised", + static const base::StringRef kCycle[] = {"neutral", "smile", "angry", "surprised", "smirk", "pucker", "eyes_closed"}; - expression_.SetExpression(kCycle[expression_pose_ % std::size(kCycle)]); + expression_.SetExpression(kCycle[expression_pose_ % (sizeof(kCycle) / sizeof(kCycle[0]))]); ++expression_pose_; expression_hold_ = 3.0f; } @@ -1024,11 +1036,11 @@ void Viewer::EmitMorphedInstances(f32 frame_delta, render::FrameView& view) { // RX_MORPH_WEIGHTS: weights were fixed at load; skip track/sweep. } else if (!instance.animation.times.empty()) { f32 time = instance.animation.duration > 0 - ? std::fmod(morph_time_, instance.animation.duration) + ? ::fmod(morph_time_, instance.animation.duration) : 0.0f; anim::SampleMorphWeights(instance.animation, time, &instance.weights); } else if (!instance.expression_map.empty()) { - std::fill(instance.weights.begin(), instance.weights.end(), 0.0f); + base::Fill(instance.weights.begin(), instance.weights.end(), 0.0f); for (u32 c = 0; c < instance.expression_map.size(); ++c) { i32 index = instance.expression_map[c]; if (index >= 0) instance.weights[static_cast(index)] = expression_.channel_weight(c); @@ -1036,11 +1048,11 @@ void Viewer::EmitMorphedInstances(f32 frame_delta, render::FrameView& view) { } else if (!instance.weights.empty()) { // No imported track (e.g. an ARKit-style blendshape face): sweep one // target at a time, eased in and out, so the expressions cycle live. - std::fill(instance.weights.begin(), instance.weights.end(), 0.0f); + base::Fill(instance.weights.begin(), instance.weights.end(), 0.0f); const f32 period = 1.2f; // seconds per target u32 index = static_cast(morph_time_ / period) % instance.weights.size(); - f32 phase = std::fmod(morph_time_, period) / period; - instance.weights[index] = std::sin(phase * 3.14159265f); + f32 phase = ::fmod(morph_time_, period) / period; + instance.weights[index] = ::sin(phase * 3.14159265f); } render::DrawItem draw; draw.mesh = instance.mesh; @@ -1077,7 +1089,7 @@ void Viewer::OnFrameEnd() { // written by the next RenderFrame), then quit once the last one landed. if (ui_shot_frames <= ui_shot_target) { char path[512]; - std::snprintf(path, sizeof(path), "%s_%04d.png", shot, ui_shot_frames); + ::snprintf(path, sizeof(path), "%s_%04d.png", shot, ui_shot_frames); renderer_->CaptureScreenshot(path); } else { host_->RequestQuit(); @@ -1102,7 +1114,7 @@ void Viewer::OnShutdown() { // the socket file goes away at the moment the engine stops serving it rather // than whenever the viewer is destroyed. authoring_endpoint_.Stop(); - bridge_.reset(); + bridge_.Reset(); // Release demo GPU resources (scenehook raw pipelines) before the host tears // the renderer's device down. if (tattoo_receiver_ != 0 && renderer_) { diff --git a/runtime/viewer.h b/runtime/viewer.h index 8e07fea4..c2fa358e 100644 --- a/runtime/viewer.h +++ b/runtime/viewer.h @@ -1,11 +1,7 @@ #ifndef RX_RUNTIME_VIEWER_H_ #define RX_RUNTIME_VIEWER_H_ -#include -#include -#include -#include -#include +#include #include @@ -22,6 +18,10 @@ #include "script/script_arena.h" #include "script/script_symbols.h" +#include "base/containers/pair.h" +#include "base/containers/span.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" #include "debug_ui.h" #include "demo_world.h" #include "engine_context.h" @@ -57,7 +57,7 @@ class Viewer : public app::Application { bool LoadRxScene(); // RX_TATTOO capture hook: bakes decal layers onto the heaviest imported mesh. void StampTattoos(const asset::ImportedScene& scene, - std::span> instances); + base::Span> instances); // Held for the session so OnShutdown can hand it back; DecalBaker reuses // handles with no generation counter, so a leaked one would be handed to the // next acquirer complete with this scene's baked decals. @@ -156,7 +156,7 @@ class Viewer : public app::Application { bool cam_init_ = false; bool cam_orbit_ = false; f32 cam_time_ = 0; - std::FILE* cam_record_ = nullptr; + FILE* cam_record_ = nullptr; base::Vector cam_replay_; // Cinematic showcase (RX_SHOWCASE): a smooth drone flythrough over the @@ -166,7 +166,7 @@ class Viewer : public app::Application { bool cam_showcase_ = false; bool showcase_done_ = false; bool showcase_quit_ = false; // RX_SHOWCASE_QUIT: exit when the pass ends - std::string showcase_shot_dir_; + base::String showcase_shot_dir_; f32 showcase_dt_min_ = 1e9f; f32 showcase_dt_max_ = 0; f32 showcase_bench_time_ = 0; // summed dt of benchmarked frames (excludes load hitches) @@ -180,12 +180,12 @@ class Viewer : public app::Application { script::ScriptSymbols symbols_; script::ScriptArena script_scratch_; script::HandlerContext script_ctx_; - std::unique_ptr bridge_; + base::UniquePointer bridge_; authoring::CommandEndpoint authoring_endpoint_; // Shared service bundle handed to the demo scenes and the debug overlay. EngineContext ctx_; - std::unique_ptr demos_; + base::UniquePointer demos_; }; } // namespace rx diff --git a/test/adaptive_water_test.cc b/test/adaptive_water_test.cc index 51586467..4980b025 100644 --- a/test/adaptive_water_test.cc +++ b/test/adaptive_water_test.cc @@ -1,6 +1,6 @@ #include "render/geometry/adaptive_water.h" -#include +#include using rx::render::AdaptiveWaterMesh; @@ -9,7 +9,7 @@ int main() { static_assert(AdaptiveWaterMesh::SanitizeBudget(0) == 2); static_assert(AdaptiveWaterMesh::SanitizeBudget(8192) == 8192); static_assert(AdaptiveWaterMesh::SanitizeBudget(~rx::u32{0}) == AdaptiveWaterMesh::kMaxTriangles); - std::printf("adaptive_water_test: PASS (capacity %u triangles)\n", + ::printf("adaptive_water_test: PASS (capacity %u triangles)\n", AdaptiveWaterMesh::kMaxTriangles); return 0; } diff --git a/test/aircraft_test.cc b/test/aircraft_test.cc index ee621463..70b3052a 100644 --- a/test/aircraft_test.cc +++ b/test/aircraft_test.cc @@ -6,11 +6,12 @@ // including deliberate post-stall tumbling. Each scenario builds its own flat // height-field runway at y = 0. -#include -#include -#include +#include +#include +#include "base/containers/vector.h" #include "core/math.h" +#include "core/scalar.h" #include "physics/aircraft.h" #include "physics/physics_world.h" @@ -27,27 +28,27 @@ namespace { constexpr f32 kDt = 1.0f / 60.0f; int Fail(const char* what) { - std::fprintf(stderr, "aircraft_test FAIL: %s\n", what); + ::fprintf(stderr, "aircraft_test FAIL: %s\n", what); return 1; } void AddRunway(PhysicsWorld& world) { constexpr u32 kSamples = 64; constexpr f32 kSize = 3000.0f; // long enough for a full takeoff roll - std::vector heights(static_cast(kSamples) * kSamples, 0.0f); + base::Vector heights(static_cast(kSamples) * kSamples, 0.0f); world.AddHeightField(Vec3{-kSize * 0.5f, 0.0f, -kSize * 0.5f}, heights.data(), kSamples, kSize, SurfaceType::kConcrete); } bool IsFinite(const Vec3& v) { - return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z); + return ::isfinite(v.x) && ::isfinite(v.y) && ::isfinite(v.z); } bool StateFinite(const AircraftState& s) { - return std::isfinite(s.airspeed_mps) && std::isfinite(s.vertical_speed_mps) && - std::isfinite(s.alpha_deg) && std::isfinite(s.beta_deg) && std::isfinite(s.rpm) && - std::isfinite(s.engine_load) && IsFinite(s.position) && std::isfinite(s.rotation.x) && - std::isfinite(s.rotation.y) && std::isfinite(s.rotation.z) && std::isfinite(s.rotation.w); + return ::isfinite(s.airspeed_mps) && ::isfinite(s.vertical_speed_mps) && + ::isfinite(s.alpha_deg) && ::isfinite(s.beta_deg) && ::isfinite(s.rpm) && + ::isfinite(s.engine_load) && IsFinite(s.position) && ::isfinite(s.rotation.x) && + ::isfinite(s.rotation.y) && ::isfinite(s.rotation.z) && ::isfinite(s.rotation.w); } // Spawns a plane a little high and lets the suspension settle under zero input. @@ -64,7 +65,7 @@ Aircraft* SpawnSettled(PhysicsWorld& world, const AircraftDesc& desc, Aircraft* // Heading (yaw) of the plane in radians about +Y, from its forward axis. f32 Heading(const Aircraft& a) { Vec3 fwd = Rotate(a.state().rotation, Vec3{0, 0, 1}); - return std::atan2(fwd.x, fwd.z); + return ::atan2(fwd.x, fwd.z); } // Wrap a heading delta into (-pi, pi] so a signed turn reads correctly even if @@ -80,7 +81,7 @@ f32 WrapPi(f32 a) { int main() { PhysicsWorld probe; if (!probe.Initialize()) { - std::fprintf(stderr, "aircraft_test: physics unavailable, skipping\n"); + ::fprintf(stderr, "aircraft_test: physics unavailable, skipping\n"); return 0; } @@ -99,12 +100,12 @@ int main() { world.Update(kDt); } const AircraftState& s = a->state(); - std::fprintf(stderr, "(a) rest y=%.3f vy=%.3f speed=%.3f on_ground=%d comp=%.2f/%.2f/%.2f\n", + ::fprintf(stderr, "(a) rest y=%.3f vy=%.3f speed=%.3f on_ground=%d comp=%.2f/%.2f/%.2f\n", s.position.y, s.vertical_speed_mps, s.airspeed_mps, s.on_ground, s.gear_compression[0], s.gear_compression[1], s.gear_compression[2]); if (!s.on_ground) return Fail("(a) not resting on the gear"); if (s.position.y < 0.5f) return Fail("(a) sank through the runway"); - if (std::fabs(s.vertical_speed_mps) > 0.2f || s.airspeed_mps > 0.3f) + if (::fabs(s.vertical_speed_mps) > 0.2f || s.airspeed_mps > 0.3f) return Fail("(a) not settled/stationary"); a->~Aircraft(); } @@ -131,15 +132,15 @@ int main() { const AircraftState& s = a->state(); if (!StateFinite(s)) return Fail("(b) NaN during takeoff"); const f32 alt = s.position.y - ground_y; - max_alt = std::max(max_alt, alt); + max_alt = rx::Max(max_alt, alt); if (!airborne && !s.on_ground && alt > 0.5f) { airborne = true; liftoff_speed = s.airspeed_mps; normal_liftoff_dist = s.position.z; } - if (airborne) peak_climb = std::max(peak_climb, s.vertical_speed_mps); + if (airborne) peak_climb = rx::Max(peak_climb, s.vertical_speed_mps); } - std::fprintf(stderr, + ::fprintf(stderr, "(b) Vr=%.1f Vlof=%.1f liftoff_dist=%.0fm max_alt=%.1fm peak_climb=%.2f m/s\n", rotate_speed, liftoff_speed, normal_liftoff_dist, max_alt, peak_climb); if (max_alt < 8.0f) return Fail("(b) did not lift off / gain altitude"); @@ -176,7 +177,7 @@ int main() { const AircraftState& s = a->state(); if (!StateFinite(s)) return false; if (*roll_dist < 0 && s.airspeed_mps >= ref_speed) *roll_dist = s.position.z - start_z; - *max_alt = std::max(*max_alt, s.position.y - ground_y); + *max_alt = rx::Max(*max_alt, s.position.y - ground_y); if (i > 60 * 43) *late_vs = s.vertical_speed_mps; } a->~Aircraft(); @@ -188,7 +189,7 @@ int main() { f32 heavy_roll, heavy_alt, heavy_vs; takeoff_run(220.0f, kRef, &light_roll, &light_alt, &light_vs); const bool over = takeoff_run(470.0f, kRef, &heavy_roll, &heavy_alt, &heavy_vs); - std::fprintf(stderr, + ::fprintf(stderr, "(c) light: roll_to_%.0f=%.0fm alt=%.1f vs=%.2f | heavy(over_mtom=%d): " "roll=%.0fm alt=%.1f vs=%.2f\n", kRef, light_roll, light_alt, light_vs, over, heavy_roll, heavy_alt, heavy_vs); @@ -220,10 +221,10 @@ int main() { const AircraftState& s = a->state(); if (!StateFinite(s)) return Fail("(d) NaN during stall"); if (s.stalled_left || s.stalled_right) saw_stall = true; - min_vs = std::min(min_vs, s.vertical_speed_mps); + min_vs = rx::Min(min_vs, s.vertical_speed_mps); } const AircraftState& s = a->state(); - std::fprintf(stderr, "(d) stalled_l=%d stalled_r=%d alpha=%.1f min_vs=%.1f end_vs=%.1f\n", + ::fprintf(stderr, "(d) stalled_l=%d stalled_r=%d alpha=%.1f min_vs=%.1f end_vs=%.1f\n", s.stalled_left, s.stalled_right, s.alpha_deg, min_vs, s.vertical_speed_mps); if (!saw_stall) return Fail("(d) wing never stalled"); if (min_vs > -3.0f) return Fail("(d) stalled wing did not sink"); @@ -258,7 +259,7 @@ int main() { }; const f32 clean = liftoff_distance(0.0f); const f32 flapped = liftoff_distance(1.0f); - std::fprintf(stderr, "(e) clean liftoff=%.0fm full-flap liftoff=%.0fm\n", clean, flapped); + ::fprintf(stderr, "(e) clean liftoff=%.0fm full-flap liftoff=%.0fm\n", clean, flapped); if (clean <= 0 || flapped <= 0) return Fail("(e) a config never lifted off"); if (flapped >= clean * 0.95f) return Fail("(e) flaps did not shorten the takeoff"); } @@ -293,7 +294,7 @@ int main() { }; const f32 coast = roll_distance(false); const f32 braked = roll_distance(true); - std::fprintf(stderr, "(f) coast dist=%.0fm braked dist=%.0fm\n", coast, braked); + ::fprintf(stderr, "(f) coast dist=%.0fm braked dist=%.0fm\n", coast, braked); if (braked >= coast * 0.6f) return Fail("(f) brakes did not stop it much shorter"); } @@ -355,7 +356,7 @@ int main() { air_yaw += WrapPi(h - aprev); aprev = h; } - std::fprintf(stderr, "(g) ground yaw=%.1f deg air yaw=%.1f deg (signed; -ve = nose right)\n", + ::fprintf(stderr, "(g) ground yaw=%.1f deg air yaw=%.1f deg (signed; -ve = nose right)\n", ground_yaw * 57.2958f, air_yaw * 57.2958f); b->~Aircraft(); if (ground_yaw > -0.15f) @@ -373,16 +374,16 @@ int main() { for (int i = 0; i < 60 * 180; ++i) { AircraftInput in; in.throttle = (i / 30) % 2 ? 1.0f : 0.0f; - in.pitch = std::sin(i * 0.05f); - in.roll = std::sin(i * 0.11f); - in.yaw = std::cos(i * 0.07f); + in.pitch = ::sin(i * 0.05f); + in.roll = ::sin(i * 0.11f); + in.yaw = ::cos(i * 0.07f); in.flaps = (i / 120) % 2 ? 1.0f : 0.0f; in.brakes = (i % 90) < 10 ? 1.0f : 0.0f; a->Update(in, kDt); world.Update(kDt); if (!StateFinite(a->state())) return Fail("(h) NaN during tumbling abuse"); } - std::fprintf(stderr, "(h) survived 180s of tumbling, y=%.1f speed=%.1f\n", a->state().position.y, + ::fprintf(stderr, "(h) survived 180s of tumbling, y=%.1f speed=%.1f\n", a->state().position.y, a->state().airspeed_mps); a->~Aircraft(); } @@ -419,7 +420,7 @@ int main() { }; const f32 calm = ground_roll(0.0f); const f32 head = ground_roll(10.0f); - std::fprintf(stderr, "(i) calm roll=%.0fm headwind(10 m/s) roll=%.0fm\n", calm, head); + ::fprintf(stderr, "(i) calm roll=%.0fm headwind(10 m/s) roll=%.0fm\n", calm, head); if (calm <= 0 || head <= 0) return Fail("(i) a config never lifted off"); if (head >= calm * 0.85f) return Fail("(i) headwind did not shorten the ground roll"); } @@ -462,11 +463,11 @@ int main() { f32 calm_dx = 0, calm_yaw = 0, cross_dx = 0, cross_yaw = 0; if (fly(0.0f, &calm_dx, &calm_yaw)) return 1; if (fly(12.0f, &cross_dx, &cross_yaw)) return 1; - std::fprintf(stderr, "(ii) calm dx=%.1f yaw=%.2f | crosswind dx=%.1f yaw=%.2f deg\n", calm_dx, + ::fprintf(stderr, "(ii) calm dx=%.1f yaw=%.2f | crosswind dx=%.1f yaw=%.2f deg\n", calm_dx, calm_yaw * 57.2958f, cross_dx, cross_yaw * 57.2958f); - if (std::fabs(cross_yaw) < 0.15f) + if (::fabs(cross_yaw) < 0.15f) return Fail("(ii) crosswind did not weathervane the nose"); - if (std::fabs(cross_yaw) <= std::fabs(calm_yaw) + 0.1f) + if (::fabs(cross_yaw) <= ::fabs(calm_yaw) + 0.1f) return Fail("(ii) crosswind weathervane not distinct from calm"); } @@ -484,11 +485,11 @@ int main() { a->~Aircraft(); for (int i = 0; i < 5; ++i) world.Update(kDt); // crash-free after removal const bool after = world.Raycast(Vec3{0, 8.0f, 0}, Vec3{0, -1, 0}, 6.0f, &hit); - std::fprintf(stderr, "(jj) hull ray before destroy=%d after=%d\n", before, after); + ::fprintf(stderr, "(jj) hull ray before destroy=%d after=%d\n", before, after); if (!before) return Fail("(jj) fuselage body not present before destroy"); if (after) return Fail("(jj) fuselage body still present after destroy"); } - std::fprintf(stderr, "aircraft_test: all checks passed\n"); + ::fprintf(stderr, "aircraft_test: all checks passed\n"); return 0; } diff --git a/test/asset_database_test.cc b/test/asset_database_test.cc index b5ee3c36..bdebeea1 100644 --- a/test/asset_database_test.cc +++ b/test/asset_database_test.cc @@ -1,11 +1,14 @@ #include "asset/asset_database.h" +#include "base/containers/array.h" +#include "base/memory/move.h" +#include "base/optional.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "base/threading/thread.h" #include -#include -#include -#include -#include +#include namespace asset = rx::asset; @@ -16,23 +19,23 @@ int failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::fprintf(stderr, "asset_database_test: FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ::fprintf(stderr, "asset_database_test: FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++failures; \ } \ } while (0) class MemoryProvider final : public asset::FileProvider { public: - bool Contains(std::string_view) const override { return true; } + bool Contains(base::StringRef) const override { return true; } - std::optional> Read(std::string_view) const override { + base::Optional> Read(base::StringRef) const override { base::Vector bytes; bytes.push_back(1); return bytes; } - void Enumerate(const std::function&) const override {} - std::string name() const override { return "memory"; } + void Enumerate(base::FunctionRef) const override {} + base::String name() const override { return "memory"; } }; void TestAddSupersedesFailure() { @@ -43,14 +46,14 @@ void TestAddSupersedesFailure() { CHECK(db.LoadMesh("missing.mesh") == nullptr); asset::Mesh mesh; mesh.id = mesh_id; - CHECK(db.AddMesh(std::move(mesh)) != nullptr); + CHECK(db.AddMesh(base::move(mesh)) != nullptr); CHECK(db.FindMesh(mesh_id) != nullptr); const asset::AssetId texture_id = asset::MakeAssetId("missing.tex"); CHECK(db.LoadTexture("missing.tex") == nullptr); asset::Texture texture; texture.id = texture_id; - CHECK(db.AddTexture(std::move(texture)) != nullptr); + CHECK(db.AddTexture(base::move(texture)) != nullptr); CHECK(db.FindTexture(texture_id) != nullptr); const asset::AssetId material_id = asset::MakeAssetId("missing.mat"); @@ -66,12 +69,12 @@ void TestConcurrentRecursiveLoad() { vfs.Mount(base::MakeUnique()); asset::AssetDatabase db(vfs); - db.RegisterTextureConverter(".tex", [](rx::ByteSpan, asset::AssetId id, std::string_view) { + db.RegisterTextureConverter(".tex", [](rx::ByteSpan, asset::AssetId id, base::StringRef) { auto texture = base::MakeUnique(); texture->id = id; return texture; }); - db.RegisterMeshConverter(".mesh", [&db](rx::ByteSpan, asset::AssetId id, std::string_view) { + db.RegisterMeshConverter(".mesh", [&db](rx::ByteSpan, asset::AssetId id, base::StringRef) { if (!db.LoadTexture("shared.tex")) return base::UniquePointer(); asset::Material material; material.id = asset::MakeAssetId("shared.mat"); @@ -81,12 +84,14 @@ void TestConcurrentRecursiveLoad() { return mesh; }); - std::array results{}; - std::array threads; + base::Array results{}; + base::Array, 8> threads; for (size_t i = 0; i < threads.size(); ++i) { - threads[i] = std::thread([&db, &results, i] { results[i] = db.LoadMesh("shared.mesh"); }); + threads[i] = base::MakeUnique( + "asset_load", [&db, &results, i] { results[i] = db.LoadMesh("shared.mesh"); }, + /*start_now=*/true); } - for (std::thread& thread : threads) thread.join(); + for (base::UniquePointer& thread : threads) thread->Join(); for (const asset::Mesh* result : results) CHECK(result == results[0]); CHECK(results[0] != nullptr); @@ -99,6 +104,6 @@ void TestConcurrentRecursiveLoad() { int main() { TestAddSupersedesFailure(); TestConcurrentRecursiveLoad(); - if (failures == 0) std::printf("asset_database_test: PASS\n"); + if (failures == 0) ::printf("asset_database_test: PASS\n"); return failures == 0 ? 0 : 1; } diff --git a/test/bc_encode_test.cc b/test/bc_encode_test.cc index 339f525a..1efbcbf9 100644 --- a/test/bc_encode_test.cc +++ b/test/bc_encode_test.cc @@ -8,15 +8,16 @@ // what the encoder currently reaches, so a real regression trips them and a // harmless change in the fit does not. -#include -#include -#include -#include -#include -#include +#include +#include +#include #include "asset/bc_encode.h" #include "asset/texture_compress.h" +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" namespace { @@ -26,7 +27,7 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "bc_encode_test: FAIL: %s\n", message); + ::fprintf(stderr, "bc_encode_test: FAIL: %s\n", message); ++failures; } @@ -68,7 +69,7 @@ void DecodeBc1(const u8* block, u8* rgba) { const u32 indices = block[4] | (block[5] << 8) | (block[6] << 16) | (static_cast(block[7]) << 24); for (u32 t = 0; t < 16; ++t) { - std::memcpy(rgba + t * 4, palette[(indices >> (t * 2)) & 3u], 4); + base::MemCopy(rgba + t * 4, palette[(indices >> (t * 2)) & 3u], 4); } } @@ -135,7 +136,7 @@ bool DecodeBc7Mode6(const u8* block, u8* rgba) { // helpers -f64 Psnr(const std::vector& a, const std::vector& b, u32 channels, u32 stride) { +f64 Psnr(const base::Vector& a, const base::Vector& b, u32 channels, u32 stride) { f64 sum = 0; u32 count = 0; for (size_t t = 0; t * stride < a.size(); ++t) { @@ -148,7 +149,7 @@ f64 Psnr(const std::vector& a, const std::vector& b, u32 channels, u32 s if (count == 0) return 0; const f64 mse = sum / static_cast(count); if (mse <= 0.0) return 99.0; - return 10.0 * std::log10(255.0 * 255.0 / mse); + return 10.0 * ::log10(255.0 * 255.0 / mse); } // Encodes a whole 4-multiple surface block by block and decodes it back, so the @@ -156,9 +157,9 @@ f64 Psnr(const std::vector& a, const std::vector& b, u32 channels, u32 s using BlockEncode = void (*)(const u8*, u8*); using BlockDecode = void (*)(const u8*, u8*); -std::vector RoundTrip(const std::vector& rgba, u32 width, u32 height, u32 block_bytes, +base::Vector RoundTrip(const base::Vector& rgba, u32 width, u32 height, u32 block_bytes, BlockEncode encode, BlockDecode decode) { - std::vector out(rgba.size()); + base::Vector out(rgba.size()); u8 block[64]; u8 packed[16]; u8 decoded[64]; @@ -166,18 +167,18 @@ std::vector RoundTrip(const std::vector& rgba, u32 width, u32 height, u3 for (u32 bx = 0; bx < width / 4; ++bx) { for (u32 ty = 0; ty < 4; ++ty) { for (u32 tx = 0; tx < 4; ++tx) { - std::memcpy(block + (ty * 4 + tx) * 4, + base::MemCopy(block + (ty * 4 + tx) * 4, rgba.data() + ((static_cast(by) * 4 + ty) * width + bx * 4 + tx) * 4, 4); } } - std::memset(packed, 0, sizeof(packed)); + base::MemSet(packed, 0, sizeof(packed)); encode(block, packed); (void)block_bytes; decode(packed, decoded); for (u32 ty = 0; ty < 4; ++ty) { for (u32 tx = 0; tx < 4; ++tx) { - std::memcpy(out.data() + ((static_cast(by) * 4 + ty) * width + bx * 4 + tx) * 4, + base::MemCopy(out.data() + ((static_cast(by) * 4 + ty) * width + bx * 4 + tx) * 4, decoded + (ty * 4 + tx) * 4, 4); } } @@ -187,7 +188,7 @@ std::vector RoundTrip(const std::vector& rgba, u32 width, u32 height, u3 } void DecodeBc7Wrapper(const u8* block, u8* rgba) { - if (!DecodeBc7Mode6(block, rgba)) std::memset(rgba, 0, 64); + if (!DecodeBc7Mode6(block, rgba)) base::MemSet(rgba, 0, 64); } u32 Rand(u32& state) { @@ -197,8 +198,8 @@ u32 Rand(u32& state) { // A plausible albedo: low-frequency colour plus fine grain, which is the mix // that separates a usable encoder from one that only handles flat blocks. -std::vector MakeAlbedo(u32 size) { - std::vector image(static_cast(size) * size * 4); +base::Vector MakeAlbedo(u32 size) { + base::Vector image(static_cast(size) * size * 4); u32 state = 12345; for (u32 y = 0; y < size; ++y) { for (u32 x = 0; x < size; ++x) { @@ -206,10 +207,10 @@ std::vector MakeAlbedo(u32 size) { const f32 v = static_cast(y) / static_cast(size); const f32 grain = static_cast(Rand(state) % 32) - 16.0f; u8* p = image.data() + (static_cast(y) * size + x) * 4; - p[0] = static_cast(std::fmin(255.0f, std::fmax(0.0f, 180.0f * u + 40.0f + grain))); - p[1] = static_cast(std::fmin(255.0f, std::fmax(0.0f, 140.0f * v + 60.0f + grain))); + p[0] = static_cast(::fmin(255.0f, ::fmax(0.0f, 180.0f * u + 40.0f + grain))); + p[1] = static_cast(::fmin(255.0f, ::fmax(0.0f, 140.0f * v + 60.0f + grain))); p[2] = static_cast( - std::fmin(255.0f, std::fmax(0.0f, 90.0f * (u + v) * 0.5f + 30.0f + grain))); + ::fmin(255.0f, ::fmax(0.0f, 90.0f * (u + v) * 0.5f + 30.0f + grain))); p[3] = 255; } } @@ -217,8 +218,8 @@ std::vector MakeAlbedo(u32 size) { } // A bumpy tangent-space normal map, encoded the way content ships it. -std::vector MakeNormalMap(u32 size) { - std::vector image(static_cast(size) * size * 4); +base::Vector MakeNormalMap(u32 size) { + base::Vector image(static_cast(size) * size * 4); for (u32 y = 0; y < size; ++y) { for (u32 x = 0; x < size; ++x) { // Built the way content is: normalize the gradient of a height field. @@ -228,9 +229,9 @@ std::vector MakeNormalMap(u32 size) { // angle. Roughly the spatial frequency a 1k tiling material carries. const f32 u = static_cast(x) * 0.09f; const f32 v = static_cast(y) * 0.07f; - f32 n[3] = {-(0.55f * std::cos(u) + 0.2f * std::cos(u * 3.1f + v)), - -(0.55f * std::sin(v) + 0.2f * std::sin(u * 2.3f)), 1.0f}; - const f32 len = std::sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]); + f32 n[3] = {-(0.55f * ::cos(u) + 0.2f * ::cos(u * 3.1f + v)), + -(0.55f * ::sin(v) + 0.2f * ::sin(u * 2.3f)), 1.0f}; + const f32 len = ::sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]); u8* p = image.data() + (static_cast(y) * size + x) * 4; for (u32 c = 0; c < 3; ++c) { p[c] = static_cast((n[c] / len) * 127.5f + 127.5f); @@ -243,22 +244,22 @@ std::vector MakeNormalMap(u32 size) { void TestBc7Color() { const u32 size = 64; - const std::vector source = MakeAlbedo(size); - const std::vector decoded = + const base::Vector source = MakeAlbedo(size); + const base::Vector decoded = RoundTrip(source, size, size, 16, asset::EncodeBc7Block, DecodeBc7Wrapper); const f64 psnr = Psnr(source, decoded, 3, 4); - std::printf("bc_encode_test: BC7 albedo psnr %.2f dB\n", psnr); + ::printf("bc_encode_test: BC7 albedo psnr %.2f dB\n", psnr); Check(psnr > 33.0, "BC7 albedo psnr below 33 dB"); } void TestBc7BeatsBc1() { const u32 size = 64; - const std::vector source = MakeAlbedo(size); + const base::Vector source = MakeAlbedo(size); const f64 bc7 = Psnr(source, RoundTrip(source, size, size, 16, asset::EncodeBc7Block, DecodeBc7Wrapper), 3, 4); const f64 bc1 = Psnr(source, RoundTrip(source, size, size, 8, asset::EncodeBc1Block, DecodeBc1), 3, 4); - std::printf("bc_encode_test: BC1 albedo psnr %.2f dB\n", bc1); + ::printf("bc_encode_test: BC1 albedo psnr %.2f dB\n", bc1); // The whole reason colour goes to BC7 and not to the half-size BC1. Check(bc7 > bc1 + 2.0, "BC7 is not clearly ahead of BC1 on albedo"); Check(bc1 > 26.0, "BC1 albedo psnr below 26 dB"); @@ -269,7 +270,7 @@ void TestBc7GreyData() { // very nearly lossless. This is the case that justifies not using BC4/BC5 for // data maps. const u32 size = 64; - std::vector source(static_cast(size) * size * 4); + base::Vector source(static_cast(size) * size * 4); for (u32 y = 0; y < size; ++y) { for (u32 x = 0; x < size; ++x) { const u8 v = static_cast((x * 2 + y) * 255 / (size * 3 - 3)); @@ -280,20 +281,20 @@ void TestBc7GreyData() { p[3] = 255; } } - const std::vector decoded = + const base::Vector decoded = RoundTrip(source, size, size, 16, asset::EncodeBc7Block, DecodeBc7Wrapper); const f64 psnr = Psnr(source, decoded, 3, 4); - std::printf("bc_encode_test: BC7 orm psnr %.2f dB\n", psnr); + ::printf("bc_encode_test: BC7 orm psnr %.2f dB\n", psnr); Check(psnr > 36.0, "BC7 orm psnr below 36 dB"); } void TestBc5Normal() { const u32 size = 64; - const std::vector source = MakeNormalMap(size); - const std::vector decoded = + const base::Vector source = MakeNormalMap(size); + const base::Vector decoded = RoundTrip(source, size, size, 16, asset::EncodeBc5Block, DecodeBc5); const f64 psnr = Psnr(source, decoded, 2, 4); - std::printf("bc_encode_test: BC5 normal xy psnr %.2f dB\n", psnr); + ::printf("bc_encode_test: BC5 normal xy psnr %.2f dB\n", psnr); Check(psnr > 44.0, "BC5 normal xy psnr below 44 dB"); // What actually matters is the angle after the shader reconstructs z, since @@ -306,22 +307,22 @@ void TestBc5Normal() { auto decode = [](const u8* p, f32* n) { n[0] = static_cast(p[0]) / 127.5f - 1.0f; n[1] = static_cast(p[1]) / 127.5f - 1.0f; - n[2] = std::sqrt(std::fmax(0.0f, 1.0f - n[0] * n[0] - n[1] * n[1])); - const f32 len = std::sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]); + n[2] = ::sqrt(::fmax(0.0f, 1.0f - n[0] * n[0] - n[1] * n[1])); + const f32 len = ::sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]); for (u32 c = 0; c < 3; ++c) n[c] /= len; }; f32 a[3]; f32 b[3]; decode(source.data() + t * 4, a); decode(decoded.data() + t * 4, b); - const f32 dot = std::fmin(1.0f, std::fmax(-1.0f, a[0] * b[0] + a[1] * b[1] + a[2] * b[2])); - const f64 degrees = std::acos(dot) * 57.2957795; - worst_degrees = std::fmax(worst_degrees, degrees); + const f32 dot = ::fmin(1.0f, ::fmax(-1.0f, a[0] * b[0] + a[1] * b[1] + a[2] * b[2])); + const f64 degrees = ::acos(dot) * 57.2957795; + worst_degrees = ::fmax(worst_degrees, degrees); mean_degrees += degrees; ++samples; } mean_degrees /= static_cast(samples); - std::printf("bc_encode_test: BC5 normal angle mean %.3f deg, worst %.3f deg\n", mean_degrees, + ::printf("bc_encode_test: BC5 normal angle mean %.3f deg, worst %.3f deg\n", mean_degrees, worst_degrees); // Banded lighting is a mean-error symptom; the worst texel is a single // pixel and gets filtered away. @@ -329,16 +330,16 @@ void TestBc5Normal() { Check(worst_degrees < 2.0, "BC5 worst reconstructed normal angle above 2 degrees"); // And the comparison the format choice rests on. - const std::vector as_bc7 = + const base::Vector as_bc7 = RoundTrip(source, size, size, 16, asset::EncodeBc7Block, DecodeBc7Wrapper); const f64 bc7_psnr = Psnr(source, as_bc7, 2, 4); - std::printf("bc_encode_test: BC7 normal xy psnr %.2f dB\n", bc7_psnr); + ::printf("bc_encode_test: BC7 normal xy psnr %.2f dB\n", bc7_psnr); Check(psnr > bc7_psnr + 3.0, "BC5 is not clearly ahead of BC7 on a normal map"); } void TestBc3Alpha() { const u32 size = 64; - std::vector source(static_cast(size) * size * 4); + base::Vector source(static_cast(size) * size * 4); u32 state = 777; for (u32 y = 0; y < size; ++y) { for (u32 x = 0; x < size; ++x) { @@ -353,7 +354,7 @@ void TestBc3Alpha() { p[3] = dx * dx * 0.6f + dy * dy < 400.0f ? 255 : 0; } } - const std::vector decoded = + const base::Vector decoded = RoundTrip(source, size, size, 16, asset::EncodeBc3Block, DecodeBc3); u32 wrong = 0; for (size_t t = 0; t * 4 < source.size(); ++t) { @@ -361,7 +362,7 @@ void TestBc3Alpha() { const bool got = decoded[t * 4 + 3] >= 128; if (want != got) ++wrong; } - std::printf("bc_encode_test: BC3 cutout mismatches %u of %u\n", wrong, + ::printf("bc_encode_test: BC3 cutout mismatches %u of %u\n", wrong, static_cast(source.size() / 4)); Check(wrong == 0, "BC3 changed which texels pass a 0.5 alpha cutoff"); } @@ -383,7 +384,7 @@ void TestBc7AlphaDecode() { Check(DecodeBc7Mode6(packed, reference), "encoder did not emit BC7 mode 6"); u8 decoded[64]; Check(asset::DecodeBc7Block(packed, decoded), "DecodeBc7Block rejected a mode 6 block"); - Check(std::memcmp(decoded, reference, sizeof(decoded)) == 0, + Check(base::MemCompare(decoded, reference, sizeof(decoded)) == 0, "DecodeBc7Block disagrees with an independent mode 6 decode"); u8 not_mode6[16] = {}; @@ -414,7 +415,7 @@ void TestFlatBlocks() { } Check(exact, "BC7 did not reproduce a flat block exactly"); - std::memset(packed, 0, sizeof(packed)); + base::MemSet(packed, 0, sizeof(packed)); asset::EncodeBc5Block(block, packed); DecodeBc5(packed, decoded); Check(decoded[0] == value && decoded[1] == value, "BC5 did not reproduce a flat block exactly"); @@ -423,30 +424,28 @@ void TestFlatBlocks() { // CompressTexture -asset::Texture MakeTexture(const std::vector& rgba, u32 size, bool srgb) { +asset::Texture MakeTexture(const base::Vector& rgba, u32 size, bool srgb) { asset::Texture texture; texture.format = asset::TextureFormat::kRgba8; texture.width = size; texture.height = size; texture.is_srgb = srgb; texture.data.resize(rgba.size()); - std::memcpy(texture.data.data(), rgba.data(), rgba.size()); + base::MemCopy(texture.data.data(), rgba.data(), rgba.size()); return texture; } void TestCompressTexture() { - namespace fs = std::filesystem; - const fs::path cache = fs::temp_directory_path() / "rx_bc_encode_test_cache"; - std::error_code error; - fs::remove_all(cache, error); + const base::String cache = rx::fs::Join(rx::fs::TempDirectory(), "rx_bc_encode_test_cache"); + rx::fs::RemoveAll(cache); #if defined(_WIN32) - _putenv_s("RX_TEXCACHE_DIR", cache.string().c_str()); + _putenv_s("RX_TEXCACHE_DIR", cache.c_str()); #else - setenv("RX_TEXCACHE_DIR", cache.string().c_str(), 1); + setenv("RX_TEXCACHE_DIR", cache.c_str(), 1); #endif const u32 size = 64; - const std::vector albedo = MakeAlbedo(size); + const base::Vector albedo = MakeAlbedo(size); // Off by default: with no gpu nothing may start emitting block formats. asset::Texture untouched = MakeTexture(albedo, size, true); @@ -483,7 +482,7 @@ void TestCompressTexture() { "data did not compress"); Check(data.format == asset::TextureFormat::kBc7, "data did not pick BC7"); - std::vector masked = albedo; + base::Vector masked = albedo; masked[3] = 0; asset::Texture cutout = MakeTexture(masked, size, true); Check(asset::CompressTexture(&cutout, asset::TextureRole::kColor, "test/cutout"), @@ -516,11 +515,11 @@ void TestCompressTexture() { const asset::TextureCompressionStats after = asset::CompressionTotals(); Check(after.cache_hits == before.cache_hits + 1, "content-keyed cache did not hit"); Check(cached.data.size() == color.data.size() && - std::memcmp(cached.data.data(), color.data.data(), color.data.size()) == 0, + base::MemCompare(cached.data.data(), color.data.data(), color.data.size()) == 0, "cache returned different bytes than the encoder"); asset::SetTextureCompression({}); - fs::remove_all(cache, error); + rx::fs::RemoveAll(cache); } } // namespace @@ -534,6 +533,6 @@ int main() { TestBc7AlphaDecode(); TestFlatBlocks(); TestCompressTexture(); - if (failures == 0) std::printf("bc_encode_test: ok\n"); + if (failures == 0) ::printf("bc_encode_test: ok\n"); return failures == 0 ? 0 : 1; } diff --git a/test/blend_jiggle_test.cc b/test/blend_jiggle_test.cc index 368ec366..9afceb27 100644 --- a/test/blend_jiggle_test.cc +++ b/test/blend_jiggle_test.cc @@ -1,7 +1,5 @@ -#include -#include -#include -#include +#include +#include #include "anim/body_dynamics.h" #include "anim/locomotion.h" @@ -9,6 +7,9 @@ #include "asset/asset_id.h" #include "asset/blend_import.h" #include "asset/gltf_loader.h" +#include "base/algorithm.h" +#include "base/strings/xstring.h" +#include "core/scalar.h" namespace { @@ -16,27 +17,27 @@ using namespace rx; bool Check(bool condition, const char *message) { if (!condition) - std::fprintf(stderr, "blend_jiggle_test: FAIL: %s\n", message); + ::fprintf(stderr, "blend_jiggle_test: FAIL: %s\n", message); return condition; } f32 MatrixDifference(const Mat4 &a, const Mat4 &b) { f32 difference = 0; for (u32 i = 0; i < 16; ++i) - difference = std::max(difference, std::fabs(a.m[i] - b.m[i])); + difference = rx::Max(difference, ::fabs(a.m[i] - b.m[i])); return difference; } f32 RotationDifference(const Quat &a, const Quat &b) { - const f32 dot = std::fabs(a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w); - return 1.0f - std::min(dot, 1.0f); + const f32 dot = ::fabs(a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w); + return 1.0f - rx::Min(dot, 1.0f); } -int TestBlend(const std::string &blend_path, const std::string &script) { +int TestBlend(const base::String &blend_path, const base::String &script) { asset::BlendImportOptions options; options.converter_script = script; asset::BlendImportResult converted; - std::string error; + base::String error; const bool converted_ok = asset::ConvertBlendScene(blend_path, options, &converted, &error); if (!Check(converted_ok, @@ -172,8 +173,8 @@ int TestBlend(const std::string &blend_path, const std::string &script) { anim::BuildSkinPalette(moved_model, body_mesh->skin, remap, &moved_palette); const i32 left_palette = static_cast( - std::find(body_mesh->skin.bones.begin(), body_mesh->skin.bones.end(), - std::string("pectoral.L")) - + base::Find(body_mesh->skin.bones.begin(), body_mesh->skin.bones.end(), + base::String("pectoral.L")) - body_mesh->skin.bones.begin()); if (!Check(left_palette >= 0 && left_palette < static_cast(moved_palette.size()), @@ -197,7 +198,7 @@ int TestBlend(const std::string &blend_path, const std::string &script) { "landing impulse activates Lara's authored flatten morph")) return 1; - std::printf( + ::printf( "blend_jiggle_test: PASS: %llu meshes, %llu instances, %llu bones, " "%llu body vertices, palette delta %.6f, flatten weight %.4f, " "walk styles Hip Sway/March%s\n", @@ -214,7 +215,7 @@ int TestBlend(const std::string &blend_path, const std::string &script) { int main(int argc, char **argv) { if (argc > 1) { - const std::string script = argc > 2 ? argv[2] : RX_BLEND_CONVERTER_SCRIPT; + const base::String script = argc > 2 ? argv[2] : RX_BLEND_CONVERTER_SCRIPT; return TestBlend(argv[1], script); } @@ -223,13 +224,13 @@ int main(int argc, char **argv) { asset::BlendImportOptions options; options.converter_script = RX_BLEND_CONVERTER_SCRIPT; asset::BlendImportResult result; - std::string error; + base::String error; if (!Check( !asset::ConvertBlendScene("missing.blend", options, &result, &error), "missing .blend is rejected")) return 1; if (!Check(!error.empty(), "missing .blend reports a useful error")) return 1; - std::puts("blend_jiggle_test: PASS: converter API validation"); + ::puts("blend_jiggle_test: PASS: converter API validation"); return 0; } diff --git a/test/boat_profiles_test.cc b/test/boat_profiles_test.cc index f6e1cf99..b0343f75 100644 --- a/test/boat_profiles_test.cc +++ b/test/boat_profiles_test.cc @@ -14,10 +14,11 @@ // empty freeboard; (g) NaN-free over a minute of Gerstner chop across every // profile and load state, including a mid-run SetCargo load transfer. -#include -#include -#include +#include +#include +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" #include "core/math.h" #include "physics/boat.h" #include "physics/boat_profiles.h" @@ -36,7 +37,7 @@ namespace { constexpr f32 kDt = 1.0f / 60.0f; int Fail(const char* what) { - std::fprintf(stderr, "boat_profiles_test FAIL: %s\n", what); + ::fprintf(stderr, "boat_profiles_test FAIL: %s\n", what); return 1; } @@ -60,7 +61,7 @@ BoatState Run(PhysicsWorld& world, Boat& boat, const BoatInput& in, int steps) { f32 Heading(const BoatState& s) { const Vec3 fwd = Rotate(s.rotation, Vec3{0, 0, 1}); - return std::atan2(fwd.x, fwd.z); + return ::atan2(fwd.x, fwd.z); } f32 Uprightness(const BoatState& s) { @@ -87,22 +88,22 @@ BoatDesc Profile(int i) { int main() { PhysicsWorld probe; if (!probe.Initialize()) { - std::fprintf(stderr, "boat_profiles_test: physics unavailable, skipping\n"); + ::fprintf(stderr, "boat_profiles_test: physics unavailable, skipping\n"); return 0; } // Spawns a settled boat in its own world. Caller owns both via the out-params. auto make = [](PhysicsWorld& world, BoatDesc desc, f32 cargo_frac, f32 settle_s, - std::unique_ptr& out) { + base::UniquePointer& out) { world.Initialize(); InstallFlatWater(world); - auto boat = std::make_unique(world, desc, Vec3{0, 0.6f, 0}, 0.0f); + auto boat = base::MakeUnique(world, desc, Vec3{0, 0.6f, 0}, 0.0f); if (cargo_frac > 0.0f) boat->SetCargo(cargo_frac * desc.max_cargo_kg); for (int i = 0; i < static_cast(settle_s * 60.0f); ++i) { boat->Update({}, kDt); world.Update(kDt); } - out = std::move(boat); + out = base::move(boat); }; // (a) Empty settle-draft ordering dinghy < speedboat < fishing < barge, and @@ -110,17 +111,17 @@ int main() { f32 empty_draft[5] = {}; f32 laden_draft[5] = {}; { - std::fprintf(stderr, "(a) settle draft (m): profile empty laden(full) d\n"); + ::fprintf(stderr, "(a) settle draft (m): profile empty laden(full) d\n"); const int order[4] = {0, 1, 3, 4}; // dinghy, speedboat, fishing, barge for (int i = 0; i < 5; ++i) { const BoatDesc d = Profile(i); PhysicsWorld we, wl; - std::unique_ptr be, bl; + base::UniquePointer be, bl; make(we, d, 0.0f, 8.0f, be); make(wl, d, 1.0f, 8.0f, bl); empty_draft[i] = be->state().draft_m; laden_draft[i] = bl->state().draft_m; - std::fprintf(stderr, " %-10s %6.3f %6.3f +%.3f\n", Name(i), empty_draft[i], + ::fprintf(stderr, " %-10s %6.3f %6.3f +%.3f\n", Name(i), empty_draft[i], laden_draft[i], laden_draft[i] - empty_draft[i]); const f32 margin = i >= 3 ? 0.10f : 0.03f; // big hulls must differ by >10 cm if (laden_draft[i] <= empty_draft[i] + margin) @@ -136,7 +137,7 @@ int main() { // full-throttle run, so a planing hull's speed ripple doesn't alias the reading. auto sustained_top = [&](int profile, f32 cargo) { PhysicsWorld w; - std::unique_ptr b; + base::UniquePointer b; make(w, Profile(profile), cargo, 2.0f, b); BoatInput in; in.throttle = 1.0f; @@ -145,7 +146,7 @@ int main() { const int n = 60 * 5; for (int k = 0; k < n; ++k) { Step(w, *b, in); - sum += std::fabs(b->state().forward_speed); + sum += ::fabs(b->state().forward_speed); } return static_cast(sum / n); }; @@ -154,13 +155,13 @@ int main() { // fully laden speedboat is measurably slower than empty. f32 top[5] = {}; { - std::fprintf(stderr, "(b) sustained top speed (m/s), full throttle:\n"); + ::fprintf(stderr, "(b) sustained top speed (m/s), full throttle:\n"); for (int i = 0; i < 5; ++i) { top[i] = sustained_top(i, 0.0f); - std::fprintf(stderr, " %-10s %5.2f\n", Name(i), top[i]); + ::fprintf(stderr, " %-10s %5.2f\n", Name(i), top[i]); } const f32 laden_speed = sustained_top(1, 1.0f); - std::fprintf(stderr, " speedboat(laden) %5.2f (empty %5.2f)\n", laden_speed, top[1]); + ::fprintf(stderr, " speedboat(laden) %5.2f (empty %5.2f)\n", laden_speed, top[1]); if (top[2] <= top[4] || top[1] <= top[4]) return Fail("(b) barge not slowest"); if (top[4] >= top[3]) return Fail("(b) barge should be slower than the fishing boat"); if (laden_speed >= top[1] - 0.5f) return Fail("(b) laden speedboat not measurably slower"); @@ -176,7 +177,7 @@ int main() { { auto planing_state = [&](int profile, f32 cargo, f32* wetted) { PhysicsWorld w; - std::unique_ptr b; + base::UniquePointer b; make(w, Profile(profile), cargo, 2.0f, b); BoatInput in; in.throttle = 1.0f; @@ -196,7 +197,7 @@ int main() { const f32 sb_empty = planing_state(1, 0.0f, &w_empty); const f32 sb_laden = planing_state(1, 1.0f, &w_laden); const f32 barge_empty = planing_state(4, 0.0f, &w_barge); - std::fprintf(stderr, + ::fprintf(stderr, "(c) speedboat empty: planing=%.2f wetted=%.2f | laden: planing=%.2f wetted=%.2f | " "barge empty planing=%.2f\n", sb_empty, w_empty, sb_laden, w_laden, barge_empty); @@ -212,7 +213,7 @@ int main() { { auto turn_rate = [&](int profile, f32 cargo) { PhysicsWorld w; - std::unique_ptr b; + base::UniquePointer b; make(w, Profile(profile), cargo, 2.0f, b); BoatInput go; go.throttle = 1.0f; @@ -221,15 +222,15 @@ int main() { BoatInput t = go; t.steer = 1.0f; const BoatState s = Run(w, *b, t, 60 * 5); - return std::fabs(Heading(s) - h0) / 5.0f; // rad/s + return ::fabs(Heading(s) - h0) / 5.0f; // rad/s }; - std::fprintf(stderr, "(d) turn rate (rad/s), full helm:\n"); + ::fprintf(stderr, "(d) turn rate (rad/s), full helm:\n"); for (int i = 0; i < 5; ++i) { turn[i] = turn_rate(i, 0.0f); - std::fprintf(stderr, " %-10s %5.3f\n", Name(i), turn[i]); + ::fprintf(stderr, " %-10s %5.3f\n", Name(i), turn[i]); } const f32 fishing_laden_turn = turn_rate(3, 1.0f); - std::fprintf(stderr, " fishing(laden) %5.3f (empty %5.3f)\n", fishing_laden_turn, turn[3]); + ::fprintf(stderr, " fishing(laden) %5.3f (empty %5.3f)\n", fishing_laden_turn, turn[3]); if (turn[0] <= turn[4] * 2.0f) return Fail("(d) dinghy not far more agile than the barge"); if (turn[2] <= turn[4] * 2.0f) return Fail("(d) jetski not far more agile than the barge"); if (fishing_laden_turn >= turn[3]) return Fail("(d) laden fishing boat did not turn slower"); @@ -243,10 +244,10 @@ int main() { // at the end (1 = upright, <=0 = on its beam-ends / inverted). auto knockdown = [&](int profile, f32 cargo, f32 heel, f32 secs) { PhysicsWorld w; - std::unique_ptr b; + base::UniquePointer b; make(w, Profile(profile), cargo, 5.0f, b); const Vec3 p = b->state().position; - const f32 rot[4] = {0.0f, 0.0f, std::sin(heel * 0.5f), std::cos(heel * 0.5f)}; + const f32 rot[4] = {0.0f, 0.0f, ::sin(heel * 0.5f), ::cos(heel * 0.5f)}; w.SetBodyPosition(b->body(), p, rot); b->Update({}, kDt); // refresh telemetry from the heeled pose return Uprightness(Run(w, *b, {}, static_cast(secs * 60.0f))); @@ -256,10 +257,10 @@ int main() { // self-right" metric. auto time_to_right = [&](int profile, f32 cargo, f32 heel, f32 timeout) { PhysicsWorld w; - std::unique_ptr b; + base::UniquePointer b; make(w, Profile(profile), cargo, 5.0f, b); const Vec3 p = b->state().position; - const f32 rot[4] = {0.0f, 0.0f, std::sin(heel * 0.5f), std::cos(heel * 0.5f)}; + const f32 rot[4] = {0.0f, 0.0f, ::sin(heel * 0.5f), ::cos(heel * 0.5f)}; w.SetBodyPosition(b->body(), p, rot); b->Update({}, kDt); const int limit = static_cast(timeout * 60.0f); @@ -274,7 +275,7 @@ int main() { const f32 fishing_empty = knockdown(3, 0.0f, heel, 6.0f); const f32 t_empty = time_to_right(3, 0.0f, heel, 8.0f); const f32 t_over = time_to_right(3, 1.25f, heel, 8.0f); // structural overload - std::fprintf(stderr, + ::fprintf(stderr, "(e) knockdown: dinghy(laden) up=%.2f (capsized) | fishing(empty) up=%.2f | " "time-to-right fishing empty=%.2fs overload=%.2fs\n", dinghy_laden, fishing_empty, t_empty, t_over); @@ -288,12 +289,12 @@ int main() { // while empty it has comfortable freeboard. { PhysicsWorld we, wo; - std::unique_ptr be, bo; + base::UniquePointer be, bo; make(we, Profile(4), 0.0f, 10.0f, be); make(wo, Profile(4), 1.25f, 10.0f, bo); // structural overload const f32 fb_empty = be->state().freeboard_m; const f32 fb_over = bo->state().freeboard_m; - std::fprintf(stderr, "(f) barge freeboard (m): empty=%.3f overloaded(125%%)=%.3f (cargo %.0f kg)\n", + ::fprintf(stderr, "(f) barge freeboard (m): empty=%.3f overloaded(125%%)=%.3f (cargo %.0f kg)\n", fb_empty, fb_over, bo->state().cargo_kg); if (fb_empty < 0.5f) return Fail("(f) empty barge freeboard not comfortable"); if (fb_over > 0.15f) return Fail("(f) overloaded barge deck not near awash"); @@ -317,8 +318,8 @@ int main() { Boat boat(w, d, Vec3{0, 0.8f, 0}, 0.0f); BoatInput in; for (int k = 0; k < 60 * 60; ++k) { - in.throttle = std::sin(t * 0.7f); - in.steer = std::sin(t * 0.3f); + in.throttle = ::sin(t * 0.7f); + in.steer = ::sin(t * 0.3f); // Load transfer partway: ramp cargo up past the rated limit and back. if (k == 60 * 20) boat.SetCargo(d.max_cargo_kg); if (k == 60 * 30) boat.SetCargo(d.max_cargo_kg * 1.25f); @@ -327,18 +328,18 @@ int main() { boat.Update(in, kDt); w.Update(kDt); const BoatState s = boat.state(); - if (!std::isfinite(s.position.x) || !std::isfinite(s.position.y) || - !std::isfinite(s.position.z) || !std::isfinite(s.rpm) || - !std::isfinite(s.forward_speed) || !std::isfinite(s.rotation.w) || - !std::isfinite(s.draft_m) || !std::isfinite(s.freeboard_m)) { + if (!::isfinite(s.position.x) || !::isfinite(s.position.y) || + !::isfinite(s.position.z) || !::isfinite(s.rpm) || + !::isfinite(s.forward_speed) || !::isfinite(s.rotation.w) || + !::isfinite(s.draft_m) || !::isfinite(s.freeboard_m)) { return Fail("(g) NaN/Inf in boat state on chop"); } } - std::fprintf(stderr, "(g) %-10s survived 60 s chop + load transfer, draft=%.2f freeboard=%.2f\n", + ::fprintf(stderr, "(g) %-10s survived 60 s chop + load transfer, draft=%.2f freeboard=%.2f\n", Name(i), boat.state().draft_m, boat.state().freeboard_m); } } - std::fprintf(stderr, "boat_profiles_test: all checks passed\n"); + ::fprintf(stderr, "boat_profiles_test: all checks passed\n"); return 0; } diff --git a/test/boat_test.cc b/test/boat_test.cc index 9c196972..270f0c3f 100644 --- a/test/boat_test.cc +++ b/test/boat_test.cc @@ -5,8 +5,8 @@ // no-planing hull, river drift, self-righting from a knock-down, buoyancy // exemption (no double buoyancy), and NaN-free simulation on Gerstner chop. -#include -#include +#include +#include #include "core/math.h" #include "physics/boat.h" @@ -25,7 +25,7 @@ namespace { constexpr f32 kDt = 1.0f / 60.0f; int Fail(const char* what) { - std::fprintf(stderr, "boat_test FAIL: %s\n", what); + ::fprintf(stderr, "boat_test FAIL: %s\n", what); return 1; } @@ -53,7 +53,7 @@ BoatState Run(PhysicsWorld& world, Boat& boat, const BoatInput& in, int steps) { // Heading angle (radians) of the hull forward axis in the XZ plane. f32 Heading(const BoatState& s) { const Vec3 fwd = Rotate(s.rotation, Vec3{0, 0, 1}); - return std::atan2(fwd.x, fwd.z); + return ::atan2(fwd.x, fwd.z); } // World up projected onto the hull's up axis: 1 = upright, 0 = on its side. @@ -67,7 +67,7 @@ f32 Uprightness(const BoatState& s) { int main() { PhysicsWorld probe; if (!probe.Initialize()) { - std::fprintf(stderr, "boat_test: physics unavailable, skipping\n"); + ::fprintf(stderr, "boat_test: physics unavailable, skipping\n"); return 0; } @@ -85,7 +85,7 @@ int main() { const f32 bottom = s0.position.y - hh_y; // hull bottom (water at y=0) const f32 deck = s0.position.y + hh_y; // hull top const f32 draft = -bottom; // submerged depth of the bottom - std::fprintf(stderr, "(a) center_y=%.3f draft=%.3f deck=%.3f wetted=%.2f\n", + ::fprintf(stderr, "(a) center_y=%.3f draft=%.3f deck=%.3f wetted=%.2f\n", s0.position.y, draft, deck, s0.wetted); if (draft <= 0.02f || draft > 0.45f) return Fail("(a) draft not in a sane floating range"); if (deck <= 0.05f) return Fail("(a) deck not above water"); @@ -94,8 +94,8 @@ int main() { const Vec3 p0 = s0.position; Run(world, boat, {}, 60 * 3); const Vec3 p1 = boat.state().position; - const f32 drift = std::sqrt((p1.x - p0.x) * (p1.x - p0.x) + (p1.z - p0.z) * (p1.z - p0.z)); - std::fprintf(stderr, "(a) idle drift=%.4f m\n", drift); + const f32 drift = ::sqrt((p1.x - p0.x) * (p1.x - p0.x) + (p1.z - p0.z) * (p1.z - p0.z)); + ::fprintf(stderr, "(a) idle drift=%.4f m\n", drift); if (drift > 0.15f) return Fail("(a) idle boat drifted"); } @@ -110,11 +110,11 @@ int main() { BoatInput in; in.throttle = 1.0f; const BoatState s = Run(world, boat, in, 60 * 15); - std::fprintf(stderr, "(b) forward_speed=%.2f pos.z=%.2f pos.x=%.2f up=%.2f\n", s.forward_speed, + ::fprintf(stderr, "(b) forward_speed=%.2f pos.z=%.2f pos.x=%.2f up=%.2f\n", s.forward_speed, s.position.z, s.position.x, Uprightness(s)); if (s.forward_speed < 4.0f) return Fail("(b) boat did not accelerate to several m/s"); if (s.position.z < 10.0f) return Fail("(b) boat did not travel forward (+Z)"); - if (std::fabs(s.position.x) > 3.0f) return Fail("(b) boat veered sideways under straight throttle"); + if (::fabs(s.position.x) > 3.0f) return Fail("(b) boat veered sideways under straight throttle"); } // (c) Sustained steer while moving yaws the boat. @@ -133,8 +133,8 @@ int main() { turn.throttle = 1.0f; turn.steer = 1.0f; const BoatState s = Run(world, boat, turn, 60 * 5); - const f32 dh = std::fabs(Heading(s) - h0); - std::fprintf(stderr, "(c) heading change=%.3f rad\n", dh); + const f32 dh = ::fabs(Heading(s) - h0); + ::fprintf(stderr, "(c) heading change=%.3f rad\n", dh); if (dh < 0.2f) return Fail("(c) sustained steer did not yaw the boat"); } @@ -150,7 +150,7 @@ int main() { in.throttle = 1.0f; const f32 top = Run(world, boat, in, 60 * 15).forward_speed; const f32 coast = Run(world, boat, {}, 60 * 8).forward_speed; // throttle cut - std::fprintf(stderr, "(d) top=%.2f coast=%.2f\n", top, coast); + ::fprintf(stderr, "(d) top=%.2f coast=%.2f\n", top, coast); if (coast >= top * 0.6f) return Fail("(d) speed did not decay after cutting throttle"); } @@ -173,7 +173,7 @@ int main() { }; const BoatState planing = top_speed(true); const BoatState displacement = top_speed(false); - std::fprintf(stderr, "(e) planing top=%.2f (frac=%.2f wetted=%.2f) displacement top=%.2f\n", + ::fprintf(stderr, "(e) planing top=%.2f (frac=%.2f wetted=%.2f) displacement top=%.2f\n", planing.forward_speed, planing.planing, planing.wetted, displacement.forward_speed); if (planing.planing < 0.5f) return Fail("(e) planing fraction did not rise at speed"); if (planing.forward_speed <= displacement.forward_speed * 1.1f) { @@ -196,7 +196,7 @@ int main() { const f32 x0 = boat.state().position.x; const BoatState s = Run(world, boat, {}, 60 * 8); const f32 dx = s.position.x - x0; - std::fprintf(stderr, "(f) downstream drift dx=%.2f m\n", dx); + ::fprintf(stderr, "(f) downstream drift dx=%.2f m\n", dx); if (dx < 2.0f) return Fail("(f) river flow did not carry the idle boat downstream"); } @@ -209,12 +209,12 @@ int main() { Boat boat(world, desc, Vec3{0, 0.4f, 0}, 0.0f); // Knock it down: roll ~80 deg about the forward (+Z) axis, then let go. const f32 heel = 1.4f; - const f32 rot[4] = {0.0f, 0.0f, std::sin(heel * 0.5f), std::cos(heel * 0.5f)}; + const f32 rot[4] = {0.0f, 0.0f, ::sin(heel * 0.5f), ::cos(heel * 0.5f)}; world.SetBodyPosition(boat.body(), Vec3{0, 0.4f, 0}, rot); Step(world, boat, {}); // refresh telemetry from the knocked-down pose const f32 before = Uprightness(boat.state()); const BoatState s = Run(world, boat, {}, 60 * 4); - std::fprintf(stderr, "(g) uprightness before=%.2f after=%.2f\n", before, Uprightness(s)); + ::fprintf(stderr, "(g) uprightness before=%.2f after=%.2f\n", before, Uprightness(s)); if (Uprightness(s) < 0.85f) return Fail("(g) boat did not self-right from a knock-down"); } @@ -232,8 +232,8 @@ int main() { // Predicted equilibrium draft d = mass / (rho * footprint). const f32 footprint = (2.0f * desc.hull_half_extent.x) * (2.0f * desc.hull_half_extent.z); const f32 predicted = desc.mass / (1000.0f * footprint); - std::fprintf(stderr, "(h) draft=%.3f predicted=%.3f\n", draft, predicted); - if (std::fabs(draft - predicted) > 0.06f) { + ::fprintf(stderr, "(h) draft=%.3f predicted=%.3f\n", draft, predicted); + if (::fabs(draft - predicted) > 0.06f) { return Fail("(h) draft does not match single-model displacement (double buoyancy?)"); } } @@ -255,18 +255,18 @@ int main() { BoatInput in; for (int i = 0; i < 60 * 60; ++i) { // Wander the helm so thrust, planing and rudder all exercise on the chop. - in.throttle = std::sin(t * 0.7f); - in.steer = std::sin(t * 0.3f); + in.throttle = ::sin(t * 0.7f); + in.steer = ::sin(t * 0.3f); t += kDt; Step(world, boat, in); const BoatState s = boat.state(); - if (!std::isfinite(s.position.x) || !std::isfinite(s.position.y) || - !std::isfinite(s.position.z) || !std::isfinite(s.rpm) || - !std::isfinite(s.forward_speed) || !std::isfinite(s.rotation.w)) { + if (!::isfinite(s.position.x) || !::isfinite(s.position.y) || + !::isfinite(s.position.z) || !::isfinite(s.rpm) || + !::isfinite(s.forward_speed) || !::isfinite(s.rotation.w)) { return Fail("(i) NaN/Inf in boat state on chop"); } } - std::fprintf(stderr, "(i) survived 60 s of chop, final speed=%.2f rpm=%.0f\n", + ::fprintf(stderr, "(i) survived 60 s of chop, final speed=%.2f rpm=%.0f\n", boat.state().speed_mps, boat.state().rpm); } @@ -291,10 +291,10 @@ int main() { f32 calm_dx = 0, calm_up = 0, wind_dx = 0, wind_up = 0; drift(0.0f, &calm_dx, &calm_up); drift(25.0f, &wind_dx, &wind_up); - std::fprintf(stderr, "(j) calm dx=%.2f up=%.4f | beam wind(25 m/s) dx=%.2f up=%.4f\n", calm_dx, + ::fprintf(stderr, "(j) calm dx=%.2f up=%.4f | beam wind(25 m/s) dx=%.2f up=%.4f\n", calm_dx, calm_up, wind_dx, wind_up); if (wind_dx < 1.5f) return Fail("(j) beam wind did not push the boat downwind"); - if (wind_dx <= std::fabs(calm_dx) + 1.0f) return Fail("(j) wind drift not distinct from calm"); + if (wind_dx <= ::fabs(calm_dx) + 1.0f) return Fail("(j) wind drift not distinct from calm"); } // (k) Lifecycle: destroying a Boat removes its hull body and clears the @@ -315,11 +315,11 @@ int main() { for (int i = 0; i < 30; ++i) world.Update(kDt); // crash-free after removal PhysicsWorld::RayHit hit; const bool after = world.Raycast(Vec3{0, 5.0f, 0}, Vec3{0, -1, 0}, 6.0f, &hit); - std::fprintf(stderr, "(k) hull ray before destroy=%d after=%d\n", before, after); + ::fprintf(stderr, "(k) hull ray before destroy=%d after=%d\n", before, after); if (!before) return Fail("(k) hull body not present before destroy"); if (after) return Fail("(k) hull body still present after destroy"); } - std::fprintf(stderr, "boat_test: all checks passed\n"); + ::fprintf(stderr, "boat_test: all checks passed\n"); return 0; } diff --git a/test/body_dynamics_test.cc b/test/body_dynamics_test.cc index c211c496..2801212c 100644 --- a/test/body_dynamics_test.cc +++ b/test/body_dynamics_test.cc @@ -1,9 +1,10 @@ -#include -#include -#include +#include +#include #include "anim/body_dynamics.h" #include "asset/asset_id.h" +#include "base/memory/move.h" +#include "core/scalar.h" namespace { @@ -15,14 +16,14 @@ int failures = 0; void Check(bool condition, const char *message) { if (condition) return; - std::fprintf(stderr, "body_dynamics_test: FAIL: %s\n", message); + ::fprintf(stderr, "body_dynamics_test: FAIL: %s\n", message); ++failures; } void Near(f32 actual, f32 expected, f32 tolerance, const char *message) { - if (std::fabs(actual - expected) <= tolerance) + if (::fabs(actual - expected) <= tolerance) return; - std::fprintf( + ::fprintf( stderr, "body_dynamics_test: FAIL: %s (got %.6f, expected %.6f +/- %.6f)\n", message, actual, expected, tolerance); @@ -69,10 +70,10 @@ BodyRegionSample Simulate(f32 step, f32 seconds, BodyDynamics dynamics; dynamics.AddRegion(TestRegion()); dynamics.Update(skeleton, frame, step, &pose); // initialize - const u32 count = static_cast(std::ceil(seconds / step)); + const u32 count = static_cast(::ceil(seconds / step)); for (u32 i = 0; i < count; ++i) { pose.ResetToBind(skeleton); - dynamics.Update(skeleton, frame, std::min(step, seconds - i * step), &pose); + dynamics.Update(skeleton, frame, rx::Min(step, seconds - i * step), &pose); } return dynamics.sample(0); } @@ -81,7 +82,7 @@ void TestGravityAndInertia() { BodyDynamicsFrame gravity; const BodyRegionSample sag = Simulate(1.0f / 120.0f, 2.0f, gravity); const f32 expected = - -9.81f / std::pow(2.0f * 3.14159265358979323846f * 2.0f, 2.0f); + -9.81f / ::pow(2.0f * 3.14159265358979323846f * 2.0f, 2.0f); Near(sag.translation.y, expected, 0.0005f, "settled sag follows the spring's physical equilibrium"); @@ -129,9 +130,9 @@ void TestLimitsAndTeleport() { pose.ResetToBind(skeleton); dynamics.Update(skeleton, frame, 1.0f / 60.0f, &pose); BodyRegionSample sample = dynamics.sample(0); - Check(std::fabs(sample.translation.x) <= 0.010001f && - std::fabs(sample.translation.y) <= 0.020001f && - std::fabs(sample.translation.z) <= 0.030001f, + Check(::fabs(sample.translation.x) <= 0.010001f && + ::fabs(sample.translation.y) <= 0.020001f && + ::fabs(sample.translation.z) <= 0.030001f, "per-axis anatomical limits hold under extreme impulses"); frame = {}; @@ -147,7 +148,7 @@ void TestLimitsAndTeleport() { frame.linear_impulse = {0, -1, 0}; pose.ResetToBind(skeleton); dynamics.Update(skeleton, frame, 1.0f / 60.0f, &pose); - Check(std::fabs(dynamics.sample(0).translation.y) > 0, + Check(::fabs(dynamics.sample(0).translation.y) > 0, "region resumes simulation on the frame after a teleport"); } @@ -168,7 +169,7 @@ void TestPoseAndMorphOutput() { BodyDynamicsFrame frame; base::Vector weights; dynamics.Update(skeleton, frame, 1.0f / 60.0f, &pose, &weights); - Check(weights.size() == 1 && std::fabs(weights[0].weight - 0.15f) < 1e-6f, + Check(weights.size() == 1 && ::fabs(weights[0].weight - 0.15f) < 1e-6f, "morph bias is emitted on the rest/initialization frame"); frame.linear_impulse = {0, -1, 0}; pose.ResetToBind(skeleton); @@ -182,11 +183,11 @@ void TestPoseAndMorphOutput() { asset::MorphTarget compression; compression.name = "softCompression"; compression.name_hash = asset::MakeAssetId(compression.name).hash; - mesh.morph_targets.push_back(std::move(compression)); + mesh.morph_targets.push_back(base::move(compression)); asset::MorphTarget unrelated; unrelated.name = "expression"; unrelated.name_hash = asset::MakeAssetId(unrelated.name).hash; - mesh.morph_targets.push_back(std::move(unrelated)); + mesh.morph_targets.push_back(base::move(unrelated)); base::Vector dense; dense.push_back(0.1f); dense.push_back(0.7f); @@ -240,7 +241,7 @@ void TestModelUnitScale() { const BodyRegionSample result = dynamics.sample(0); Check(result.active, "changing the source-unit scale keeps the region active"); - Check(std::fabs(result.translation.y) > 0, + Check(::fabs(result.translation.y) > 0, "source-unit scaled simulation responds to a physical impulse"); Near((pose.translation[2].y - skeleton.bones[2].bind_translation.y) / 100.0f, result.translation.y, 1e-6f, @@ -257,9 +258,9 @@ int main() { TestPresetsAndMissingBones(); TestModelUnitScale(); if (failures == 0) { - std::printf("body_dynamics_test: all checks passed\n"); + ::printf("body_dynamics_test: all checks passed\n"); return 0; } - std::fprintf(stderr, "body_dynamics_test: %d checks failed\n", failures); + ::fprintf(stderr, "body_dynamics_test: %d checks failed\n", failures); return 1; } diff --git a/test/camera_rig_test.cc b/test/camera_rig_test.cc index eded1c30..5b9cc430 100644 --- a/test/camera_rig_test.cc +++ b/test/camera_rig_test.cc @@ -1,7 +1,8 @@ #include "scene/camera_rig.h" -#include -#include +#include +#include +#include #include "ecs/world.h" @@ -19,20 +20,20 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "camera_rig_test: FAIL: %s\n", message); + ::fprintf(stderr, "camera_rig_test: FAIL: %s\n", message); ++failures; } void Near(f32 actual, f32 expected, const char* message, f32 epsilon = 1e-4f) { - if (std::abs(actual - expected) <= epsilon) return; - std::fprintf(stderr, "camera_rig_test: FAIL: %s (got %.6f, expected %.6f)\n", message, actual, + if (::abs(actual - expected) <= epsilon) return; + ::fprintf(stderr, "camera_rig_test: FAIL: %s (got %.6f, expected %.6f)\n", message, actual, expected); ++failures; } void NearVec(const Vec3& actual, const Vec3& expected, const char* message, f32 epsilon = 1e-4f) { if (Length(actual - expected) <= epsilon) return; - std::fprintf(stderr, + ::fprintf(stderr, "camera_rig_test: FAIL: %s (got %.4f, %.4f, %.4f; expected " "%.4f, %.4f, %.4f)\n", message, actual.x, actual.y, actual.z, expected.x, expected.y, expected.z); @@ -292,9 +293,9 @@ int main() { TestFramingDampingAndDegenerateUp(); if (failures != 0) { - std::fprintf(stderr, "camera_rig_test: %d failure(s)\n", failures); + ::fprintf(stderr, "camera_rig_test: %d failure(s)\n", failures); return 1; } - std::printf("camera_rig_test: PASS\n"); + ::printf("camera_rig_test: PASS\n"); return 0; } diff --git a/test/camera_test.cc b/test/camera_test.cc index 82a34729..e2a82ef5 100644 --- a/test/camera_test.cc +++ b/test/camera_test.cc @@ -1,11 +1,13 @@ #include "scene/camera.h" #include +#include -#include -#include -#include +#include +#include +#include +#include "core/scalar.h" #include "ecs/world.h" namespace { @@ -23,13 +25,13 @@ struct alignas(64) OverAlignedComponent { void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "camera_test: FAIL: %s\n", message); + ::fprintf(stderr, "camera_test: FAIL: %s\n", message); ++failures; } void Near(f32 actual, f32 expected, const char* message, f32 epsilon = 1e-4f) { - if (std::abs(actual - expected) <= epsilon) return; - std::fprintf(stderr, "camera_test: FAIL: %s (got %.6f, expected %.6f)\n", message, actual, + if (::abs(actual - expected) <= epsilon) return; + ::fprintf(stderr, "camera_test: FAIL: %s (got %.6f, expected %.6f)\n", message, actual, expected); ++failures; } @@ -221,8 +223,8 @@ void TestLensAndOrientationInterpolation() { CameraView half = InterpolateCameraView(source, destination, 0.5f); const f32 expected_fov = - 2.0f * std::atan(std::lerp(std::tan(source.lens.fov_y * 0.5f), - std::tan(destination.lens.fov_y * 0.5f), 0.5f)); + 2.0f * ::atan(rx::Lerp(::tan(source.lens.fov_y * 0.5f), + ::tan(destination.lens.fov_y * 0.5f), 0.5f)); Near(half.lens.fov_y, expected_fov, "field of view blends in focal scale"); Near(rx::Length(CameraForward(half)), 1.0f, "slerped orientation remains normalized"); @@ -251,9 +253,9 @@ int main() { TestLensAndOrientationInterpolation(); if (failures != 0) { - std::fprintf(stderr, "camera_test: %d failure(s)\n", failures); + ::fprintf(stderr, "camera_test: %d failure(s)\n", failures); return 1; } - std::printf("camera_test: PASS\n"); + ::printf("camera_test: PASS\n"); return 0; } diff --git a/test/character_test.cc b/test/character_test.cc index 21ec8168..bd10bc2b 100644 --- a/test/character_test.cc +++ b/test/character_test.cc @@ -1,8 +1,10 @@ #include "character/character.h" -#include -#include +#include +#include +#include +#include "core/scalar.h" #include "ecs/world.h" #include "physics/physics_world.h" #include "scene/camera.h" @@ -21,13 +23,13 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "character_test: FAIL: %s\n", message); + ::fprintf(stderr, "character_test: FAIL: %s\n", message); ++failures; } void Near(f32 actual, f32 expected, const char* message, f32 epsilon = 1e-2f) { - if (std::abs(actual - expected) <= epsilon) return; - std::fprintf(stderr, "character_test: FAIL: %s (got %.4f, expected %.4f)\n", message, actual, + if (::abs(actual - expected) <= epsilon) return; + ::fprintf(stderr, "character_test: FAIL: %s (got %.4f, expected %.4f)\n", message, actual, expected); ++failures; } @@ -44,7 +46,7 @@ struct Scene { physics.AddStaticBox({0, -0.5f, 0}, {60, 0.5f, 60}); // top at y = 0 CharacterShape shape; // defaults: standing 1.8/0.3, crouched 1.2/0.3 - const f32 half = std::max(shape.standing_height * 0.5f - shape.standing_radius, 0.01f); + const f32 half = rx::Max(shape.standing_height * 0.5f - shape.standing_radius, 0.01f); const f32 total_half = half + shape.standing_radius; const Vec3 center = feet + Vec3{0, total_half, 0}; physics::CharacterId id = physics.CreateCharacter(center, shape.standing_radius, half); @@ -82,7 +84,7 @@ struct Scene { }; f32 HorizontalSpeed(const CharacterState& s) { - return std::sqrt(s.velocity.x * s.velocity.x + s.velocity.z * s.velocity.z); + return ::sqrt(s.velocity.x * s.velocity.x + s.velocity.z * s.velocity.z); } void TestGaitSpeeds() { @@ -329,7 +331,7 @@ void TestTurnSmoothingConverges() { // Movement direction with heading yaw ~1.0 rad. const f32 theta = 1.0f; - s.intent().move = {std::sin(theta), 0, -std::cos(theta)}; + s.intent().move = {::sin(theta), 0, -::cos(theta)}; s.intent().gait = CharacterGait::kRun; f32 prev = s.state().facing_yaw; @@ -362,7 +364,7 @@ void TestQuickPivotIsFaster() { move->pivot_angle = pivot_angle; s.Settle(); const f32 theta = 2.6f; // ~149 deg from the +? forward: a near reversal - s.intent().move = {std::sin(theta), 0, -std::cos(theta)}; + s.intent().move = {::sin(theta), 0, -::cos(theta)}; s.intent().gait = CharacterGait::kRun; for (int i = 0; i < steps; ++i) { StepCharacters(s.world, s.physics, kDt); @@ -467,7 +469,7 @@ void TestAnchorVerticalSmoothing() { SyncCharacterCameraAnchors(s.world); const CharacterState& st = s.state(); const f32 raw_eye = s.transform().position[1] + st.eye_height; - max_lag = std::max(max_lag, raw_eye - st.anchor_eye_y); // smoothed lags a rising step + max_lag = rx::Max(max_lag, raw_eye - st.anchor_eye_y); // smoothed lags a rising step } Check(s.transform().position[1] > 0.25f, "character climbed the step"); Check(max_lag > 0.05f, "anchor eye Y lags a sudden step-up (glide, not pop)"); @@ -491,7 +493,7 @@ void TestLandingDip() { bool landed = false; for (int i = 0; i < 400; ++i) { s.Step(1); - peak_dip = std::max(peak_dip, s.state().landing_dip); + peak_dip = rx::Max(peak_dip, s.state().landing_dip); if (s.state().grounded && i > 2) { landed = true; break; @@ -583,9 +585,9 @@ int main() { TestViewModeToggleTransition(); if (failures == 0) { - std::printf("character_test: all checks passed\n"); + ::printf("character_test: all checks passed\n"); return 0; } - std::fprintf(stderr, "character_test: %d checks failed\n", failures); + ::fprintf(stderr, "character_test: %d checks failed\n", failures); return 1; } diff --git a/test/cloth_collision_test.cc b/test/cloth_collision_test.cc index c08d509e..ecdd514d 100644 --- a/test/cloth_collision_test.cc +++ b/test/cloth_collision_test.cc @@ -1,16 +1,17 @@ +#include "core/scalar.h" #include "physics/cloth_collision.h" #include -#include -#include +#include +#include using namespace rx; namespace { int Fail(const char* what) { - std::fprintf(stderr, "cloth_collision_test FAIL: %s\n", what); + ::fprintf(stderr, "cloth_collision_test FAIL: %s\n", what); return 1; } @@ -22,8 +23,8 @@ physics::detail::ClothTopology ManualTopology(u32 vertex_count) { bool Finite(const base::Vector& values) { for (const Vec3& value : values) { - if (!std::isfinite(value.x) || !std::isfinite(value.y) || - !std::isfinite(value.z)) { + if (!::isfinite(value.x) || !::isfinite(value.y) || + !::isfinite(value.z)) { return false; } } @@ -134,7 +135,7 @@ int TestEdgeEdgeCcd() { } const f32 z2 = positions[2].z + velocities[2].z / 60.0f; const f32 z3 = positions[3].z + velocities[3].z / 60.0f; - if (!Finite(velocities) || std::min(z2, z3) < 0.015f) { + if (!Finite(velocities) || rx::Min(z2, z3) < 0.015f) { return Fail("edge crossed through edge"); } return 0; @@ -166,6 +167,6 @@ int main() { if (int rc = TestHighTangentialCcd()) return rc; if (int rc = TestEdgeEdgeCcd()) return rc; if (int rc = TestRuntimeDegenerateTriangle()) return rc; - std::printf("cloth_collision_test OK\n"); + ::printf("cloth_collision_test OK\n"); return 0; } diff --git a/test/cloth_sim_test.cc b/test/cloth_sim_test.cc index 51ffdbf4..620db206 100644 --- a/test/cloth_sim_test.cc +++ b/test/cloth_sim_test.cc @@ -1,10 +1,12 @@ #include +#include -#include -#include -#include -#include +#include +#include +#include +#include "base/containers/pair.h" +#include "base/time/time.h" #include "core/feature_registry.h" #include "core/math.h" #include "physics/physics_world.h" @@ -16,12 +18,12 @@ namespace { constexpr f32 kDt = 1.0f / 60.0f; int Fail(const char* what) { - std::fprintf(stderr, "cloth_sim_test FAIL: %s\n", what); + ::fprintf(stderr, "cloth_sim_test FAIL: %s\n", what); return 1; } bool IsFinite(const Vec3& p) { - return std::isfinite(p.x) && std::isfinite(p.y) && std::isfinite(p.z); + return ::isfinite(p.x) && ::isfinite(p.y) && ::isfinite(p.z); } struct Mesh { @@ -72,7 +74,7 @@ Mesh MakeSkirt(u32 segments, u32 rings) { const f32 angle = 6.28318530718f * static_cast(segment) / static_cast(segments); mesh.positions.push_back( - {radius * std::cos(angle), -0.7f * t, radius * std::sin(angle)}); + {radius * ::cos(angle), -0.7f * t, radius * ::sin(angle)}); } } for (u32 ring = 0; ring + 1 < rings; ++ring) { @@ -122,7 +124,7 @@ bool ReadCloth(physics::PhysicsWorld& world, physics::ClothId id, if (!world.GetClothPositions(id, positions->data(), count)) return false; for (u32 i = 0; i < positions->size(); ++i) { if (!IsFinite((*positions)[i])) { - std::fprintf(stderr, "non-finite cloth vertex %u: %g %g %g\n", i, + ::fprintf(stderr, "non-finite cloth vertex %u: %g %g %g\n", i, (*positions)[i].x, (*positions)[i].y, (*positions)[i].z); return false; } @@ -164,7 +166,7 @@ int TestCurtain(physics::PhysicsWorld& world) { const u32 a = mesh.indices[i + 0], b = mesh.indices[i + 1], c = mesh.indices[i + 2]; for (const auto edge : - {std::pair{a, b}, std::pair{b, c}, std::pair{c, a}}) { + {base::Pair{a, b}, base::Pair{b, c}, base::Pair{c, a}}) { const f32 rest_length = Length(mesh.positions[edge.first] - mesh.positions[edge.second]); if (Length(positions[edge.first] - positions[edge.second]) > @@ -232,7 +234,7 @@ int TestSkirt(physics::PhysicsWorld& world) { for (u32 ring = 1; ring < kRings; ++ring) { for (u32 segment = 0; segment < kSegments; ++segment) { const Vec3 p = positions[ring * kSegments + segment] - Vec3{1.4f, 0, 0}; - if (std::sqrt(p.x * p.x + p.z * p.z) < 0.27f) { + if (::sqrt(p.x * p.x + p.z * p.z) < 0.27f) { return Fail("skirt penetrated character capsule"); } } @@ -361,7 +363,7 @@ int TestPressureAndValidation(physics::PhysicsWorld& world) { for (const Vec3& p : positions) final_radius += Length(p - Vec3{0, 1, -1}); final_radius /= static_cast(positions.size()); if (final_radius <= initial_radius + 1.0e-6f) { - std::fprintf(stderr, "pressure radius: initial=%g final=%g\n", + ::fprintf(stderr, "pressure radius: initial=%g final=%g\n", initial_radius, final_radius); return Fail("closed pressure body did not expand"); } @@ -401,7 +403,7 @@ int TestSelfCollision(physics::PhysicsWorld& world) { lower += positions[i].z; upper += positions[i + 3].z; } - if (std::abs(upper - lower) / 3.0f < 0.025f) { + if (::abs(upper - lower) / 3.0f < 0.025f) { return Fail("self-collision did not separate overlapping panels"); } world.RemoveCloth(cloth); @@ -414,20 +416,19 @@ int main(int argc, char** argv) { InitFeatures(); physics::PhysicsWorld world; if (!world.Initialize()) return Fail("physics init (Jolt missing?)"); - if (argc == 2 && std::strcmp(argv[1], "--feature-off") == 0) { + if (argc == 2 && ::strcmp(argv[1], "--feature-off") == 0) { const Mesh mesh = MakeCurtain(3, 3, 0.1f); if (world.CreateCloth(DescFor(mesh), Mat4::Identity()) != 0) { return Fail("disabled physics.cloth feature created cloth"); } - std::printf("cloth feature gate OK\n"); + ::printf("cloth feature gate OK\n"); return 0; } auto run = [&](const char* name, auto test) { - const auto begin = std::chrono::steady_clock::now(); + const base::TimeTicks begin = base::TimeTicks::Now(); const int result = test(world); - const auto elapsed = - std::chrono::duration(std::chrono::steady_clock::now() - begin); - std::printf("%s: %.3f s\n", name, elapsed.count()); + const base::TimeDelta elapsed = base::TimeTicks::Now() - begin; + ::printf("%s: %.3f s\n", name, elapsed.InSecondsF()); return result; }; if (int rc = run("curtain", TestCurtain)) return rc; @@ -435,6 +436,6 @@ int main(int argc, char** argv) { if (int rc = run("skinning", TestSkinning)) return rc; if (int rc = run("pressure", TestPressureAndValidation)) return rc; if (int rc = run("self collision", TestSelfCollision)) return rc; - std::printf("cloth_sim_test OK\n"); + ::printf("cloth_sim_test OK\n"); return 0; } diff --git a/test/cloud_lighting_test.cc b/test/cloud_lighting_test.cc index 9f4839c9..b992da17 100644 --- a/test/cloud_lighting_test.cc +++ b/test/cloud_lighting_test.cc @@ -1,10 +1,10 @@ -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include "base/containers/vector.h" +#include "core/scalar.h" #include "render/atmosphere/clouds.h" #include "render/rhi/device.h" @@ -15,20 +15,20 @@ namespace { f32 Half(u16 bits) { if ((bits & 0x7c00u) == 0x7c00u) return INFINITY; const int exponent = (bits >> 10) & 31; - return std::ldexp(float((bits & 1023) + (exponent ? 1024 : 0)), + return ::ldexp(float((bits & 1023) + (exponent ? 1024 : 0)), exponent ? exponent - 25 : -24) * ((bits & 0x8000) ? -1.f : 1.f); } } // namespace int main() { DeviceDesc desc; - const char* backend = std::getenv("RX_RHI"); - desc.backend = backend && std::strcmp(backend, "d3d12") == 0 ? Backend::kD3D12 : Backend::kVulkan; + const char* backend = ::getenv("RX_RHI"); + desc.backend = backend && ::strcmp(backend, "d3d12") == 0 ? Backend::kD3D12 : Backend::kVulkan; desc.request_raytracing = false; desc.enable_validation = true; auto device = Device::CreateOffscreen(desc); if (!device || device->is_stub()) { - std::printf("cloud_lighting_test: SKIP, GPU unavailable\n"); + ::printf("cloud_lighting_test: SKIP, GPU unavailable\n"); return 77; } Clouds clouds; @@ -36,32 +36,32 @@ int main() { TransientPool pool(*device); int failures = 0; auto check = [&](bool ok, const char* name, double error) { - std::printf("%s: %s, error=%g\n", name, ok ? "PASS" : "FAIL", error); + ::printf("%s: %s, error=%g\n", name, ok ? "PASS" : "FAIL", error); if (!ok) ++failures; }; for (Extent2D extent : {Extent2D{32, 16}, Extent2D{37, 23}}) { - std::vector owned; + base::Vector owned; const u32 count = extent.width * extent.height; - auto input = [&](Format format, const std::vector& data) { + auto input = [&](Format format, const base::Vector& data) { GpuImage image = device->CreateImage2D(format, extent, kTextureUsageSampled | kTextureUsageTransferDst); GpuBuffer staging = device->CreateBufferWithData( - {reinterpret_cast(data.data()), data.size() * sizeof(f32)}, kBufferUsageTransferSrc); - if (!image || !staging) std::exit(1); + ByteSpan(reinterpret_cast(data.data()), data.size() * sizeof(f32)), kBufferUsageTransferSrc); + if (!image || !staging) ::exit(1); device->ImmediateSubmit([&](CommandList& cmd) { cmd.Barrier(Transition(image, ResourceState::kUndefined, ResourceState::kCopyDst)); BufferTextureCopy copy{.extent = extent}; - cmd.CopyBufferToTexture(staging, image, {©, 1}); + cmd.CopyBufferToTexture(staging, image, base::Span(©, 1)); cmd.Barrier(Transition(image, ResourceState::kCopyDst, ResourceState::kShaderReadCompute)); }); device->DestroyBuffer(staging); owned.push_back(image); return image; }; - const GpuImage black = input(Format::kRGBA32Float, std::vector(count * 4, 0)); - const GpuImage white = input(Format::kRGBA32Float, std::vector(count * 4, 1)); - const GpuImage depth = input(Format::kR32Float, std::vector(count, 1.f / 5000)); - const GpuImage foreground = input(Format::kR32Float, std::vector(count, 1)); + const GpuImage black = input(Format::kRGBA32Float, base::Vector(count * 4, 0)); + const GpuImage white = input(Format::kRGBA32Float, base::Vector(count * 4, 1)); + const GpuImage depth = input(Format::kR32Float, base::Vector(count, 1.f / 5000)); + const GpuImage foreground = input(Format::kR32Float, base::Vector(count, 1)); Clouds::Frame frame; // Parallel upward rays: world y = 1/depth. The surface at 5000 m clips // cirrus, leaving only the cumulus layer and its known input background. @@ -84,15 +84,15 @@ int main() { graph.AddPass("readback", [out](RenderGraph::PassBuilder& b) { b.Read(out, ResourceUsage::kResolveSrc); }, [](PassContext&) {}); - if (!graph.Compile(*device, pool)) std::exit(1); + if (!graph.Compile(*device, pool)) ::exit(1); device->ImmediateSubmit([&](CommandList& cmd) { - PassContext ctx{.cmd = &cmd, .device = device.get(), .graph = &graph}; + PassContext ctx{.cmd = &cmd, .device = device.Get_UseOnlyIfYouKnowWhatYouareDoing(), .graph = &graph}; graph.Execute(ctx); }); - std::vector bits(count * 4); + base::Vector bits(count * 4); if (!device->ReadbackImage(graph.image(out), ResourceState::kResolveSrc, - bits.data(), bits.size() * sizeof(u16))) std::exit(1); - std::vector pixels(bits.size()); + bits.data(), bits.size() * sizeof(u16))) ::exit(1); + base::Vector pixels(bits.size()); for (u32 i = 0; i < bits.size(); ++i) pixels[i] = Half(bits[i]); return pixels; }; @@ -113,28 +113,28 @@ int main() { const u32 i = p * 4 + c; const f32 opacity_a = 1 - (light_a[i] - dark_a[i]); const f32 opacity_b = 1 - (light_b[i] - dark_b[i]); - if (!std::isfinite(opacity_a) || !std::isfinite(opacity_b)) { + if (!::isfinite(opacity_a) || !::isfinite(opacity_b)) { error = INFINITY; continue; } if (opacity_a < .1f || opacity_b < .1f) continue; const f32 source_a = dark_a[i] / opacity_a; const f32 source_b = dark_b[i] / opacity_b; - min_source = std::min(min_source, double(std::min(source_a, source_b))); - error = std::max(error, double(std::abs(source_a - source_b) / - std::max(source_b, 1e-6f))); + min_source = rx::Min(min_source, double(rx::Min(source_a, source_b))); + error = rx::Max(error, double(::abs(source_a - source_b) / + rx::Max(source_b, 1e-6f))); ++compared; } } check(compared > count && error < .025, "Cloud source independent of density", error); - check(std::isfinite(min_source) && min_source > .1, "Cloud ambient illumination preserved", min_source); - std::printf("extent=%ux%u, compared=%u channels\n", extent.width, extent.height, compared); + check(::isfinite(min_source) && min_source > .1, "Cloud ambient illumination preserved", min_source); + ::printf("extent=%ux%u, compared=%u channels\n", extent.width, extent.height, compared); - auto expect = [&](const std::vector& pixels, f32 value, const char* name) { + auto expect = [&](const base::Vector& pixels, f32 value, const char* name) { double difference = 0; for (u32 p = 0; p < count; ++p) for (u32 c = 0; c < 3; ++c) { f32 sample = pixels[p * 4 + c]; - difference = std::isfinite(sample) ? std::max(difference, double(std::abs(sample - value))) : INFINITY; + difference = ::isfinite(sample) ? rx::Max(difference, double(::abs(sample - value))) : INFINITY; } check(difference < .001, name, difference); }; diff --git a/test/combat_test.cc b/test/combat_test.cc index 9ef1f563..a91d1f54 100644 --- a/test/combat_test.cc +++ b/test/combat_test.cc @@ -1,5 +1,6 @@ -#include -#include +#include +#include +#include #include "combat/damage.h" #include "combat/projectile.h" @@ -20,13 +21,13 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "combat_test: FAIL: %s\n", message); + ::fprintf(stderr, "combat_test: FAIL: %s\n", message); ++failures; } void Near(f32 actual, f32 expected, const char* message, f32 epsilon = 1e-3f) { - if (std::abs(actual - expected) <= epsilon) return; - std::fprintf(stderr, "combat_test: FAIL: %s (got %.4f, expected %.4f)\n", message, actual, + if (::abs(actual - expected) <= epsilon) return; + ::fprintf(stderr, "combat_test: FAIL: %s (got %.4f, expected %.4f)\n", message, actual, expected); ++failures; } @@ -609,9 +610,9 @@ int main() { TestHitRegistryLifetime(); if (failures == 0) { - std::printf("combat_test: all checks passed\n"); + ::printf("combat_test: all checks passed\n"); return 0; } - std::fprintf(stderr, "combat_test: %d checks failed\n", failures); + ::fprintf(stderr, "combat_test: %d checks failed\n", failures); return 1; } diff --git a/test/command_bridge_test.cc b/test/command_bridge_test.cc index 35074101..4437f56c 100644 --- a/test/command_bridge_test.cc +++ b/test/command_bridge_test.cc @@ -8,14 +8,17 @@ #include #include -#include -#include -#include -#include -#include +#include +#include #include "authoring/command_bridge.h" #include "authoring/command_endpoint.h" +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/optional.h" +#include "base/strings/xstring.h" +#include "core/format.h" #include "ecs/world.h" #include "rpc/rpc_message.h" #include "scene/components.h" @@ -32,7 +35,7 @@ int g_failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++g_failures; \ } \ } while (0) @@ -52,7 +55,7 @@ struct Rig { script::ScriptArena scratch; script::HandlerRegistry commands; script::HandlerContext ctx; - std::optional bridge; // built after the registry is filled + base::Optional bridge; // built after the registry is filled Rig() { scene::SetupSceneCommands(commands); @@ -62,9 +65,9 @@ struct Rig { bridge.emplace(commands, ctx); } - authoring::CommandBridge::Reply Call(std::string name, rpc::RpcArgs args) { + authoring::CommandBridge::Reply Call(base::String name, rpc::RpcArgs args) { const rpc::RpcContext local{authoring::kLocalSender, /*from_server=*/false}; - return bridge->Invoke(local, rpc::RpcCall{std::move(name), std::move(args)}); + return bridge->Invoke(local, rpc::RpcCall{base::move(name), base::move(args)}); } }; @@ -75,7 +78,7 @@ void TestMarshalling() { // World.Spawn(symbol, vec3, float): 3 params, 5 wire args. authoring::CommandBridge::Reply reply = - rig.Call("World.Spawn", {rpc::RpcValue(std::string("crate")), rpc::RpcValue(1.0), + rig.Call("World.Spawn", {rpc::RpcValue(base::String("crate")), rpc::RpcValue(1.0), rpc::RpcValue(2.0), rpc::RpcValue(3.0), rpc::RpcValue(rx::i64(2))}); CHECK(reply.ok); CHECK(reply.values.size() == 1); @@ -98,7 +101,7 @@ void TestMarshalling() { // A string round trip: in as a borrowed view, out of the scratch arena and // copied into the reply, so resetting the arena cannot invalidate it. - rig.Call("World.SetName", {rpc::RpcValue(entity), rpc::RpcValue(std::string("Crate 01"))}); + rig.Call("World.SetName", {rpc::RpcValue(entity), rpc::RpcValue(base::String("Crate 01"))}); reply = rig.Call("World.GetName", {rpc::RpcValue(entity)}); rig.scratch.Reset(); CHECK(reply.ok && reply.values.size() == 1); @@ -123,24 +126,24 @@ void TestRejection() { // Too few args for the signature. authoring::CommandBridge::Reply reply = rig.Call("World.Teleport", {rpc::RpcValue(rx::i64(1))}); CHECK(!reply.ok); - CHECK(reply.error.find("expects 4 arg(s) for (entity, vec3)") != std::string::npos); + CHECK(reply.error.find("expects 4 arg(s) for (entity, vec3)") != base::String::npos); // Right arity, wrong type: a float where an entity id is wanted is a caller // bug, and truncating it would move some other entity. reply = rig.Call("World.Teleport", {rpc::RpcValue(1.5), rpc::RpcValue(0.0), rpc::RpcValue(0.0), rpc::RpcValue(0.0)}); CHECK(!reply.ok); - CHECK(reply.error.find("arg 0 expects entity, got float") != std::string::npos); + CHECK(reply.error.find("arg 0 expects entity, got float") != base::String::npos); // A string where a number is wanted, inside the vec3 expansion. reply = rig.Call("World.Teleport", {rpc::RpcValue(rx::i64(1)), rpc::RpcValue(0.0), - rpc::RpcValue(std::string("up")), rpc::RpcValue(0.0)}); + rpc::RpcValue(base::String("up")), rpc::RpcValue(0.0)}); CHECK(!reply.ok); - CHECK(reply.error.find("arg 2 expects float") != std::string::npos); + CHECK(reply.error.find("arg 2 expects float") != base::String::npos); reply = rig.Call("World.DoesNotExist", {}); CHECK(!reply.ok); - CHECK(reply.error.find("unknown command") != std::string::npos); + CHECK(reply.error.find("unknown command") != base::String::npos); } void TestTrustGate() { @@ -155,7 +158,7 @@ void TestTrustGate() { // A remote game peer: any peer id the net path could attribute a packet to. authoring::CommandBridge::Reply reply = rig.bridge->Invoke(rpc::RpcContext{7, false}, move); CHECK(!reply.ok); - CHECK(reply.error.find("local-endpoint only") != std::string::npos); + CHECK(reply.error.find("local-endpoint only") != base::String::npos); // The host, on a client build. Also not the local authoring endpoint. reply = rig.bridge->Invoke(rpc::RpcContext{authoring::kLocalSender, true}, move); @@ -174,10 +177,10 @@ void TestTrustGate() { void TestEndpoint() { Rig rig; authoring::CommandEndpoint endpoint; - const std::string path = "/tmp/rx_command_test_" + std::to_string(::getpid()) + ".sock"; - std::string error; + const base::String path = "/tmp/rx_command_test_" + rx::ToString(::getpid()) + ".sock"; + base::String error; if (!endpoint.Start(path, &error)) { - std::printf("FAIL endpoint start: %s\n", error.c_str()); + ::printf("FAIL endpoint start: %s\n", error.c_str()); ++g_failures; return; } @@ -189,7 +192,7 @@ void TestEndpoint() { sockaddr_un addr{}; addr.sun_family = AF_UNIX; - std::memcpy(addr.sun_path, path.c_str(), path.size()); + base::MemCopy(addr.sun_path, path.c_str(), path.size()); const int fd = ::socket(AF_UNIX, SOCK_STREAM, 0); CHECK(fd >= 0); CHECK(::connect(fd, reinterpret_cast(&addr), sizeof(addr)) == 0); @@ -199,8 +202,8 @@ void TestEndpoint() { const rpc::RpcCall call{"World.Teleport", {rpc::RpcValue(static_cast(e.index)), rpc::RpcValue(1.0), rpc::RpcValue(2.0), rpc::RpcValue(3.0)}}; - const std::vector payload = rpc::EncodeCall(call); - std::vector frame; + const base::Vector payload = rpc::EncodeCall(call); + base::Vector frame; for (int i = 0; i < 4; ++i) frame.push_back(static_cast(static_cast(payload.size()) >> (8 * i))); frame.insert(frame.end(), payload.begin(), payload.end()); @@ -212,9 +215,9 @@ void TestEndpoint() { CHECK(::read(fd, header, 4) == 4); const u32 length = u32(header[0]) | u32(header[1]) << 8 | u32(header[2]) << 16 | u32(header[3]) << 24; - std::vector reply_bytes(length); + base::Vector reply_bytes(length); CHECK(::read(fd, reply_bytes.data(), length) == static_cast(length)); - std::optional reply = rpc::DecodeCall(reply_bytes.data(), reply_bytes.size()); + base::Optional reply = rpc::DecodeCall(reply_bytes.data(), reply_bytes.size()); CHECK(reply && reply->name == "ok"); CHECK(rig.world.Get(e)->position[2] == 3.0f); @@ -247,9 +250,9 @@ int main() { TestTrustGate(); TestEndpoint(); if (g_failures == 0) { - std::printf("command_bridge_test: all checks passed\n"); + ::printf("command_bridge_test: all checks passed\n"); return 0; } - std::printf("command_bridge_test: %d failure(s)\n", g_failures); + ::printf("command_bridge_test: %d failure(s)\n", g_failures); return 1; } diff --git a/test/compaction_test.cc b/test/compaction_test.cc index 3ec607af..63bcc82c 100644 --- a/test/compaction_test.cc +++ b/test/compaction_test.cc @@ -10,11 +10,12 @@ // Skips cleanly (exit 0) when no Vulkan driver is present (null backend) or the // adapter has no ray tracing. Run under vkrun to exercise the real GPU path. -#include -#include -#include -#include +#include +#include +#include +#include "base/memory/mem_ops.h" +#include "base/memory/unique_pointer.h" #include "render/rhi/command_list.h" #include "render/rhi/device.h" @@ -23,7 +24,7 @@ using namespace rx::render; namespace { int Fail(const char* msg) { - std::fprintf(stderr, "compaction_test: FAIL: %s\n", msg); + ::fprintf(stderr, "compaction_test: FAIL: %s\n", msg); return 1; } @@ -33,24 +34,24 @@ u64 AlignUp(u64 v, u64 a) { return (v + a - 1) & ~(a - 1); } int main() { DeviceDesc desc; - const char* rhi = std::getenv("RX_RHI"); - desc.backend = (rhi && std::strcmp(rhi, "d3d12") == 0) ? Backend::kD3D12 : Backend::kVulkan; + const char* rhi = ::getenv("RX_RHI"); + desc.backend = (rhi && ::strcmp(rhi, "d3d12") == 0) ? Backend::kD3D12 : Backend::kVulkan; desc.request_raytracing = true; - desc.enable_validation = std::getenv("RX_VALIDATION") != nullptr; - std::unique_ptr device = Device::CreateOffscreen(desc); + desc.enable_validation = ::getenv("RX_VALIDATION") != nullptr; + base::UniquePointer device = Device::CreateOffscreen(desc); if (!device) return Fail("CreateOffscreen returned null"); if (device->is_stub()) { - std::printf("compaction_test: no %s driver, skipping (null backend)\n", + ::printf("compaction_test: no %s driver, skipping (null backend)\n", BackendName(desc.backend)); return 0; } if (!device->caps().raytracing) { - std::printf("compaction_test: adapter '%s' has no ray tracing, skipping\n", + ::printf("compaction_test: adapter '%s' has no ray tracing, skipping\n", device->caps().adapter_name.c_str()); return 0; } - std::printf("compaction_test: device '%s'\n", device->caps().adapter_name.c_str()); + ::printf("compaction_test: device '%s'\n", device->caps().adapter_name.c_str()); // a single opaque triangle in a host-visible, AS-build-input buffer const f32 verts[9] = {-0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 0.5f, 0.0f}; @@ -58,7 +59,7 @@ int main() { kBufferUsageAccelBuildInput | kBufferUsageDeviceAddress, /*host_visible=*/true); if (!vbo || !vbo.mapped || vbo.address == 0) return Fail("vertex buffer creation failed"); - std::memcpy(vbo.mapped, verts, sizeof(verts)); + base::MemCopy(vbo.mapped, verts, sizeof(verts)); AccelTriangles tri{.vertex_address = vbo.address, .vertex_stride = 3 * sizeof(f32), @@ -66,11 +67,11 @@ int main() { .vertex_format = Format::kRGB32Float, .index_count = 0, // non-indexed soup .opaque = true}; - BlasBuildDesc build_desc{.geometries = {&tri, 1}, .fast_trace = true, .allow_compaction = true}; + BlasBuildDesc build_desc{.geometries = base::Span(&tri, 1), .fast_trace = true, .allow_compaction = true}; AccelSizes sizes = device->GetBlasSizes(build_desc); if (sizes.accel_bytes == 0) return Fail("GetBlasSizes returned 0"); - std::printf("compaction_test: original blas = %llu bytes, scratch = %llu bytes\n", + ::printf("compaction_test: original blas = %llu bytes, scratch = %llu bytes\n", (unsigned long long)sizes.accel_bytes, (unsigned long long)sizes.scratch_bytes); AccelStructHandle fat = device->CreateAccelStruct(AccelStructType::kBlas, sizes.accel_bytes); @@ -95,12 +96,12 @@ int main() { // Non-blocking poll: legal to return false while the frame is still in flight. rx::u64 compacted = 0; const bool ready_before = device->GetCompactedSizes(query, &compacted, 1); - std::printf("compaction_test: poll before wait -> %s\n", ready_before ? "ready" : "not ready"); + ::printf("compaction_test: poll before wait -> %s\n", ready_before ? "ready" : "not ready"); device->WaitIdle(); // the frame's fence has now signalled if (!device->GetCompactedSizes(query, &compacted, 1)) return Fail("GetCompactedSizes false after the fence signalled"); - std::printf("compaction_test: compacted blas = %llu bytes (%.1f%% of original)\n", + ::printf("compaction_test: compacted blas = %llu bytes (%.1f%% of original)\n", (unsigned long long)compacted, 100.0 * (double)compacted / (double)sizes.accel_bytes); if (compacted == 0) return Fail("compacted size is 0"); @@ -125,7 +126,7 @@ int main() { GpuBuffer instances = device->CreateBuffer( sizeof(TlasInstance), kBufferUsageAccelBuildInput | kBufferUsageDeviceAddress, true); if (!instances || !instances.mapped) return Fail("instance buffer creation failed"); - std::memcpy(instances.mapped, &inst, sizeof(inst)); + base::MemCopy(instances.mapped, &inst, sizeof(inst)); AccelSizes tlas_sizes = device->GetTlasSizes(1); AccelStructHandle tlas = device->CreateAccelStruct(AccelStructType::kTlas, tlas_sizes.accel_bytes); @@ -136,13 +137,13 @@ int main() { device->ImmediateSubmit( [&](CommandList& c) { c.BuildTlas(tlas, instances, 1, tlas_scratch); }); device->WaitIdle(); - std::printf("compaction_test: built tlas over the compacted blas (ray_query)\n"); + ::printf("compaction_test: built tlas over the compacted blas (ray_query)\n"); device->DestroyAccelStruct(tlas); device->DestroyBuffer(tlas_scratch); device->DestroyBuffer(instances); } else { - std::printf("compaction_test: no ray_query; proved compacted sizes + copy only\n"); + ::printf("compaction_test: no ray_query; proved compacted sizes + copy only\n"); } // Retire the fat BLAS through the frame-safe graveyard; drained at teardown. @@ -153,6 +154,6 @@ int main() { device->DestroyBuffer(scratch); device->DestroyBuffer(vbo); - std::printf("compaction_test: PASS\n"); + ::printf("compaction_test: PASS\n"); return 0; } diff --git a/test/cull_bounds_test.cc b/test/cull_bounds_test.cc index b19cf5a1..b5256fa3 100644 --- a/test/cull_bounds_test.cc +++ b/test/cull_bounds_test.cc @@ -15,11 +15,12 @@ // default vulkan). Skips cleanly (exit 0) when no driver is present; run under // vkrun for the real GPU path. -#include -#include -#include -#include +#include +#include +#include +#include "base/memory/mem_ops.h" +#include "base/memory/unique_pointer.h" #include "core/math.h" #include "render/pipeline/gpu_cull.h" #include "render/rhi/command_list.h" @@ -35,12 +36,12 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "cull_bounds_test: FAIL: %s\n", message); + ::fprintf(stderr, "cull_bounds_test: FAIL: %s\n", message); ++failures; } int Fail(const char* message) { - std::fprintf(stderr, "cull_bounds_test: FAIL: %s\n", message); + ::fprintf(stderr, "cull_bounds_test: FAIL: %s\n", message); return 1; } @@ -70,17 +71,17 @@ Mat4 RotatedScale(f32 radians, const Vec3& scale, f32 tx) { int main() { DeviceDesc desc; - const char* rhi = std::getenv("RX_RHI"); - desc.backend = (rhi && std::strcmp(rhi, "d3d12") == 0) ? Backend::kD3D12 : Backend::kVulkan; - desc.enable_validation = std::getenv("RX_VALIDATION") != nullptr; - std::unique_ptr device = Device::CreateOffscreen(desc); + const char* rhi = ::getenv("RX_RHI"); + desc.backend = (rhi && ::strcmp(rhi, "d3d12") == 0) ? Backend::kD3D12 : Backend::kVulkan; + desc.enable_validation = ::getenv("RX_VALIDATION") != nullptr; + base::UniquePointer device = Device::CreateOffscreen(desc); if (!device) return Fail("CreateOffscreen returned null"); if (device->is_stub()) { - std::printf("cull_bounds_test: no %s driver, skipping (null backend)\n", + ::printf("cull_bounds_test: no %s driver, skipping (null backend)\n", BackendName(desc.backend)); return 0; } - std::printf("cull_bounds_test: device '%s'\n", device->caps().adapter_name.c_str()); + ::printf("cull_bounds_test: device '%s'\n", device->caps().adapter_name.c_str()); // Same set layout as GpuCull::Initialize. PipelineHandle pipeline = device->CreateComputePipeline({ @@ -138,7 +139,7 @@ int main() { for (u32 i = 0; i < kInstances; ++i) cmd_data[i] = {.index_count = 3, .instance_count = 1}; *static_cast(counts.mapped) = 0; const Mat4 identity = Mat4::Identity(); - std::memcpy(reproject.mapped, &identity, sizeof(identity)); + base::MemCopy(reproject.mapped, &identity, sizeof(identity)); CullPush push{}; // Plane 0 is the one under test: inside is x <= 0, so an instance at x = t @@ -148,7 +149,7 @@ int main() { {0, 1, 0, 1000}, {0, -1, 0, 1000}, {0, 0, 1, 1000}}; - std::memcpy(push.planes, planes, sizeof(planes)); + base::MemCopy(push.planes, planes, sizeof(planes)); push.misc[0] = kInstances; push.misc[1] = 1; // frustum on push.misc[2] = 0; // occlusion off: no hi-z to bind @@ -166,7 +167,7 @@ int main() { const u32 kept0 = cmd_data[0].instance_count; const u32 kept1 = cmd_data[1].instance_count; const u32 kept2 = cmd_data[2].instance_count; - std::printf("cull_bounds_test: instanceCount = {%u, %u, %u}, visible = %u\n", kept0, kept1, + ::printf("cull_bounds_test: instanceCount = {%u, %u, %u}, visible = %u\n", kept0, kept1, kept2, *static_cast(counts.mapped)); Check(kept0 == 1, "rotated non-uniform instance kept (column radius 4 clears the 3.4 plane)"); @@ -181,6 +182,6 @@ int main() { device->DestroyBuffer(counts); device->DestroyBuffer(reproject); - if (failures == 0) std::printf("cull_bounds_test: all checks passed\n"); + if (failures == 0) ::printf("cull_bounds_test: all checks passed\n"); return failures; } diff --git a/test/decal_bake_test.cc b/test/decal_bake_test.cc index 5fd7ff48..7cc1e220 100644 --- a/test/decal_bake_test.cc +++ b/test/decal_bake_test.cc @@ -8,14 +8,15 @@ // the projector math at the top is pure CPU and always runs. Run under vkrun to // exercise the GPU path. -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include "asset/mesh.h" +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "base/memory/unique_pointer.h" #include "render/core/render_graph.h" #include "render/rhi/command_list.h" #include "render/rhi/device.h" @@ -31,7 +32,7 @@ int failures = 0; void Check(bool ok, const char* what) { if (ok) return; - std::fprintf(stderr, "decal_bake_test: FAIL: %s\n", what); + ::fprintf(stderr, "decal_bake_test: FAIL: %s\n", what); ++failures; } @@ -41,7 +42,7 @@ Vec3 ToDecalSpace(const Decal& d, const Vec3& world) { d.row2[0] * world.x + d.row2[1] * world.y + d.row2[2] * world.z + d.row2[3]}; } -bool Near(f32 a, f32 b) { return std::fabs(a - b) < 1e-4f; } +bool Near(f32 a, f32 b) { return ::fabs(a - b) < 1e-4f; } // A unit quad in the XZ plane, uv0 covering the full 0..1 chart. World x maps to // u and world z to v, so a projector at the origin lands in the middle of the @@ -58,9 +59,9 @@ GpuMesh CreateQuad(Device& device, f32 udim_u = 0) { const u32 indices[6] = {0, 1, 2, 0, 2, 3}; GpuMesh mesh; mesh.vertices = device.CreateBufferWithData( - {reinterpret_cast(vertices), sizeof(vertices)}, kBufferUsageVertex); + rx::ByteSpan(reinterpret_cast(vertices), sizeof(vertices)), kBufferUsageVertex); mesh.indices = device.CreateBufferWithData( - {reinterpret_cast(indices), sizeof(indices)}, kBufferUsageIndex); + rx::ByteSpan(reinterpret_cast(indices), sizeof(indices)), kBufferUsageIndex); mesh.index_count = 6; mesh.vertex_count = 4; return mesh; @@ -84,7 +85,7 @@ void RunBake(Device& device, DecalBaker& baker, TransientPool& pool, const DecalBaker::Target& target, u64 frame_index, TextureView source) { RenderGraph graph; pool.BeginFrame(); - baker.AddToGraph(graph, {&target, 1}, 0, frame_index, source, source); + baker.AddToGraph(graph, base::Span(&target, 1), 0, frame_index, source, source); CommandList* cmd = device.BeginFrame(0); if (!cmd || !graph.Compile(device, pool)) { Check(false, "frame and graph setup"); @@ -108,12 +109,12 @@ int main() { "the projector centre is the origin of decal space"); // Half the FULL width along a plane axis is the box edge. const Vec3 edge = ToDecalSpace(d, {2, 3, 5}); - Check(Near(std::fabs(edge.x) + std::fabs(edge.y), 1.0f) && Near(edge.z, 0), + Check(Near(::fabs(edge.x) + ::fabs(edge.y), 1.0f) && Near(edge.z, 0), "a point at half the width sits on the box edge"); const Vec3 above = ToDecalSpace(d, {2, 3.5f, 4}); Check(Near(above.z, 1.0f), "the box depth runs along the surface normal"); const Vec3 outside = ToDecalSpace(d, {5, 3, 4}); - Check(std::fabs(outside.x) > 1.0f, "a point past the box falls outside"); + Check(::fabs(outside.x) > 1.0f, "a point past the box falls outside"); } DeviceDesc desc; @@ -121,17 +122,17 @@ int main() { desc.request_raytracing = false; // RX_VALIDATION=1 runs the bake through the Vulkan validation layers, which // is where the pass's hand-written image barriers get checked. - desc.enable_validation = std::getenv("RX_VALIDATION") != nullptr; - std::unique_ptr device = Device::CreateOffscreen(desc); + desc.enable_validation = ::getenv("RX_VALIDATION") != nullptr; + base::UniquePointer device = Device::CreateOffscreen(desc); if (!device) { - std::fprintf(stderr, "decal_bake_test: FAIL: CreateOffscreen returned null\n"); + ::fprintf(stderr, "decal_bake_test: FAIL: CreateOffscreen returned null\n"); return 1; } if (device->is_stub()) { - std::printf("decal_bake_test: no vulkan driver, skipping the gpu half\n"); + ::printf("decal_bake_test: no vulkan driver, skipping the gpu half\n"); return failures == 0 ? 0 : 1; } - std::printf("decal_bake_test: device '%s'\n", device->caps().adapter_name.c_str()); + ::printf("decal_bake_test: device '%s'\n", device->caps().adapter_name.c_str()); // One tile only: the second receiver has to evict the first, which is exactly // the path the journal exists for. @@ -140,7 +141,7 @@ int main() { bd.atlas_size = 64; bd.tile_size = 64; if (!baker.Initialize(*device, bd)) { - std::fprintf(stderr, "decal_bake_test: FAIL: baker initialize\n"); + ::fprintf(stderr, "decal_bake_test: FAIL: baker initialize\n"); return 1; } Check(baker.stats().tile_capacity == 1, "a 64/64 atlas holds exactly one tile"); @@ -148,7 +149,7 @@ int main() { GpuMesh quad = CreateQuad(*device); GpuImage source = CreateWhiteSource(*device); if (!quad.vertices || !quad.indices || !source) { - std::fprintf(stderr, "decal_bake_test: FAIL: test resource creation\n"); + ::fprintf(stderr, "decal_bake_test: FAIL: test resource creation\n"); return 1; } TransientPool pool(*device); @@ -158,12 +159,12 @@ int main() { Check(first != 0 && second != 0 && first != second, "receivers get distinct handles"); Check(baker.tile_slot(first) == 0, "a receiver holds no tile before its first stamp"); - std::vector pixels(static_cast(bd.atlas_size) * bd.atlas_size * 4); + base::Vector pixels(static_cast(bd.atlas_size) * bd.atlas_size * 4); auto coverage_at = [&](u32 x, u32 y) -> u32 { return pixels[(static_cast(y) * bd.atlas_size + x) * 4 + 3]; }; auto read_atlas = [&] { - std::memset(pixels.data(), 0, pixels.size()); + base::MemSet(pixels.data(), 0, pixels.size()); Check(device->ReadbackImage(baker.albedo_atlas(), ResourceState::kShaderReadFragment, pixels.data(), pixels.size()), "reading the layer atlas back"); @@ -195,7 +196,7 @@ int main() { read_atlas(); const u32 mid = bd.tile_size / 2; - std::printf("decal_bake_test: centre coverage %u, corner coverage %u\n", coverage_at(mid, mid), + ::printf("decal_bake_test: centre coverage %u, corner coverage %u\n", coverage_at(mid, mid), coverage_at(2, 2)); Check(coverage_at(mid, mid) > 240, "the projector covers the middle of the tile"); Check(coverage_at(2, 2) == 0, "the tile corner is outside the projector"); @@ -216,7 +217,7 @@ int main() { RunBake(*device, baker, pool, target, 3, source.view); Check(baker.tile_slot(first) == 1, "the returning receiver reclaimed the tile"); read_atlas(); - std::printf("decal_bake_test: rebaked centre coverage %u\n", coverage_at(mid, mid)); + ::printf("decal_bake_test: rebaked centre coverage %u\n", coverage_at(mid, mid)); Check(coverage_at(mid, mid) > 240, "the journal replayed the decal into the fresh tile"); Check(coverage_at(2, 2) == 0, "the reclaimed tile was cleared of the other receiver"); @@ -276,12 +277,12 @@ int main() { } RenderGraph graph; pool.BeginFrame(); - budget.AddToGraph(graph, {t3, 2}, 0, 1, source.view, source.view); + budget.AddToGraph(graph, base::Span(t3, 2), 0, 1, source.view, source.view); CommandList* cmd = device->BeginFrame(0); if (cmd && graph.Compile(*device, pool)) { PassContext ctx; ctx.cmd = cmd; - ctx.device = device.get(); + ctx.device = device.Get_UseOnlyIfYouKnowWhatYouareDoing(); device->SubmitFrame(graph.Execute(ctx)); device->WaitIdle(); } @@ -323,7 +324,7 @@ int main() { t2.mesh = &quad2; RunBake(*device, burst, pool, t2, 1, source.view); - std::vector tile(256ull * 256 * 4); + base::Vector tile(256ull * 256 * 4); Check(device->ReadbackImage(burst.albedo_atlas(), ResourceState::kShaderReadFragment, tile.data(), tile.size()), "reading the burst atlas back"); @@ -338,7 +339,7 @@ int main() { if (first_landed == kSpots) first_landed = i; } } - std::printf("decal_bake_test: burst of %u with journal_limit 8 -> %u landed, first index %u\n", + ::printf("decal_bake_test: burst of %u with journal_limit 8 -> %u landed, first index %u\n", kSpots, landed, first_landed); Check(landed == bd2.journal_limit, "a burst keeps exactly journal_limit stamps"); Check(first_landed == kSpots - bd2.journal_limit, "the OLDEST stamps are the ones dropped"); @@ -369,7 +370,7 @@ int main() { baker.SetReceiverUv(shifted, 1.0f, 1.0f, -2.0f, 0.0f); RunBake(*device, baker, pool, target, 6, source.view); read_atlas(); - std::printf("decal_bake_test: udim centre coverage %u\n", coverage_at(mid, mid)); + ::printf("decal_bake_test: udim centre coverage %u\n", coverage_at(mid, mid)); Check(coverage_at(mid, mid) > 240, "biasing onto the uv tile bakes the decal"); Check(coverage_at(2, 2) == 0, "the tile corner is still outside the projector"); baker.ReleaseReceiver(shifted); @@ -381,6 +382,6 @@ int main() { device->DestroyBuffer(quad.vertices); device->DestroyBuffer(quad.indices); - std::printf("decal_bake_test: %s\n", failures == 0 ? "PASS" : "FAIL"); + ::printf("decal_bake_test: %s\n", failures == 0 ? "PASS" : "FAIL"); return failures == 0 ? 0 : 1; } diff --git a/test/ecs_test.cc b/test/ecs_test.cc index 55e34829..68546d84 100644 --- a/test/ecs_test.cc +++ b/test/ecs_test.cc @@ -2,13 +2,15 @@ // exact-size column resize was O(n^2) and relocated rows bitwise), non-POD // components with interior pointers (bitwise relocation would corrupt them), // archetype transitions, swap-remove churn and chunk reclamation. -#include -#include -#include -#include -#include +#include +#include +#include "base/containers/vector.h" +#include "base/memory/move.h" +#include "base/strings/xstring.h" +#include "core/format.h" #include "core/memory/chunk_pool.h" +#include "core/scalar.h" #include "ecs/world.h" namespace { @@ -18,7 +20,7 @@ int g_failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++g_failures; \ } \ } while (0) @@ -40,12 +42,12 @@ struct Tag { // self pointing at the stale block. struct SelfRef { SelfRef* self; - std::string payload; + base::String payload; int id; - explicit SelfRef(int i) : self(this), payload("payload-" + std::to_string(i)), id(i) {} + explicit SelfRef(int i) : self(this), payload("payload-" + rx::ToString(i)), id(i) {} SelfRef(SelfRef&& other) noexcept - : self(this), payload(std::move(other.payload)), id(other.id) {} + : self(this), payload(base::move(other.payload)), id(other.id) {} bool valid() const { return self == this; } }; @@ -83,7 +85,7 @@ void TestSpawnStormAndIteration() { rx::ecs::World world; constexpr int kCount = 20000; // thousands of rows -> many chunks per archetype - std::vector entities; + base::Vector entities; entities.reserve(kCount); for (int i = 0; i < kCount; ++i) { rx::ecs::Entity entity = world.Create(); @@ -114,7 +116,7 @@ void TestNonPodRelocation() { rx::ecs::World world; constexpr int kCount = 5000; - std::vector entities; + base::Vector entities; for (int i = 0; i < kCount; ++i) { rx::ecs::Entity entity = world.Create(); world.Add(entity, SelfRef(i)); @@ -123,7 +125,7 @@ void TestNonPodRelocation() { int valid = 0; world.Each([&](rx::ecs::Entity, SelfRef& ref) { - if (ref.valid() && ref.payload == "payload-" + std::to_string(ref.id)) ++valid; + if (ref.valid() && ref.payload == "payload-" + rx::ToString(ref.id)) ++valid; }); CHECK(valid == kCount); @@ -161,7 +163,7 @@ void TestChunkReclamation() { const size_t free_before = rx::mem::GlobalChunkPool().stats().free_chunks; { rx::ecs::World world; - std::vector entities; + base::Vector entities; for (int i = 0; i < 10000; ++i) { rx::ecs::Entity entity = world.Create(); world.Add(entity, Position{}); @@ -202,7 +204,7 @@ void TestCreateBatchFillsColumnRuns() { // Enough rows to cross several 16 KiB chunks for a 24-byte row. constexpr rx::u32 kCount = 5000; - std::vector created; + base::Vector created; world.CreateBatch(signature, kCount, [&](const rx::ecs::EntityBatch& batch) { CHECK(batch.count() == kCount); rx::u32 covered = 0; @@ -222,7 +224,7 @@ void TestCreateBatchFillsColumnRuns() { // optimization: the caller memcpys `run` rows from one pointer, so a run // that overstates the chunk writes into the next one. const rx::u32 rows_per_chunk = 16 * 1024 / (sizeof(Position) + sizeof(Velocity)); - CHECK(run == std::min(rows_per_chunk - covered % rows_per_chunk, batch.count() - covered)); + CHECK(run == rx::Min(rows_per_chunk - covered % rows_per_chunk, batch.count() - covered)); for (rx::u32 i = 0; i < run; ++i) { new (positions + i) Position{static_cast(covered + i), 0, 0}; new (velocities + i) Velocity{0, static_cast(covered + i), 0}; @@ -262,7 +264,7 @@ void TestCreateBatchFillsColumnRuns() { // column pointers have to be offset by the rows already there. Filling only // the second batch and reading back both is what catches an implementation // that ignores where the batch begins. - std::vector second; + base::Vector second; world.CreateBatch(signature, 700, [&](const rx::ecs::EntityBatch& batch) { rx::u32 covered = 0; while (covered < batch.count()) { @@ -392,9 +394,9 @@ int main() { TestCreateBatchFillsColumnRuns(); TestCreateBatchReusesSlotsAndSkipsEmpty(); if (g_failures) { - std::fprintf(stderr, "ecs_test: %d failure(s)\n", g_failures); + ::fprintf(stderr, "ecs_test: %d failure(s)\n", g_failures); return 1; } - std::puts("ecs_test: ok"); + ::puts("ecs_test: ok"); return 0; } diff --git a/test/edit_test.cc b/test/edit_test.cc index dbb017a6..a9557de2 100644 --- a/test/edit_test.cc +++ b/test/edit_test.cc @@ -3,17 +3,17 @@ // world-transform composition. No GPU, no game assets. Exits non-zero on the // first failure so it slots into ctest. -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include #include "asset/asset_database.h" #include "asset/asset_id.h" #include "asset/vfs.h" +#include "base/algorithm.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" #include "core/math.h" #include "ecs/world.h" #include "edit/hierarchy.h" @@ -33,12 +33,12 @@ int failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++failures; \ } \ } while (0) -#define CHECK_NEAR(a, b, eps) CHECK(std::abs((a) - (b)) <= (eps)) +#define CHECK_NEAR(a, b, eps) CHECK(::abs((a) - (b)) <= (eps)) // A component exercising every PropType. struct TestAll { @@ -52,7 +52,7 @@ struct TestAll { f32 v4[4] = {}; Quat q{}; f32 col[4] = {}; - std::string s; + base::String s; asset::AssetId aid; ecs::Entity ent; }; @@ -75,7 +75,7 @@ void RegisterTestComponent() { .Prop("ent", &TestAll::ent); } -const PropDesc* FindProp(const ComponentDesc& c, std::string_view name) { +const PropDesc* FindProp(const ComponentDesc& c, base::StringRef name) { for (u32 i = 0; i < c.prop_count; ++i) if (name == c.props[i].name) return &c.props[i]; return nullptr; @@ -149,7 +149,7 @@ void TestReflection() { // ComponentsOn reports the component; removal drops it. auto on = ComponentsOn(world, e); - CHECK(std::find(on.begin(), on.end(), desc) != on.end()); + CHECK(base::Find(on.begin(), on.end(), desc) != on.end()); CHECK(RemoveComponentByDesc(world, e, *desc)); CHECK(!world.HasRaw(e, desc->id)); } @@ -182,7 +182,7 @@ bool DeepEqual(ecs::World& wa, ecs::Entity a, ecs::World& wb, ecs::Entity b) { if (va.b != vb.b) return false; } else { for (int k = 0; k < 4; ++k) - if (std::abs(va.f[k] - vb.f[k]) > 1e-5f) return false; + if (::abs(va.f[k] - vb.f[k]) > 1e-5f) return false; } } } @@ -190,8 +190,7 @@ bool DeepEqual(ecs::World& wa, ecs::Entity a, ecs::World& wb, ecs::Entity b) { } void TestSceneRoundTrip() { - namespace fs = std::filesystem; - fs::path path = fs::temp_directory_path() / "rx_edit_roundtrip.rxscene"; + base::String path = rx::fs::Join(rx::fs::TempDirectory(), "rx_edit_roundtrip.rxscene"); // A parent with a named, renderable child that references the parent. ecs::World src; @@ -223,17 +222,17 @@ void TestSceneRoundTrip() { src.Add(transient, scene::Name{"Editor preview"}); src.Add(transient, scene::Transient{}); - std::string err; - CHECK(SaveScene(src, path.string(), &err)); - if (!err.empty()) std::printf("save error: %s\n", err.c_str()); + base::String err; + CHECK(SaveScene(src, path, &err)); + if (!err.empty()) ::printf("save error: %s\n", err.c_str()); // Reload into a fresh world. asset::Vfs vfs; asset::AssetDatabase db(vfs); ecs::World dst; err.clear(); - CHECK(LoadScene(dst, db, path.string(), &err)); - if (!err.empty()) std::printf("load error: %s\n", err.c_str()); + CHECK(LoadScene(dst, db, path, &err)); + if (!err.empty()) ::printf("load error: %s\n", err.c_str()); // Same number of identity entities and per-entity deep equality (matched by guid). size_t src_count = 0; @@ -259,27 +258,26 @@ void TestSceneRoundTrip() { CHECK(dst.Get(dchild)->prefab == 0x123456789abcdef0ull); } - fs::remove(path); + rx::fs::Remove(path); } // Hand-authored scenes load strict: a misspelt name has to fail loudly and // point at the line, or a typo silently drops the object it was meant to place. void TestStrictLoad() { - namespace fs = std::filesystem; - fs::path path = fs::temp_directory_path() / "rx_edit_strict.rxscene"; + base::String path = rx::fs::Join(rx::fs::TempDirectory(), "rx_edit_strict.rxscene"); auto write = [&](const char* body) { - std::FILE* f = std::fopen(path.string().c_str(), "wb"); + FILE* f = ::fopen(path.c_str(), "wb"); CHECK(f != nullptr); if (!f) return; - std::fputs(body, f); - std::fclose(f); + ::fputs(body, f); + ::fclose(f); }; - auto load = [&](bool strict, std::string* error) { + auto load = [&](bool strict, base::String* error) { asset::Vfs vfs; asset::AssetDatabase db(vfs); ecs::World world; - bool ok = LoadScene(world, db, path.string(), error, strict); + bool ok = LoadScene(world, db, path, error, strict); // A rejected load must leave nothing behind. size_t entities = 0; world.Each([&](ecs::Entity, scene::Transform&) { ++entities; }); @@ -288,25 +286,25 @@ void TestStrictLoad() { }; write("rxscene 1\n\nentity\nTransform.position = 1 2 3\nNaem.value = \"oops\"\n"); - std::string error; + base::String error; CHECK(load(/*strict=*/false, &error)); // lenient: the editor keeps opening it CHECK(!load(/*strict=*/true, &error)); - CHECK(error.find("Naem") != std::string::npos); - CHECK(error.find(":5:") != std::string::npos); + CHECK(error.find("Naem") != base::String::npos); + CHECK(error.find(":5:") != base::String::npos); write("rxscene 1\n\nentity\nTransform.postion = 1 2 3\n"); error.clear(); CHECK(!load(/*strict=*/true, &error)); - CHECK(error.find("Transform") != std::string::npos); - CHECK(error.find("postion") != std::string::npos); - CHECK(error.find(":4:") != std::string::npos); + CHECK(error.find("Transform") != base::String::npos); + CHECK(error.find("postion") != base::String::npos); + CHECK(error.find(":4:") != base::String::npos); // A clean file still loads under strict. write("rxscene 1\n\nentity\nTransform.position = 1 2 3\nName.value = \"ok\"\n"); error.clear(); CHECK(load(/*strict=*/true, &error)); - fs::remove(path); + rx::fs::Remove(path); } // Numbers. A malformed or unrepresentable literal used to load as a silent @@ -316,15 +314,14 @@ void TestStrictLoad() { // keeps its old zero-padded value with a warning so the editor still opens the // file. void TestNumberLiterals() { - namespace fs = std::filesystem; - fs::path path = fs::temp_directory_path() / "rx_edit_numbers.rxscene"; + base::String path = rx::fs::Join(rx::fs::TempDirectory(), "rx_edit_numbers.rxscene"); - auto write = [&](const std::string& body) { - std::FILE* f = std::fopen(path.string().c_str(), "wb"); + auto write = [&](const base::String& body) { + FILE* f = ::fopen(path.c_str(), "wb"); CHECK(f != nullptr); if (!f) return; - std::fputs(("rxscene 1\n\nentity\n" + body).c_str(), f); - std::fclose(f); + ::fputs(("rxscene 1\n\nentity\n" + body).c_str(), f); + ::fclose(f); }; // Loads leniently and hands back the entity's Transform, which is what the // editor would show for the same file. @@ -332,7 +329,7 @@ void TestNumberLiterals() { asset::Vfs vfs; asset::AssetDatabase db(vfs); ecs::World world; - CHECK(LoadScene(world, db, path.string(), nullptr, /*strict=*/false)); + CHECK(LoadScene(world, db, path, nullptr, /*strict=*/false)); bool found = false; world.Each([&](ecs::Entity, scene::Transform& t) { if (!found) *out = t; @@ -344,8 +341,8 @@ void TestNumberLiterals() { asset::Vfs vfs; asset::AssetDatabase db(vfs); ecs::World world; - std::string error; - CHECK(!LoadScene(world, db, path.string(), &error, /*strict=*/true)); + base::String error; + CHECK(!LoadScene(world, db, path, &error, /*strict=*/true)); // A rejected load must leave nothing behind, numbers included. size_t entities = 0; world.Each([&](ecs::Entity, scene::Transform&) { ++entities; }); @@ -357,10 +354,10 @@ void TestNumberLiterals() { // A token that is not a number: rejected by name, with its line. write("Transform.position = 1 abc 3\n"); - std::string error = strict_error(); - CHECK(error.find("abc") != std::string::npos); - CHECK(error.find("not a number") != std::string::npos); - CHECK(error.find(":4:") != std::string::npos); + base::String error = strict_error(); + CHECK(error.find("abc") != base::String::npos); + CHECK(error.find("not a number") != base::String::npos); + CHECK(error.find(":4:") != base::String::npos); lenient(&t); // unchanged from before the fix: the bad lane and the rest zero CHECK_NEAR(t.position[0], 1.f, 1e-6f); CHECK_NEAR(t.position[1], 0.f, 1e-6f); @@ -370,27 +367,27 @@ void TestNumberLiterals() { // refused in both a lane and a scalar, which is the agreement that was // missing: only the scalar path used to accept these. for (const char* literal : {"1e40", "inf", "-inf", "nan"}) { - write(std::string("Transform.position = 0 ") + literal + " 0\n"); + write(base::String("Transform.position = 0 ") + literal + " 0\n"); error = strict_error(); - CHECK(error.find(literal) != std::string::npos); - CHECK(error.find("not finite") != std::string::npos); + CHECK(error.find(literal) != base::String::npos); + CHECK(error.find("not finite") != base::String::npos); lenient(&t); CHECK_NEAR(t.position[1], 0.f, 1e-6f); - CHECK(std::isfinite(t.position[1])); + CHECK(::isfinite(t.position[1])); - write(std::string("Transform.scale = ") + literal + "\n"); + write(base::String("Transform.scale = ") + literal + "\n"); error = strict_error(); - CHECK(error.find("not finite") != std::string::npos); + CHECK(error.find("not finite") != base::String::npos); lenient(&t); - CHECK(std::isfinite(t.scale)); + CHECK(::isfinite(t.scale)); } // A trailing suffix is not a number either, in the scalar path where strtof // used to keep the prefix and drop it. write("Transform.scale = 0.6f\n"); error = strict_error(); - CHECK(error.find("0.6f") != std::string::npos); - CHECK(error.find("not a number") != std::string::npos); + CHECK(error.find("0.6f") != base::String::npos); + CHECK(error.find("not a number") != base::String::npos); // A short list is the authored short form, not an error: it pads with zeros. write("Transform.position = 1 2\n"); @@ -398,7 +395,7 @@ void TestNumberLiterals() { asset::Vfs vfs; asset::AssetDatabase db(vfs); ecs::World world; - CHECK(LoadScene(world, db, path.string(), nullptr, /*strict=*/true)); + CHECK(LoadScene(world, db, path, nullptr, /*strict=*/true)); } lenient(&t); CHECK_NEAR(t.position[0], 1.f, 1e-6f); @@ -412,16 +409,16 @@ void TestNumberLiterals() { // authored, with --validate reporting the file clean. write("Transform.position = 1 2 3 4\n"); error = strict_error(); - CHECK(error.find("takes 3 values") != std::string::npos); - CHECK(error.find("'4'") != std::string::npos); - CHECK(error.find(":4:") != std::string::npos); + CHECK(error.find("takes 3 values") != base::String::npos); + CHECK(error.find("'4'") != base::String::npos); + CHECK(error.find(":4:") != base::String::npos); // Including a surplus that is not a number: the count is what is wrong, and // reporting it as a bad literal would send the author looking at the value. write("Transform.scale = 2 unrelated\n"); error = strict_error(); - CHECK(error.find("takes 1 value") != std::string::npos); - CHECK(error.find("unrelated") != std::string::npos); + CHECK(error.find("takes 1 value") != base::String::npos); + CHECK(error.find("unrelated") != base::String::npos); // Lenient keeps the lanes it could read, so the editor still opens the file. lenient(&t); CHECK_NEAR(t.scale, 2.f, 1e-6f); @@ -430,13 +427,13 @@ void TestNumberLiterals() { // whole quaternion is zero and MakeFromQuat collapses the mesh to a point. write("Transform.rotation = 0 0 0\n"); error = strict_error(); - CHECK(error.find("zero quaternion") != std::string::npos); + CHECK(error.find("zero quaternion") != base::String::npos); // Non-unit: MakeFromQuat does not normalize, so this scales the mesh by 0.707 // on top of Transform.scale. write("Transform.rotation = 0 0.5 0 0.5\n"); error = strict_error(); - CHECK(error.find("length") != std::string::npos); + CHECK(error.find("length") != base::String::npos); // Hand-rounded unit quaternions stay legal; 0.7 0 0 0.7 is 1% short. write("Transform.rotation = 0.7 0 0 0.7\nTransform.position = 0 0 0\n"); @@ -444,39 +441,38 @@ void TestNumberLiterals() { asset::Vfs vfs; asset::AssetDatabase db(vfs); ecs::World world; - std::string clean; - CHECK(LoadScene(world, db, path.string(), &clean, /*strict=*/true)); + base::String clean; + CHECK(LoadScene(world, db, path, &clean, /*strict=*/true)); } - fs::remove(path); + rx::fs::Remove(path); } // A non-finite float has no literal the loader reads back, so saving one would // be silent data loss on the next load. The save has to refuse, and refuse // before it has replaced whatever was on disk. void TestSaveRejectsNonFinite() { - namespace fs = std::filesystem; - fs::path path = fs::temp_directory_path() / "rx_edit_nonfinite.rxscene"; - fs::remove(path); + base::String path = rx::fs::Join(rx::fs::TempDirectory(), "rx_edit_nonfinite.rxscene"); + rx::fs::Remove(path); ecs::World world; ecs::Entity e = world.Create(); - world.Add(e, scene::Transform{{0, std::numeric_limits::quiet_NaN(), 0}, {0, 0, 0, 1}, 1.f}); + world.Add(e, scene::Transform{{0, NAN, 0}, {0, 0, 0, 1}, 1.f}); world.Add(e, scene::Name{"Poisoned"}); - std::string error; - CHECK(!SaveScene(world, path.string(), &error)); - CHECK(error.find("Transform.position") != std::string::npos); - CHECK(error.find("Poisoned") != std::string::npos); - CHECK(!fs::exists(path)); // nothing written, not a truncated file + base::String error; + CHECK(!SaveScene(world, path, &error)); + CHECK(error.find("Transform.position") != base::String::npos); + CHECK(error.find("Poisoned") != base::String::npos); + CHECK(!rx::fs::Exists(path)); // nothing written, not a truncated file // The same scene saves once the value is one the format can spell. world.Get(e)->position[1] = 2.f; error.clear(); - CHECK(SaveScene(world, path.string(), &error)); - CHECK(fs::exists(path)); + CHECK(SaveScene(world, path, &error)); + CHECK(rx::fs::Exists(path)); - fs::remove(path); + rx::fs::Remove(path); } // The schema dump names every PropType, so an unmapped one would silently @@ -487,7 +483,7 @@ void TestPropTypeNames() { PropType::kVec3, PropType::kVec4, PropType::kQuat, PropType::kColor, PropType::kString, PropType::kAssetId, PropType::kEntity}; - for (PropType type : kAll) CHECK(std::string(PropTypeName(type)) != "?"); + for (PropType type : kAll) CHECK(base::String(PropTypeName(type)) != "?"); CHECK(!AllComponents().empty()); } @@ -630,6 +626,6 @@ int main() { TestUndo(); TestHierarchy(); TestSelection(); - if (failures == 0) std::printf("edit_test: all passed\n"); + if (failures == 0) ::printf("edit_test: all passed\n"); return failures == 0 ? 0 : 1; } diff --git a/test/expression_test.cc b/test/expression_test.cc index b0f6ec28..62b71e9e 100644 --- a/test/expression_test.cc +++ b/test/expression_test.cc @@ -8,19 +8,22 @@ // configured interval window, a pose that holds the eyes closed absorbs // them, and a fixed seed + fixed dt replays bit-identically. -#include -#include -#include +#include +#include +#include +#include #include "anim/expression.h" #include "asset/asset_id.h" +#include "base/memory/mem_ops.h" +#include "core/scalar.h" using namespace rx; namespace { int Fail(const char* msg) { - std::fprintf(stderr, "expression_test: FAIL: %s\n", msg); + ::fprintf(stderr, "expression_test: FAIL: %s\n", msg); return 1; } @@ -51,20 +54,20 @@ int TestTransition() { controller.Update(dt); const f32 weight = controller.Weight(smile); const f32 v = (weight - previous) / dt; - if (i > 0) max_jump = std::max(max_jump, std::abs(v - velocity)); + if (i > 0) max_jump = rx::Max(max_jump, ::abs(v - velocity)); previous = weight; velocity = v; } - std::printf("expression_test: max velocity jump %g /s per step\n", max_jump); + ::printf("expression_test: max velocity jump %g /s per step\n", max_jump); if (max_jump > 1.5f) return Fail("velocity discontinuity across a retarget"); - if (std::abs(controller.Weight(smile) - 0.75f) > 1e-3f) { + if (::abs(controller.Weight(smile) - 0.75f) > 1e-3f) { return Fail("smile did not converge to its pose weight"); } - if (std::abs(velocity) > 1e-2f) return Fail("velocity did not settle"); + if (::abs(velocity) > 1e-2f) return Fail("velocity did not settle"); controller.SetExpression("neutral"); for (int i = 0; i < 2 * 240; ++i) controller.Update(dt); - if (std::abs(controller.Weight(smile)) > 1e-3f) return Fail("neutral did not release"); + if (::abs(controller.Weight(smile)) > 1e-3f) return Fail("neutral did not release"); return 0; } @@ -103,13 +106,13 @@ int TestBlink() { time += dt; const bool now = controller.Weight(blink) > 0.5f; if (now && !closed) { - if (blinks > 0) max_gap = std::max(max_gap, time - last_start); + if (blinks > 0) max_gap = rx::Max(max_gap, time - last_start); last_start = time; ++blinks; } closed = now; } - std::printf("expression_test: %d blinks in 30 s, max gap %.2f s\n", blinks, max_gap); + ::printf("expression_test: %d blinks in 30 s, max gap %.2f s\n", blinks, max_gap); // 2-6 s intervals plus occasional double blinks: 5..25 in 30 s. if (blinks < 5 || blinks > 25) return Fail("blink count outside the interval window"); if (max_gap > 6.6f) return Fail("blink gap exceeded the configured maximum"); @@ -143,7 +146,7 @@ int TestDeterminism() { for (u32 ch = 0; ch < a.channel_count(); ++ch) { const f32 wa = a.channel_weight(ch); const f32 wb = b.channel_weight(ch); - if (std::memcmp(&wa, &wb, sizeof(f32)) != 0) return Fail("same seed diverged"); + if (base::MemCompare(&wa, &wb, sizeof(f32)) != 0) return Fail("same seed diverged"); if (wa != c.channel_weight(ch)) diverged = true; } } @@ -158,6 +161,6 @@ int main() { if (int rc = TestClamped()) return rc; if (int rc = TestBlink()) return rc; if (int rc = TestDeterminism()) return rc; - std::printf("expression_test: PASS\n"); + ::printf("expression_test: PASS\n"); return 0; } diff --git a/test/fluid_sim_test.cc b/test/fluid_sim_test.cc index 8b551cf7..46222661 100644 --- a/test/fluid_sim_test.cc +++ b/test/fluid_sim_test.cc @@ -10,12 +10,14 @@ // Skips cleanly (exit 0) when no Vulkan driver is present (null backend), like // offscreen_test; run under vkrun to exercise the real GPU path. -#include -#include -#include -#include -#include - +#include +#include +#include +#include + +#include "base/containers/vector.h" +#include "base/memory/unique_pointer.h" +#include "core/scalar.h" #include "render/core/render_graph.h" #include "render/geometry/fluid_sim.h" #include "render/rhi/device.h" @@ -25,7 +27,7 @@ using namespace rx::render; namespace { int Fail(const char* msg) { - std::fprintf(stderr, "fluid_sim_test: FAIL: %s\n", msg); + ::fprintf(stderr, "fluid_sim_test: FAIL: %s\n", msg); return 1; } @@ -44,18 +46,18 @@ f32 BowlBed(u32 x, u32 y) { int main() { DeviceDesc desc; desc.request_raytracing = false; - std::unique_ptr device = Device::CreateOffscreen(desc); + base::UniquePointer device = Device::CreateOffscreen(desc); if (!device) return Fail("CreateOffscreen returned null"); if (device->is_stub()) { - std::printf("fluid_sim_test: no Vulkan driver, skipping (null backend)\n"); + ::printf("fluid_sim_test: no Vulkan driver, skipping (null backend)\n"); return 0; } - std::printf("fluid_sim_test: device '%s'\n", device->caps().adapter_name.c_str()); + ::printf("fluid_sim_test: device '%s'\n", device->caps().adapter_name.c_str()); // Bed with the ridge (a dam down the middle) and bed without it (dam removed). - std::vector bed_ridge(static_cast(kRes) * kRes); - std::vector bed_open(static_cast(kRes) * kRes); - std::vector water0(static_cast(kRes) * kRes, 0.0f); + base::Vector bed_ridge(static_cast(kRes) * kRes); + base::Vector bed_open(static_cast(kRes) * kRes); + base::Vector water0(static_cast(kRes) * kRes, 0.0f); f32 initial_volume = 0.0f; for (u32 y = 0; y < kRes; ++y) { for (u32 x = 0; x < kRes; ++x) { @@ -121,7 +123,7 @@ int main() { if (!graph.Compile(*device, pool)) return false; PassContext ctx; ctx.cmd = cmd; - ctx.device = device.get(); + ctx.device = device.Get_UseOnlyIfYouKnowWhatYouareDoing(); ctx.graph = &graph; graph.Execute(ctx); device->SubmitFrame(cmd); @@ -130,7 +132,7 @@ int main() { return true; }; - auto readback = [&](std::vector& out) -> bool { + auto readback = [&](base::Vector& out) -> bool { out.assign(static_cast(kRes) * kRes * 4, 0.0f); const GpuImage& state = FluidSimProbe::state(sim); return device->ReadbackImage(state, ResourceState::kGeneral, out.data(), @@ -139,19 +141,19 @@ int main() { // Verify the upload landed before stepping (bed + water, flat pond). { - std::vector s0; + base::Vector s0; if (!readback(s0)) return Fail("ReadbackImage failed (pre-step)"); f32 v = 0; for (u32 i = 0; i < kRes * kRes; ++i) v += s0[i * 4 + 0]; - std::printf("fluid_sim_test: uploaded water volume %.3f (cpu %.3f)\n", v, initial_volume); - if (std::fabs(v - initial_volume) > 0.01f * initial_volume) + ::printf("fluid_sim_test: uploaded water volume %.3f (cpu %.3f)\n", v, initial_volume); + if (::fabs(v - initial_volume) > 0.01f * initial_volume) return Fail("uploaded water volume does not match the CPU fill"); } // Phase 1: settle behind the ridge, then check conservation + sanity if (!run(dom, 75)) return Fail("frame loop failed (phase 1)"); // ~300 substeps - std::vector s1; + base::Vector s1; if (!readback(s1)) return Fail("ReadbackImage failed (phase 1)"); f32 vol1 = 0.0f; @@ -160,13 +162,13 @@ int main() { const f32 dl = s1[i * 4 + 1]; const f32 T = s1[i * 4 + 2]; const f32 C = s1[i * 4 + 3]; - if (!std::isfinite(dw) || !std::isfinite(dl) || !std::isfinite(T) || !std::isfinite(C)) + if (!::isfinite(dw) || !::isfinite(dl) || !::isfinite(T) || !::isfinite(C)) return Fail("non-finite value in state"); if (dw < -1e-4f || dl < -1e-4f || C < -1e-4f) return Fail("negative depth/crust in state"); vol1 += dw; } - const f32 drift = std::fabs(vol1 - initial_volume) / std::max(initial_volume, 1e-6f); - std::printf("fluid_sim_test: initial volume %.3f, after settle %.3f (drift %.3f%%)\n", + const f32 drift = ::fabs(vol1 - initial_volume) / rx::Max(initial_volume, 1e-6f); + ::printf("fluid_sim_test: initial volume %.3f, after settle %.3f (drift %.3f%%)\n", initial_volume, vol1, drift * 100.0f); if (drift > 0.01f) return Fail("water volume not conserved within 1%"); @@ -182,7 +184,7 @@ int main() { // so the flickering shoreline does not dominate the spread. constexpr f32 kLevelTol = 0.05f; // "a few cm" constexpr u32 kMaxChunks = 40; // 40 * 100 frames = 16000 substeps cap - std::vector s2; + base::Vector s2; f32 spread = 1e9f; u32 wet = 0; for (u32 chunk = 0; chunk < kMaxChunks && spread > kLevelTol; ++chunk) { @@ -192,24 +194,24 @@ int main() { wet = 0; for (u32 i = 0; i < kRes * kRes; ++i) { const f32 dw = s2[i * 4 + 0]; - if (!std::isfinite(dw)) return Fail("non-finite depth after flood"); + if (!::isfinite(dw)) return Fail("non-finite depth after flood"); if (dw < -1e-4f) return Fail("negative depth after flood"); vol2 += dw; if (dw > 0.5f) { // core pond; shoreline partial cells sit below the plane const f32 surface = bed_open[i] + dw; - mn = std::fmin(mn, surface); - mx = std::fmax(mx, surface); + mn = ::fmin(mn, surface); + mx = ::fmax(mx, surface); ++wet; } } spread = (wet > 0) ? mx - mn : 1e9f; - std::printf("fluid_sim_test: flood %u frames, volume %.3f, wet %u, spread %.3f m\n", + ::printf("fluid_sim_test: flood %u frames, volume %.3f, wet %u, spread %.3f m\n", (chunk + 1) * 100, vol2, wet, spread); } if (wet < 16) return Fail("too few wet cells after flood (sim collapsed?)"); if (spread > kLevelTol) return Fail("flooded surface not level within tolerance"); sim.Destroy(*device); - std::printf("fluid_sim_test: PASS\n"); + ::printf("fluid_sim_test: PASS\n"); return 0; } diff --git a/test/gltf_skin_test.cc b/test/gltf_skin_test.cc index 20fc6aaa..d8b2a8ac 100644 --- a/test/gltf_skin_test.cc +++ b/test/gltf_skin_test.cc @@ -1,8 +1,9 @@ -#include -#include -#include +#include +#include #include "asset/gltf_loader.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" namespace { @@ -47,35 +48,35 @@ int failures = 0; void Check(bool condition, const char *message) { if (condition) return; - std::fprintf(stderr, "gltf_skin_test: FAIL: %s\n", message); + ::fprintf(stderr, "gltf_skin_test: FAIL: %s\n", message); ++failures; } bool IdentityTransform(const asset::ImportedScene::Instance &instance) { return Length(instance.position) < 1e-7f && - std::fabs(instance.rotation[0]) < 1e-7f && - std::fabs(instance.rotation[1]) < 1e-7f && - std::fabs(instance.rotation[2]) < 1e-7f && - std::fabs(instance.rotation[3] - 1.0f) < 1e-7f && - std::fabs(instance.scale - 1.0f) < 1e-7f; + ::fabs(instance.rotation[0]) < 1e-7f && + ::fabs(instance.rotation[1]) < 1e-7f && + ::fabs(instance.rotation[2]) < 1e-7f && + ::fabs(instance.rotation[3] - 1.0f) < 1e-7f && + ::fabs(instance.scale - 1.0f) < 1e-7f; } } // namespace int main() { - const std::filesystem::path path = - std::filesystem::temp_directory_path() / "rx_shared_mesh_skins.gltf"; - std::FILE *file = std::fopen(path.string().c_str(), "wb"); + const base::String path = + rx::fs::Join(rx::fs::TempDirectory(), "rx_shared_mesh_skins.gltf"); + FILE *file = ::fopen(path.c_str(), "wb"); if (!file) { - std::fprintf(stderr, "gltf_skin_test: cannot create fixture\n"); + ::fprintf(stderr, "gltf_skin_test: cannot create fixture\n"); return 1; } - std::fwrite(kSharedMeshSkins, 1, sizeof(kSharedMeshSkins) - 1, file); - std::fclose(file); + ::fwrite(kSharedMeshSkins, 1, sizeof(kSharedMeshSkins) - 1, file); + ::fclose(file); asset::ImportedScene scene; - const bool loaded = asset::LoadGltfScene(path.string(), &scene); - std::filesystem::remove(path); + const bool loaded = asset::LoadGltfScene(path, &scene); + rx::fs::Remove(path); Check(loaded, "generated glTF loads"); if (loaded) { Check(scene.meshes.size() == 1 && scene.meshes[0].skinned, @@ -97,7 +98,7 @@ int main() { } if (failures == 0) { - std::puts("gltf_skin_test: PASS"); + ::puts("gltf_skin_test: PASS"); return 0; } return 1; diff --git a/test/gltf_texture_transform_test.cc b/test/gltf_texture_transform_test.cc index fb89645c..b9cf557c 100644 --- a/test/gltf_texture_transform_test.cc +++ b/test/gltf_texture_transform_test.cc @@ -8,11 +8,12 @@ // Three quads sharing one set of accessors, so the only difference between them // is the material each names. -#include -#include -#include +#include +#include #include "asset/gltf_loader.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" namespace { @@ -67,7 +68,7 @@ int failures = 0; void Check(bool condition, const char *message) { if (condition) return; - std::fprintf(stderr, "gltf_texture_transform_test: FAIL: %s\n", message); + ::fprintf(stderr, "gltf_texture_transform_test: FAIL: %s\n", message); ++failures; } @@ -75,25 +76,25 @@ bool UvIs(const asset::Mesh &mesh, u32 vertex, f32 u, f32 v) { if (mesh.lods.empty() || mesh.lods[0].vertices.size() <= vertex) return false; const asset::Vertex &out = mesh.lods[0].vertices[vertex]; - return std::fabs(out.uv[0] - u) < 1e-5f && std::fabs(out.uv[1] - v) < 1e-5f; + return ::fabs(out.uv[0] - u) < 1e-5f && ::fabs(out.uv[1] - v) < 1e-5f; } } // namespace int main() { - const std::filesystem::path path = - std::filesystem::temp_directory_path() / "rx_texture_transform.gltf"; - std::FILE *file = std::fopen(path.string().c_str(), "wb"); + const base::String path = + rx::fs::Join(rx::fs::TempDirectory(), "rx_texture_transform.gltf"); + FILE *file = ::fopen(path.c_str(), "wb"); if (!file) { - std::fprintf(stderr, "gltf_texture_transform_test: cannot create fixture\n"); + ::fprintf(stderr, "gltf_texture_transform_test: cannot create fixture\n"); return 1; } - std::fwrite(kDocument, 1, sizeof(kDocument) - 1, file); - std::fclose(file); + ::fwrite(kDocument, 1, sizeof(kDocument) - 1, file); + ::fclose(file); asset::ImportedScene scene; - const bool loaded = asset::LoadGltfScene(path.string(), &scene); - std::filesystem::remove(path); + const bool loaded = asset::LoadGltfScene(path, &scene); + rx::fs::Remove(path); Check(loaded, "generated glTF loads"); if (!loaded) return 1; @@ -122,7 +123,7 @@ int main() { "conflicting slots resolve to the base colour's transform"); if (failures == 0) { - std::puts("gltf_texture_transform_test: PASS"); + ::puts("gltf_texture_transform_test: PASS"); return 0; } return 1; diff --git a/test/hair_bsdf_test.cc b/test/hair_bsdf_test.cc index ce53610d..03994cdc 100644 --- a/test/hair_bsdf_test.cc +++ b/test/hair_bsdf_test.cc @@ -1,8 +1,9 @@ +#include "core/scalar.h" #include "render/pipeline/hair_material.h" -#include -#include -#include +#include +#include +#include // Regression tests for the hair BSDF (render/shaders/hair_bsdf.hlsli, mirrored // on the CPU in hair_material.cc). The contracts that matter for hair: @@ -25,7 +26,7 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "hair_bsdf_test: FAIL: %s\n", message); + ::fprintf(stderr, "hair_bsdf_test: FAIL: %s\n", message); ++failures; } @@ -34,9 +35,9 @@ constexpr float kPi = 3.14159265358979323846f; // A direction in the strand frame: theta is the longitudinal angle off the // normal plane, phi the azimuth around the fibre. void Dir(float theta, float phi, rx::f32 out[3]) { - out[0] = std::sin(theta); - out[1] = std::cos(theta) * std::cos(phi); - out[2] = std::cos(theta) * std::sin(phi); + out[0] = ::sin(theta); + out[1] = ::cos(theta) * ::cos(phi); + out[2] = ::cos(theta) * ::sin(phi); } float Luma(const rx::f32 rgb[3]) { @@ -64,7 +65,7 @@ int main() { "a non-absorbing fibre reflects essentially all of it (the residual lobe closes " "the gap Marschner's three leave open)"); if (albedo > 1.02f || albedo < 0.93f) { - std::fprintf(stderr, " beta %.2f theta_o %+.2f -> albedo %.4f\n", beta, theta_o, + ::fprintf(stderr, " beta %.2f theta_o %+.2f -> albedo %.4f\n", beta, theta_o, albedo); } } @@ -106,10 +107,10 @@ int main() { best_phi = phi; } } - worst = std::max(worst, std::abs(best_phi - (-2.0f * std::asin(h)))); + worst = rx::Max(worst, ::abs(best_phi - (-2.0f * ::asin(h)))); } Check(worst < 0.01f, "the surface lobe peaks where the cylinder geometry says it should"); - if (worst >= 0.01f) std::fprintf(stderr, " worst azimuthal peak error: %.4f rad\n", worst); + if (worst >= 0.01f) ::fprintf(stderr, " worst azimuthal peak error: %.4f rad\n", worst); // ... and h has to matter. A model evaluated at h = 0 everywhere loses the // variation across the strand's width entirely. @@ -119,7 +120,7 @@ int main() { rx::f32 centre[3], edge[3]; HairEvaluateCpu(brown, wo, wi, 0.0f, centre); HairEvaluateCpu(brown, wo, wi, 0.85f, edge); - Check(std::abs(Luma(centre) - Luma(edge)) > 0.01f * std::max(Luma(centre), Luma(edge)), + Check(::abs(Luma(centre) - Luma(edge)) > 0.01f * rx::Max(Luma(centre), Luma(edge)), "the response varies across the fibre's width"); } @@ -141,22 +142,22 @@ int main() { rx::f32 a[3], b[3]; HairEvaluateCpu(p, samples[i][0], samples[i][1], 0.3f, a); HairEvaluateCpu(p, samples[i][1], samples[i][0], -0.3f, b); - for (int c = 0; c < 3; ++c) peak = std::max(peak, std::max(a[c], b[c])); + for (int c = 0; c < 3; ++c) peak = rx::Max(peak, rx::Max(a[c], b[c])); } for (int i = 0; i < 40; ++i) { rx::f32 a[3], b[3]; HairEvaluateCpu(p, samples[i][0], samples[i][1], 0.3f, a); HairEvaluateCpu(p, samples[i][1], samples[i][0], -0.3f, b); for (int c = 0; c < 3; ++c) { - const float m = std::max(a[c], b[c]); + const float m = rx::Max(a[c], b[c]); if (m < 0.05f * peak) continue; - worst = std::max(worst, std::abs(a[c] - b[c]) / m); + worst = rx::Max(worst, ::abs(a[c] - b[c]) / m); } } Check(worst < 0.75f, "the model's known non-reciprocity stays within the range the published " "formulation produces"); - if (worst >= 0.75f) std::fprintf(stderr, " asymmetry: %.3f\n", worst); + if (worst >= 0.75f) ::fprintf(stderr, " asymmetry: %.3f\n", worst); } // 2c. the lobes are what they claim to be @@ -173,9 +174,9 @@ int main() { Dir(0.0f, 0.0f, wo); Dir(0.0f, 0.0f, wi); // phi = 0: the R lobe's peak at h = 0 HairEvaluateCpu(black, wo, wi, 0.0f, rgb); - const float spread = (std::max({rgb[0], rgb[1], rgb[2]}) - - std::min({rgb[0], rgb[1], rgb[2]})) / - std::max({rgb[0], rgb[1], rgb[2], 1e-6f}); + const float spread = (rx::Max({rgb[0], rgb[1], rgb[2]}) - + rx::Min({rgb[0], rgb[1], rgb[2]})) / + rx::Max({rgb[0], rgb[1], rgb[2], 1e-6f}); Check(spread < 0.02f, "the surface lobe is achromatic even on a heavily pigmented fibre"); // The transmission lobe, by contrast, has to be strongly coloured. @@ -185,7 +186,7 @@ int main() { rx::f32 through[3]; Dir(0.0f, kPi, wi); // straight through the fibre HairEvaluateCpu(red, wo, wi, 0.0f, through); - const float tint = through[0] / std::max(through[2], 1e-6f); + const float tint = through[0] / rx::Max(through[2], 1e-6f); Check(tint > 2.0f, "the transmission lobe carries the pigment's colour"); } @@ -212,8 +213,8 @@ int main() { rx::f32 red_rgb[3], brown_rgb[3]; HairEvaluateCpu(red, wo, wi, 0.0f, red_rgb); HairEvaluateCpu(brown, wo, wi, 0.0f, brown_rgb); - Check(red_rgb[0] / std::max(red_rgb[2], 1e-6f) > - brown_rgb[0] / std::max(brown_rgb[2], 1e-6f), + Check(red_rgb[0] / rx::Max(red_rgb[2], 1e-6f) > + brown_rgb[0] / rx::Max(brown_rgb[2], 1e-6f), "pheomelanin reddens rather than merely darkening"); // The colour inversion. Chiang's fit targets the colour a GROOM settles at @@ -262,24 +263,24 @@ int main() { rx::f32 wi[3], rgb[3]; Dir(theta, phi, wi); HairShadeCpu(q, w_out, wi, 0.0f, depth, rgb); - total += static_cast(rgb[1]) * std::cos(theta); + total += static_cast(rgb[1]) * ::cos(theta); } } } return total; }; - return static_cast(integrate(p) / std::max(integrate(clear), 1e-12)); + return static_cast(integrate(p) / rx::Max(integrate(clear), 1e-12)); }; float worst = 0.0f; for (const float depth : {3.0f, 6.0f, 10.0f}) { for (const float target : {0.85f, 0.65f, 0.45f, 0.28f, 0.15f, 0.07f}) { - worst = std::max(worst, std::abs(shaded_albedo(target, depth) - target)); + worst = rx::Max(worst, ::abs(shaded_albedo(target, depth) - target)); } } Check(worst < 0.02f, "the fitted colour inversion renders the colour it was asked for, at every " "reference depth"); - if (worst >= 0.02f) std::fprintf(stderr, " worst colour round-trip error: %.4f\n", worst); + if (worst >= 0.02f) ::fprintf(stderr, " worst colour round-trip error: %.4f\n", worst); // And the published constants must NOT be silently substituted: they are // right for a path tracer and wrong here, which is the whole reason the @@ -321,8 +322,8 @@ int main() { HairShadeCpu(blonde, wo, wi, 0.0f, 6.0f, blonde_on); HairShadeCpu(black_noscatter, wo, wi, 0.0f, 6.0f, black_off); HairShadeCpu(black, wo, wi, 0.0f, 6.0f, black_on); - const float blonde_gain = Luma(blonde_on) / std::max(Luma(blonde_off), 1e-9f); - const float black_gain = Luma(black_on) / std::max(Luma(black_off), 1e-9f); + const float blonde_gain = Luma(blonde_on) / rx::Max(Luma(blonde_off), 1e-9f); + const float black_gain = Luma(black_on) / rx::Max(Luma(black_off), 1e-9f); Check(blonde_gain > 1.05f, "multiple scattering lifts a light fibre"); Check(blonde_gain > black_gain, "and lifts a light fibre more than a black one (a black fibre absorbs before it " @@ -365,7 +366,7 @@ int main() { }; const float flat_peak = peak_theta(flat, 1); const float tilted_peak = peak_theta(tilted, 1); - Check(std::abs(tilted_peak - flat_peak) > 0.01f, + Check(::abs(tilted_peak - flat_peak) > 0.01f, "the cuticle tilt shifts the highlight off the specular direction"); } @@ -407,7 +408,7 @@ int main() { rx::f32 shallow[3], deep[3]; HairShadeCpu(distant, wo, wi, 0.0f, 1.0f, shallow); HairShadeCpu(distant, wo, wi, 0.0f, 40.0f, deep); - Check(std::abs(shallow[0] - deep[0]) < 1e-6f && std::abs(shallow[2] - deep[2]) < 1e-6f, + Check(::abs(shallow[0] - deep[0]) < 1e-6f && ::abs(shallow[2] - deep[2]) < 1e-6f, "with multiple scattering off, fibre depth changes nothing"); rx::f32 lit_hero[3]; @@ -460,6 +461,6 @@ int main() { "blonde is darker than white"); } - if (failures == 0) std::fprintf(stderr, "hair_bsdf_test: all checks passed\n"); + if (failures == 0) ::fprintf(stderr, "hair_bsdf_test: all checks passed\n"); return failures == 0 ? 0 : 1; } diff --git a/test/hair_sim_test.cc b/test/hair_sim_test.cc index 1901b27f..e0e1ed38 100644 --- a/test/hair_sim_test.cc +++ b/test/hair_sim_test.cc @@ -3,8 +3,8 @@ // a moving head transform, and asserts the hairstyle constraints hold: nodes // stay finite, pinned nodes (roots and the ponytail tie) track the transform, // braid strands stay woven, and no node ends up inside the head sphere. -#include -#include +#include +#include #include @@ -19,7 +19,7 @@ namespace { constexpr u32 kPointsPerStrand = render::kGroomPointsPerStrand; int Fail(const char* what) { - std::fprintf(stderr, "hair_sim_test FAIL: %s\n", what); + ::fprintf(stderr, "hair_sim_test FAIL: %s\n", what); return 1; } @@ -68,8 +68,8 @@ int CheckGroom(physics::PhysicsWorld& world, Groom& g, const Mat4& transform, return Fail("GetStrandGroomPositions"); } for (u32 i = 0; i < count; ++i) { - if (!std::isfinite(g.positions[i])) { - std::fprintf(stderr, " groom %s\n", name); + if (!::isfinite(g.positions[i])) { + ::fprintf(stderr, " groom %s\n", name); return Fail("non-finite node position"); } } @@ -82,13 +82,13 @@ int CheckGroom(physics::PhysicsWorld& world, Groom& g, const Mat4& transform, }; for (u32 s = 0; s < g.data.guide_count; ++s) { if (!check_pin(s, 0)) { - std::fprintf(stderr, " groom %s strand %u\n", name, s); + ::fprintf(stderr, " groom %s strand %u\n", name, s); return Fail("root not pinned"); } } for (size_t i = 0; i + 1 < g.data.pins.size(); i += 2) { if (!check_pin(g.data.pins[i], g.data.pins[i + 1])) { - std::fprintf(stderr, " groom %s pin %zu\n", name, i / 2); + ::fprintf(stderr, " groom %s pin %zu\n", name, i / 2); return Fail("style pin not held"); } } @@ -103,7 +103,7 @@ int CheckGroom(physics::PhysicsWorld& world, Groom& g, const Mat4& transform, Vec3 rb{g.data.points[ib], g.data.points[ib + 1], g.data.points[ib + 2]}; f32 rest = Length(ra - rb); if (Length(a - b) > rest + 0.01f) { - std::fprintf(stderr, " groom %s bind %zu: %.4f vs rest %.4f\n", name, i / 4, + ::fprintf(stderr, " groom %s bind %zu: %.4f vs rest %.4f\n", name, i / 4, Length(a - b), rest); return Fail("bind did not hold"); } @@ -115,7 +115,7 @@ int CheckGroom(physics::PhysicsWorld& world, Groom& g, const Mat4& transform, for (u32 k = 1; k < kPointsPerStrand; ++k) { f32 d = Length(NodeAt(g.positions, s, k) - head_center); if (d < g.head.radius * 0.85f) { - std::fprintf(stderr, " groom %s strand %u node %u: %.4f into r=%.4f\n", name, s, k, d, + ::fprintf(stderr, " groom %s strand %u node %u: %.4f into r=%.4f\n", name, s, k, d, g.head.radius); return Fail("node inside the head sphere"); } @@ -164,7 +164,7 @@ int main() { { Vec3 root = NodeAt(braid.positions, 0, 0); Vec3 tip = NodeAt(braid.positions, 0, kPointsPerStrand - 1); - std::printf("braid: root y=%.3f tip y=%.3f\n", root.y, tip.y); + ::printf("braid: root y=%.3f tip y=%.3f\n", root.y, tip.y); if (tip.y > root.y - 0.15f) return Fail("braid does not hang"); } @@ -173,8 +173,8 @@ int main() { Mat4 moving = rest_transform; for (int i = 1; i <= 240; ++i) { f32 a = static_cast(i) * dt * 3.0f; - moving = MakeTranslation({0.05f * std::sin(a), 1.6f + 0.03f * std::sin(a * 1.7f), 0}) * - MakeFromQuat(QuatFromAxisAngle({0, 1, 0}, 0.8f * std::sin(a))); + moving = MakeTranslation({0.05f * ::sin(a), 1.6f + 0.03f * ::sin(a * 1.7f), 0}) * + MakeFromQuat(QuatFromAxisAngle({0, 1, 0}, 0.8f * ::sin(a))); world.SetStrandGroomTransform(braid.sim, moving, dt); world.SetStrandGroomTransform(ponytail.sim, moving, dt); world.Update(dt); @@ -190,6 +190,6 @@ int main() { world.RemoveStrandGroom(braid.sim); world.RemoveStrandGroom(ponytail.sim); - std::printf("hair_sim_test OK\n"); + ::printf("hair_sim_test OK\n"); return 0; } diff --git a/test/handling_test.cc b/test/handling_test.cc index 28bae614..ead9fbbb 100644 --- a/test/handling_test.cc +++ b/test/handling_test.cc @@ -14,12 +14,12 @@ // (f) downforce: the sports car's grip grows with speed, the hatch's doesn't // (g) every profile is NaN-free, settles at rest, and respects surface grip -#include -#include -#include -#include +#include +#include +#include "base/containers/vector.h" #include "core/math.h" +#include "core/scalar.h" #include "physics/physics_world.h" #include "physics/vehicle_profiles.h" @@ -37,7 +37,7 @@ constexpr f32 kKmh = 1.0f / 3.6f; // km/h -> m/s int g_failures = 0; void Check(bool ok, const char* what) { if (!ok) { - std::fprintf(stderr, "handling_test FAIL: %s\n", what); + ::fprintf(stderr, "handling_test FAIL: %s\n", what); ++g_failures; } } @@ -48,7 +48,7 @@ physics::BodyId AddFlatGround(PhysicsWorld& world, SurfaceType surface) { // vehicle fell into the void). Flat, so the coarse sampling is exact. constexpr u32 kSamples = 96; constexpr f32 kSize = 24000.0f; - std::vector heights(static_cast(kSamples) * kSamples, 0.0f); + base::Vector heights(static_cast(kSamples) * kSamples, 0.0f); return world.AddHeightField(Vec3{-kSize * 0.5f, 0.0f, -kSize * 0.5f}, heights.data(), kSamples, kSize, surface); } @@ -65,12 +65,12 @@ VehicleId SpawnSettled(PhysicsWorld& world, const Desc& d) { f32 Heading(const f32 q[4]) { const Vec3 f = Rotate(Quat{q[0], q[1], q[2], q[3]}, Vec3{0, 0, 1}); - return std::atan2(f.x, f.z); + return ::atan2(f.x, f.z); } f32 RollAngle(const f32 q[4]) { const Vec3 right = Rotate(Quat{q[0], q[1], q[2], q[3]}, Vec3{1, 0, 0}); - return std::asin(std::clamp(right.y, -1.0f, 1.0f)); + return ::asin(rx::Clamp(right.y, -1.0f, 1.0f)); } f32 WrapPi(f32 a) { @@ -115,10 +115,10 @@ f32 BrakeDistance(const Desc& d, f32 entry) { world.DriveVehicle(id, 0.0f, 0.0f, 1.0f, 0.0f); world.Update(kDt); world.GetVehicleTransform(id, &last, rot); - if (std::fabs(world.VehicleForwardSpeed(id)) < 0.5f) break; + if (::fabs(world.VehicleForwardSpeed(id)) < 0.5f) break; } const f32 dx = last.x - start.x, dz = last.z - start.z; - return std::sqrt(dx * dx + dz * dz); + return ::sqrt(dx * dx + dz * dz); } struct SteerResult { @@ -152,11 +152,11 @@ SteerResult StepSteer(const Desc& d, f32 entry) { world.DriveVehicle(id, 0.35f, 0.30f, 0.0f, 0.0f); world.Update(kDt); world.GetVehicleTransform(id, &pos, rot); - r.peak_roll_deg = std::max(r.peak_roll_deg, std::fabs(RollAngle(rot)) * 57.2958f); - min_speed = std::min(min_speed, std::fabs(world.VehicleForwardSpeed(id))); + r.peak_roll_deg = rx::Max(r.peak_roll_deg, ::fabs(RollAngle(rot)) * 57.2958f); + min_speed = rx::Min(min_speed, ::fabs(world.VehicleForwardSpeed(id))); } world.GetVehicleTransform(id, &pos, rot); - r.heading_change_deg = std::fabs(WrapPi(Heading(rot) - start_heading)) * 57.2958f; + r.heading_change_deg = ::fabs(WrapPi(Heading(rot) - start_heading)) * 57.2958f; r.speed_lost_mps = entry_speed - min_speed; return r; } @@ -189,10 +189,10 @@ SlipResult CornerSlip(const Desc& d) { if (i < 40) continue; // let the corner develop PhysicsWorld::VehicleState st; if (!world.GetVehicleState(id, &st)) continue; - const f32 f = 0.5f * (std::fabs(st.wheels[0].lateral_slip) + std::fabs(st.wheels[1].lateral_slip)); - const f32 rr = 0.5f * (std::fabs(st.wheels[2].lateral_slip) + std::fabs(st.wheels[3].lateral_slip)); - r.front = std::max(r.front, f); - r.rear = std::max(r.rear, rr); + const f32 f = 0.5f * (::fabs(st.wheels[0].lateral_slip) + ::fabs(st.wheels[1].lateral_slip)); + const f32 rr = 0.5f * (::fabs(st.wheels[2].lateral_slip) + ::fabs(st.wheels[3].lateral_slip)); + r.front = rx::Max(r.front, f); + r.rear = rx::Max(r.rear, rr); } return r; } @@ -222,22 +222,22 @@ f32 LateralAccel(const Desc& d, f32 target) { world.Update(kDt); world.GetVehicleTransform(id, &pos, rot); const f32 h = Heading(rot); - const f32 yaw_rate = std::fabs(WrapPi(h - prev_heading)) / kDt; + const f32 yaw_rate = ::fabs(WrapPi(h - prev_heading)) / kDt; prev_heading = h; if (i < 8) continue; // skip the initial transient - peak = std::max(peak, std::fabs(world.VehicleForwardSpeed(id)) * yaw_rate); + peak = rx::Max(peak, ::fabs(world.VehicleForwardSpeed(id)) * yaw_rate); } return peak; } -bool Finite(const Vec3& v) { return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z); } +bool Finite(const Vec3& v) { return ::isfinite(v.x) && ::isfinite(v.y) && ::isfinite(v.z); } } // namespace int main() { PhysicsWorld probe; if (!probe.Initialize()) { - std::fprintf(stderr, "handling_test: physics unavailable, skipping\n"); + ::fprintf(stderr, "handling_test: physics unavailable, skipping\n"); return 0; } @@ -257,7 +257,7 @@ int main() { const f32 u = ZeroToSpeed(suv, t100, SurfaceType::kAsphalt, 45.0f); const f32 v = ZeroToSpeed(van, t100, SurfaceType::kAsphalt, 60.0f); const f32 sm = ZeroToSpeed(semi, t100, SurfaceType::kAsphalt, 90.0f); - std::fprintf(stderr, + ::fprintf(stderr, "(a) 0-100 km/h: sports=%.2fs muscle=%.2fs hatch=%.2fs suv=%.2fs van=%.2fs " "semi=%.2fs\n", s, m, h, u, v, sm); @@ -293,7 +293,7 @@ int main() { const f32 u = BrakeDistance(suv, entry); const f32 v = BrakeDistance(van, entry); const f32 sm = BrakeDistance(semi, entry); - std::fprintf(stderr, + ::fprintf(stderr, "(b) 100-0 braking: sports=%.1fm muscle=%.1fm hatch=%.1fm suv=%.1fm van=%.1fm " "semi=%.1fm\n", s, m, h, u, v, sm); @@ -312,10 +312,10 @@ int main() { const SteerResult u = StepSteer(suv, entry); const SteerResult v = StepSteer(van, entry); const SteerResult sm = StepSteer(semi, entry); - std::fprintf(stderr, + ::fprintf(stderr, "(c) step-steer roll: sports=%.1fdeg suv=%.1fdeg van=%.1fdeg semi=%.1fdeg\n", s.peak_roll_deg, u.peak_roll_deg, v.peak_roll_deg, sm.peak_roll_deg); - std::fprintf(stderr, + ::fprintf(stderr, "(c) speed lost: sports=%.1f suv=%.1f van=%.1f semi=%.1f m/s | heading chg: " "sports=%.0f van=%.0f semi=%.0f deg\n", s.speed_lost_mps, u.speed_lost_mps, v.speed_lost_mps, sm.speed_lost_mps, @@ -332,7 +332,7 @@ int main() { const f32 t40 = 40.0f * kKmh; const f32 u = ZeroToSpeed(suv, t40, SurfaceType::kGrass, 25.0f); const f32 m = ZeroToSpeed(muscle, t40, SurfaceType::kGrass, 25.0f); - std::fprintf(stderr, "(d) grass 0-40 km/h: AWD suv=%.2fs RWD muscle=%.2fs\n", u, m); + ::fprintf(stderr, "(d) grass 0-40 km/h: AWD suv=%.2fs RWD muscle=%.2fs\n", u, m); Check(u < m * 0.85f, "(d) AWD SUV not materially quicker than RWD muscle on grass"); } @@ -340,7 +340,7 @@ int main() { { const SlipResult h = CornerSlip(hatch); const SlipResult m = CornerSlip(muscle); - std::fprintf(stderr, + ::fprintf(stderr, "(e) corner slip (rad): hatch front=%.3f rear=%.3f | muscle front=%.3f rear=%.3f\n", h.front, h.rear, m.front, m.rear); Check(h.front > h.rear, "(e) FWD hatch front slip not above rear (no understeer)"); @@ -355,7 +355,7 @@ int main() { const f32 s_hi = LateralAccel(sports, hi); const f32 h_lo = LateralAccel(hatch, lo); const f32 h_hi = LateralAccel(hatch, hi); - std::fprintf(stderr, + ::fprintf(stderr, "(f) lat accel (m/s^2): sports 60=%.2f 120=%.2f (x%.2f) | hatch 60=%.2f 120=%.2f " "(x%.2f)\n", s_lo, s_hi, s_hi / s_lo, h_lo, h_hi, h_hi / h_lo); @@ -379,7 +379,7 @@ int main() { Vec3 pos{}; f32 rot[4]; world.GetVehicleTransform(id, &pos, rot); - const f32 rest_speed = std::fabs(world.VehicleForwardSpeed(id)); + const f32 rest_speed = ::fabs(world.VehicleForwardSpeed(id)); Check(Finite(pos), "(g) profile position went non-finite"); Check(rest_speed < 0.5f, "(g) profile did not settle at rest"); // Drive full throttle 2 s; must move and stay finite. @@ -388,10 +388,10 @@ int main() { world.Update(kDt); } world.GetVehicleTransform(id, &pos, rot); - Check(Finite(pos) && std::isfinite(world.VehicleForwardSpeed(id)), + Check(Finite(pos) && ::isfinite(world.VehicleForwardSpeed(id)), "(g) profile went non-finite under throttle"); Check(world.VehicleForwardSpeed(id) > 1.0f, "(g) profile did not accelerate"); - std::fprintf(stderr, "(g) %s: rest_speed=%.3f moved_to=%.1f m/s (finite)\n", it.name, + ::fprintf(stderr, "(g) %s: rest_speed=%.3f moved_to=%.1f m/s (finite)\n", it.name, rest_speed, world.VehicleForwardSpeed(id)); } // Surface respect: the sports car stops far longer on ice than asphalt, and @@ -416,9 +416,9 @@ int main() { world.DriveVehicle(id, 0.0f, 0.0f, 1.0f, 0.0f); world.Update(kDt); world.GetVehicleTransform(id, &b, r); - if (std::fabs(world.VehicleForwardSpeed(id)) < 0.5f) break; + if (::fabs(world.VehicleForwardSpeed(id)) < 0.5f) break; } - ice = std::sqrt((b.x - a.x) * (b.x - a.x) + (b.z - a.z) * (b.z - a.z)); + ice = ::sqrt((b.x - a.x) * (b.x - a.x) + (b.z - a.z) * (b.z - a.z)); } { PhysicsWorld world; @@ -439,19 +439,19 @@ int main() { world.DriveVehicle(id, 0.0f, 0.0f, 1.0f, 0.0f); world.Update(kDt); world.GetVehicleTransform(id, &b, r); - if (std::fabs(world.VehicleForwardSpeed(id)) < 0.5f) break; + if (::fabs(world.VehicleForwardSpeed(id)) < 0.5f) break; } - wet = std::sqrt((b.x - a.x) * (b.x - a.x) + (b.z - a.z) * (b.z - a.z)); + wet = ::sqrt((b.x - a.x) * (b.x - a.x) + (b.z - a.z) * (b.z - a.z)); } - std::fprintf(stderr, "(g) sports 80-0: dry=%.1fm ice=%.1fm wet=%.1fm\n", dry, ice, wet); + ::fprintf(stderr, "(g) sports 80-0: dry=%.1fm ice=%.1fm wet=%.1fm\n", dry, ice, wet); Check(ice > dry * 2.0f, "(g) ice not much longer than asphalt"); Check(wet > dry * 1.1f, "(g) wetness did not lengthen the asphalt stop"); } if (g_failures == 0) { - std::fprintf(stderr, "handling_test: all checks passed\n"); + ::fprintf(stderr, "handling_test: all checks passed\n"); return 0; } - std::fprintf(stderr, "handling_test: %d check(s) FAILED\n", g_failures); + ::fprintf(stderr, "handling_test: %d check(s) FAILED\n", g_failures); return 1; } diff --git a/test/http_test.cc b/test/http_test.cc index da744517..e7142807 100644 --- a/test/http_test.cc +++ b/test/http_test.cc @@ -12,15 +12,16 @@ #include #include -#include -#include -#include -#include -#include -#include +#include +#include #include +#include "base/atomic.h" +#include "base/memory/move.h" +#include "base/memory/unique_pointer.h" +#include "base/threading/thread.h" +#include "base/time/time.h" #include "http/http.h" #include "http/url.h" @@ -31,7 +32,7 @@ int g_failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++g_failures; \ } \ } while (0) @@ -218,7 +219,7 @@ class CannedServer { // what makes the client read a body across several calls: the chunked decoder // resumes from a cursor, and only a sliced body exercises that. CannedServer(base::String reply, bool reset = false, size_t slice = 0) - : reply_(std::move(reply)), reset_(reset), slice_(slice) { + : reply_(base::move(reply)), reset_(reset), slice_(slice) { listener_ = ::socket(AF_INET, SOCK_STREAM, 0); const int on = 1; ::setsockopt(listener_, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); @@ -231,25 +232,24 @@ class CannedServer { socklen_t len = sizeof(addr); ::getsockname(listener_, reinterpret_cast(&addr), &len); port_ = ntohs(addr.sin_port); - thread_ = std::thread([this] { Serve(); }); + thread_ = base::MakeUnique("canned_server", [this] { Serve(); }, + /*start_now=*/true); } ~CannedServer() { - if (thread_.joinable()) - thread_.join(); + thread_->Join(); ::close(listener_); } base::String url(const char* path = "/") const { char buffer[64] = {}; - std::snprintf(buffer, sizeof(buffer), "http://127.0.0.1:%u%s", unsigned(port_), path); + ::snprintf(buffer, sizeof(buffer), "http://127.0.0.1:%u%s", unsigned(port_), path); return base::String(buffer); } // What the client actually put on the wire, for the injection checks. base::String request() { - if (thread_.joinable()) - thread_.join(); + thread_->Join(); return request_; } @@ -268,7 +268,7 @@ class CannedServer { for (size_t at = 0; at < reply_.size(); at += slice_) { const size_t left = reply_.size() - at; ::send(client, reply_.c_str() + at, left < slice_ ? left : slice_, MSG_NOSIGNAL); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + base::SleepForMilliseconds(1); } } if (reset_) { @@ -285,7 +285,7 @@ class CannedServer { size_t slice_ = 0; int listener_ = -1; u16 port_ = 0; - std::thread thread_; + base::UniquePointer thread_; base::String request_; }; @@ -381,16 +381,14 @@ void TestBodyFraming() { http::Request request; request.url = server.url(); request.max_body_bytes = 32u * 1024 * 1024; // http.h invites raising it - const auto began = std::chrono::steady_clock::now(); + const base::TimeTicks began = base::TimeTicks::Now(); const http::Response r = http::Fetch(request); - const auto took = std::chrono::duration_cast( - std::chrono::steady_clock::now() - began) - .count(); + const i64 took = (base::TimeTicks::Now() - began).InMilliseconds(); CHECK_EQ(r.status, u16{200}); CHECK_EQ(r.body.size(), kChunk * kChunks); CHECK(took < 3000); if (took >= 3000) - std::printf(" chunked 16 MB took %lld ms\n", static_cast(took)); + ::printf(" chunked 16 MB took %lld ms\n", static_cast(took)); } { // A chunk that declares 2^64-1 bytes: the guards have to survive the @@ -464,35 +462,34 @@ class SilentServer { socklen_t len = sizeof(addr); ::getsockname(listener_, reinterpret_cast(&addr), &len); port_ = ntohs(addr.sin_port); - thread_ = std::thread([this] { + thread_ = base::MakeUnique("silent_server", [this] { const int client = ::accept(listener_, nullptr, nullptr); if (client < 0) return; // Hold the connection open, saying nothing, until the test is done. while (!done_.load()) - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + base::SleepForMilliseconds(10); ::close(client); - }); + }, /*start_now=*/true); } ~SilentServer() { done_ = true; - if (thread_.joinable()) - thread_.join(); + thread_->Join(); ::close(listener_); } base::String url() const { char buffer[64] = {}; - std::snprintf(buffer, sizeof(buffer), "http://127.0.0.1:%u/", unsigned(port_)); + ::snprintf(buffer, sizeof(buffer), "http://127.0.0.1:%u/", unsigned(port_)); return base::String(buffer); } private: int listener_ = -1; u16 port_ = 0; - std::atomic done_{false}; - std::thread thread_; + base::Atomic done_{false}; + base::UniquePointer thread_; }; void TestDeadline() { @@ -502,21 +499,19 @@ void TestDeadline() { request.timeout_ms = 30'000; // the idle timeout alone would hold for 30 s request.total_timeout_ms = 700; // the deadline is what ends this - const auto began = std::chrono::steady_clock::now(); + const base::TimeTicks began = base::TimeTicks::Now(); const http::Response r = http::Fetch(request); - const auto took = std::chrono::duration_cast( - std::chrono::steady_clock::now() - began) - .count(); + const i64 took = (base::TimeTicks::Now() - began).InMilliseconds(); CHECK_EQ(r.status, u16{0}); CHECK(!r.cancelled); // it ran out of time, nobody cancelled it CHECK(took >= 600 && took < 3000); if (took >= 3000) - std::printf(" deadline took %lld ms\n", static_cast(took)); + ::printf(" deadline took %lld ms\n", static_cast(took)); } void TestCancel() { SilentServer server; - std::atomic cancel{false}; + base::Atomic cancel{false}; http::Request request; request.url = server.url(); @@ -525,27 +520,25 @@ void TestCancel() { request.cancel = &cancel; // What a shutdown does: raise the flag on another thread and join. - std::thread raiser([&cancel] { - std::this_thread::sleep_for(std::chrono::milliseconds(300)); + base::Thread raiser("raiser", [&cancel] { + base::SleepForMilliseconds(300); cancel = true; - }); + }, /*start_now=*/true); - const auto began = std::chrono::steady_clock::now(); + const base::TimeTicks began = base::TimeTicks::Now(); const http::Response r = http::Fetch(request); - const auto took = std::chrono::duration_cast( - std::chrono::steady_clock::now() - began) - .count(); - raiser.join(); + const i64 took = (base::TimeTicks::Now() - began).InMilliseconds(); + raiser.Join(); CHECK_EQ(r.status, u16{0}); CHECK(r.cancelled); // the caller's doing, not the peer's: do not log a fault // Within a wake tick or so of the flag going up, not 30 seconds later. CHECK(took < 2000); if (took >= 2000) - std::printf(" cancel took %lld ms\n", static_cast(took)); + ::printf(" cancel took %lld ms\n", static_cast(took)); // A flag already raised means the call never opens a socket at all. - std::atomic already{true}; + base::Atomic already{true}; http::Request second = request; second.cancel = &already; const http::Response none = http::Fetch(second); @@ -555,25 +548,25 @@ void TestCancel() { // Off by default: reaches the network, so it only runs when asked. void TestLive() { - const char* target = std::getenv("RX_HTTP_LIVE"); + const char* target = ::getenv("RX_HTTP_LIVE"); if (target == nullptr || *target == '\0') return; - std::printf("http_test: live fetch of %s (tls backend: %s)\n", target, + ::printf("http_test: live fetch of %s (tls backend: %s)\n", target, http::TlsAvailable() ? "yes" : "no"); const http::Response response = http::Get(target); - std::printf(" status %u, %zu bytes, error '%s'\n", unsigned(response.status), + ::printf(" status %u, %zu bytes, error '%s'\n", unsigned(response.status), size_t(response.body.size()), response.error.c_str()); // The first line or so of the body, which is how a probe endpoint (say // howsmyssl) is read back: the answer is the point of the fetch. The whole // body goes to RX_HTTP_LIVE_OUT when the interesting part is further in. if (!response.body.empty()) { const size_t shown = response.body.size() < 240 ? response.body.size() : 240; - std::printf(" body: %.*s\n", int(shown), response.body.c_str()); - if (const char* out = std::getenv("RX_HTTP_LIVE_OUT"); out != nullptr && *out != '\0') { - if (std::FILE* file = std::fopen(out, "wb"); file != nullptr) { - std::fwrite(response.body.c_str(), 1, response.body.size(), file); - std::fclose(file); - std::printf(" body written to %s\n", out); + ::printf(" body: %.*s\n", int(shown), response.body.c_str()); + if (const char* out = ::getenv("RX_HTTP_LIVE_OUT"); out != nullptr && *out != '\0') { + if (FILE* file = ::fopen(out, "wb"); file != nullptr) { + ::fwrite(response.body.c_str(), 1, response.body.size(), file); + ::fclose(file); + ::printf(" body written to %s\n", out); } } } @@ -598,9 +591,9 @@ int main() { TestCancel(); TestLive(); if (g_failures == 0) { - std::printf("http_test: all passed\n"); + ::printf("http_test: all passed\n"); return 0; } - std::printf("http_test: %d failure(s)\n", g_failures); + ::printf("http_test: %d failure(s)\n", g_failures); return 1; } diff --git a/test/human_brdf_test.cc b/test/human_brdf_test.cc index 82715221..267c5fb9 100644 --- a/test/human_brdf_test.cc +++ b/test/human_brdf_test.cc @@ -1,9 +1,10 @@ +#include "core/scalar.h" #include "render/pipeline/human_material.h" -#include -#include -#include -#include +#include +#include +#include +#include // Regression tests for the character surface model. The contract these defend // is the one the whole thing rests on: @@ -27,7 +28,7 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "human_brdf_test: FAIL: %s\n", message); + ::fprintf(stderr, "human_brdf_test: FAIL: %s\n", message); ++failures; } @@ -36,12 +37,12 @@ struct V3 { }; V3 Norm(V3 v) { - rx::f32 l = std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z); + rx::f32 l = ::sqrt(v.x * v.x + v.y * v.y + v.z * v.z); return {v.x / l, v.y / l, v.z / l}; } V3 Spherical(rx::f32 theta, rx::f32 phi) { - return {std::sin(theta) * std::cos(phi), std::cos(theta), std::sin(theta) * std::sin(phi)}; + return {::sin(theta) * ::cos(phi), ::cos(theta), ::sin(theta) * ::sin(phi)}; } rx::f32 Sum(const rx::f32 v[3]) { return v[0] + v[1] + v[2]; } @@ -79,15 +80,15 @@ int main() { HumanEvaluateCpu(neutral, base, roughness, f0, n, n, n, v, l, 0.4f, 0.0f); const HumanBrdfSample s = StockBrdfCpu(base, roughness, f0, n, v, l); for (int c = 0; c < 3; ++c) { - worst = std::max(worst, std::abs(h.diffuse[c] - s.diffuse[c])); - worst = std::max(worst, std::abs(h.specular[c] - s.specular[c])); + worst = rx::Max(worst, ::abs(h.diffuse[c] - s.diffuse[c])); + worst = rx::Max(worst, ::abs(h.specular[c] - s.specular[c])); } Check(Sum(h.transmission) == 0.0f, "neutral transmission is exactly zero"); } } } Check(worst < 1e-6f, "the neutral set reproduces stock Lambert + GGX"); - if (worst >= 1e-6f) std::fprintf(stderr, " worst neutral delta: %g\n", worst); + if (worst >= 1e-6f) ::fprintf(stderr, " worst neutral delta: %g\n", worst); } const rx::f32 v_side[3] = {0.5f, 0.6f, 0.62f}; @@ -104,7 +105,7 @@ int main() { HumanEvaluateCpu(p, base, 0.4f, f0, n, n, n, v_side, l_graze, 0.0f, 0.0f); Check(Sum(lifted.diffuse) > Sum(zero.diffuse), "diffuse Fresnel lifts the grazing diffuse"); - Check(std::abs(Sum(lifted.specular) - Sum(zero.specular)) < 1e-7f, + Check(::abs(Sum(lifted.specular) - Sum(zero.specular)) < 1e-7f, "diffuse Fresnel does not touch the specular lobe"); p = HumanNeutral(); @@ -112,7 +113,7 @@ int main() { const HumanBrdfSample retro = HumanEvaluateCpu(p, base, 0.4f, f0, n, n, n, v_side, l_graze, 0.0f, 0.0f); Check(Sum(retro.diffuse) > Sum(zero.diffuse), "retroreflection lifts the back-scatter"); - Check(std::abs(Sum(retro.specular) - Sum(zero.specular)) < 1e-7f, + Check(::abs(Sum(retro.specular) - Sum(zero.specular)) < 1e-7f, "retroreflection does not touch the specular lobe"); p = HumanNeutral(); @@ -121,7 +122,7 @@ int main() { HumanEvaluateCpu(p, base, 0.4f, f0, n, n, n, v_side, l_graze, 0.0f, 0.0f); Check(Sum(fres.specular) > Sum(zero.specular), "a lower specular Fresnel exponent raises grazing reflectance"); - Check(std::abs(Sum(fres.diffuse) - Sum(zero.diffuse)) < 1e-7f, + Check(::abs(Sum(fres.diffuse) - Sum(zero.diffuse)) < 1e-7f, "the specular Fresnel exponent does not touch the diffuse lobe"); } @@ -146,7 +147,7 @@ int main() { const rx::f32 l[3] = {ll.x, ll.y, ll.z}; const HumanBrdfSample s = HumanEvaluateCpu(p, base, 0.5f, f0, n, n, n, v_side, l, 0.0f, 0.0f); - total += static_cast(s.diffuse[0]) * std::sin(theta); + total += static_cast(s.diffuse[0]) * ::sin(theta); } return total * (kPi / steps) * 2.0 * kPi; }; @@ -179,7 +180,7 @@ int main() { HumanEvaluateCpu(single, base, 0.3f, f0, n, n, n, v_side, l_45, 0.0f, 0.0f); const HumanBrdfSample b = HumanEvaluateCpu(dual, base, 0.3f, f0, n, n, n, v_side, l_45, 0.0f, 0.0f); - Check(std::abs(Sum(a.diffuse) - Sum(b.diffuse)) < 1e-7f, + Check(::abs(Sum(a.diffuse) - Sum(b.diffuse)) < 1e-7f, "the second specular lobe does not touch the diffuse lobe"); // Integrated over the hemisphere the two lobes must carry the same energy; @@ -196,7 +197,7 @@ int main() { const rx::f32 l[3] = {ll.x, ll.y, ll.z}; const HumanBrdfSample s = HumanEvaluateCpu(p, base, 0.3f, f0, n, n, n, v_side, l, 0.0f, 0.0f); - total += static_cast(s.specular[0]) * std::sin(theta); + total += static_cast(s.specular[0]) * ::sin(theta); } } return total; @@ -212,8 +213,8 @@ int main() { const double e_single = spec_energy(single); const double e_dual = spec_energy(dual); const double e_tail = spec_energy(tail_only); - const double lo = std::min(e_single, e_tail); - const double hi = std::max(e_single, e_tail); + const double lo = rx::Min(e_single, e_tail); + const double hi = rx::Max(e_single, e_tail); Check(e_dual >= lo - hi * 1e-6 && e_dual <= hi + hi * 1e-6, "the second lobe redistributes specular energy rather than adding it"); Check(e_dual > lo && e_dual < hi, @@ -228,9 +229,9 @@ int main() { HumanEvaluateCpu(p, base, 0.25f, f0, n, n, n, v_side, l_45, 0.0f, 0.0f); const HumanBrdfSample split = HumanEvaluateCpu(p, base, 0.25f, f0, n, n, ns, v_side, l_45, 0.0f, 0.0f); - Check(std::abs(Sum(shared_n.diffuse) - Sum(split.diffuse)) < 1e-7f, + Check(::abs(Sum(shared_n.diffuse) - Sum(split.diffuse)) < 1e-7f, "a specular-only normal leaves the diffuse lobe alone"); - Check(std::abs(Sum(shared_n.specular) - Sum(split.specular)) > 1e-6f, + Check(::abs(Sum(shared_n.specular) - Sum(split.specular)) > 1e-6f, "a specular-only normal moves the highlight"); } @@ -371,18 +372,18 @@ int main() { const rx::f32 theta = kPi * 0.5f - static_cast(i) * (kPi * 0.5f / 64.0f); const V3 ll = Spherical(theta, 0.0f); const rx::f32 rep[3] = {ll.x, ll.y, ll.z}; - worst = std::max(worst, HumanTerminatorMultiplierCpu(p, n, n, rep)); + worst = rx::Max(worst, HumanTerminatorMultiplierCpu(p, n, n, rep)); } Check(worst <= 2.0f, "the area-light terminator multiplier is bounded"); - if (worst > 2.0f) std::fprintf(stderr, " region %d worst multiplier: %g\n", ri, worst); + if (worst > 2.0f) ::fprintf(stderr, " region %d worst multiplier: %g\n", ri, worst); } // Neutral has nothing to soften, so the multiplier must be exactly 1 - // otherwise turning the model on re-shades every area-lit surface. const HumanSurfaceParameters neutral = HumanNeutral(); - Check(std::abs(HumanTerminatorMultiplierCpu(neutral, n, n, rep_grazing) - 1.0f) < 1e-6f, + Check(::abs(HumanTerminatorMultiplierCpu(neutral, n, n, rep_grazing) - 1.0f) < 1e-6f, "the neutral set leaves area-light diffuse untouched"); } - if (failures == 0) std::fprintf(stderr, "human_brdf_test: all checks passed\n"); + if (failures == 0) ::fprintf(stderr, "human_brdf_test: all checks passed\n"); return failures == 0 ? 0 : 1; } diff --git a/test/input_touch_sdl_test.cc b/test/input_touch_sdl_test.cc index bfdad4ba..4a7a830e 100644 --- a/test/input_touch_sdl_test.cc +++ b/test/input_touch_sdl_test.cc @@ -12,8 +12,8 @@ // exactly as from a panel; no touchscreen or uinput needed. Needs a display to // open a window; skips cleanly without one. -#include -#include +#include +#include #include @@ -24,13 +24,13 @@ namespace { int g_failures = 0; void Check(const char* what, bool ok) { - std::printf(" [%s] %s\n", ok ? "ok" : "FAIL", what); + ::printf(" [%s] %s\n", ok ? "ok" : "FAIL", what); if (!ok) ++g_failures; } bool Near(rx::f32 a, rx::f32 b, rx::f32 eps = 1.0f) { - return std::fabs(a - b) < eps; // a pixel of slack: normalized -> pixel rounds + return ::fabs(a - b) < eps; // a pixel of slack: normalized -> pixel rounds } constexpr SDL_TouchID kTouch = 1; @@ -56,7 +56,7 @@ void PushFinger(Uint32 type, SDL_FingerID finger, float nx, float ny, float pres } // namespace int main() { - std::printf("sdl finger event translation\n"); + ::printf("sdl finger event translation\n"); rx::WindowDesc desc; desc.width = 640; @@ -67,7 +67,7 @@ int main() { auto window = rx::Window::Create(desc); if (!window || !window->native_handles().window) { - std::printf(" no display available, skipping\n"); + ::printf(" no display available, skipping\n"); return 0; } @@ -80,10 +80,10 @@ int main() { const rx::f32 w = static_cast(window->width()); const rx::f32 h = static_cast(window->height()); const rx::f32 lw = static_cast(layout_w); - std::printf(" window is %.0fx%.0f pixels, laid out at %dx%d (density %.2f)\n", w, h, layout_w, + ::printf(" window is %.0fx%.0f pixels, laid out at %dx%d (density %.2f)\n", w, h, layout_w, layout_h, static_cast(window->pixel_density())); if (w <= 0 || h <= 0) { - std::printf(" degenerate window, skipping\n"); + ::printf(" degenerate window, skipping\n"); return 0; } @@ -102,7 +102,7 @@ int main() { if (!Near(lw, w)) Check("scaled by the pixel size, not the layout size", !Near(t.points[0].x, 0.25f * lw)); else - std::printf(" [--] unscaled display, the two sizes cannot be told apart here\n"); + ::printf(" [--] unscaled display, the two sizes cannot be told apart here\n"); Check("press edge reported", t.points[0].pressed); Check("pressure carried through", Near(t.points[0].pressure, 1.0f, 0.01f)); Check("id carried through", t.points[0].id == static_cast(kFinger)); @@ -145,9 +145,9 @@ int main() { Check("second contact scaled independently", Near(window->touch().Find(2)->x, 0.9f * w)); if (g_failures != 0) { - std::printf("%d check(s) failed\n", g_failures); + ::printf("%d check(s) failed\n", g_failures); return 1; } - std::printf("all checks passed\n"); + ::printf("all checks passed\n"); return 0; } diff --git a/test/input_touch_test.cc b/test/input_touch_test.cc index 52b6da33..d8068550 100644 --- a/test/input_touch_test.cc +++ b/test/input_touch_test.cc @@ -9,8 +9,8 @@ // panel must not open a slot nothing can ever release. Needs no window, so it // runs in the ctest gate. -#include -#include +#include +#include #include "core/input.h" @@ -19,13 +19,13 @@ namespace { int g_failures = 0; void Check(const char* what, bool ok) { - std::printf(" [%s] %s\n", ok ? "ok" : "FAIL", what); + ::printf(" [%s] %s\n", ok ? "ok" : "FAIL", what); if (!ok) ++g_failures; } bool Near(rx::f32 a, rx::f32 b) { - return std::fabs(a - b) < 1e-4f; + return ::fabs(a - b) < 1e-4f; } using rx::TouchState; @@ -34,7 +34,7 @@ using Phase = rx::TouchState::Phase; // One finger: down, drag, lift. The lift has to survive a pump so a frame that // only samples state still sees the tap end. void TestSingleContactLifecycle() { - std::printf("single contact lifecycle\n"); + ::printf("single contact lifecycle\n"); TouchState t; t.BeginPump(); @@ -73,7 +73,7 @@ void TestSingleContactLifecycle() { // Slots must compact, and the survivor must keep its own identity/position. void TestCompactionKeepsSurvivors() { - std::printf("compaction keeps survivors\n"); + ::printf("compaction keeps survivors\n"); TouchState t; t.BeginPump(); @@ -97,7 +97,7 @@ void TestCompactionKeepsSurvivors() { // Events for ids we never saw must not corrupt anything. void TestUnknownIdsIgnored() { - std::printf("unknown ids ignored\n"); + ::printf("unknown ids ignored\n"); TouchState t; t.BeginPump(); @@ -114,7 +114,7 @@ void TestUnknownIdsIgnored() { // More fingers than we track must drop cleanly rather than overrun the array. void TestOverflowDropsCleanly() { - std::printf("overflow drops cleanly\n"); + ::printf("overflow drops cleanly\n"); TouchState t; t.BeginPump(); @@ -133,7 +133,7 @@ void TestOverflowDropsCleanly() { // A canceled gesture reports kUp, so consumers only handle one end state. void TestCancelEndsContact() { - std::printf("cancel ends the contact\n"); + ::printf("cancel ends the contact\n"); TouchState t; t.BeginPump(); @@ -151,7 +151,7 @@ void TestCancelEndsContact() { // lift resolves to the first slot) and so never released, and BeginPump only // reclaims released slots, so it would sit there claiming a finger forever. void TestDuplicateDownDoesNotStrand() { - std::printf("duplicate down does not strand a contact\n"); + ::printf("duplicate down does not strand a contact\n"); TouchState t; t.BeginPump(); @@ -172,7 +172,7 @@ void TestDuplicateDownDoesNotStrand() { // end still has to be visible, and the new contact must get that pump's motion // rather than have it applied to the slot that already ended. void TestRedownWithinAPump() { - std::printf("re-down within one pump\n"); + ::printf("re-down within one pump\n"); TouchState t; t.BeginPump(); @@ -194,7 +194,7 @@ void TestRedownWithinAPump() { } // namespace int main() { - std::printf("touch contact state machine\n"); + ::printf("touch contact state machine\n"); TestSingleContactLifecycle(); TestCompactionKeepsSurvivors(); TestUnknownIdsIgnored(); @@ -204,9 +204,9 @@ int main() { TestRedownWithinAPump(); if (g_failures != 0) { - std::printf("%d check(s) failed\n", g_failures); + ::printf("%d check(s) failed\n", g_failures); return 1; } - std::printf("all checks passed\n"); + ::printf("all checks passed\n"); return 0; } diff --git a/test/instance_store_test.cc b/test/instance_store_test.cc index 4c042027..6b9a839e 100644 --- a/test/instance_store_test.cc +++ b/test/instance_store_test.cc @@ -1,10 +1,14 @@ #include "render/geometry/instance_store.h" -#include -#include -#include -#include - +#include +#include +#include +#include + +#include "base/containers/span.h" +#include "base/functional/function.h" +#include "base/memory/mem_ops.h" +#include "base/memory/unique_pointer.h" #include "render/rhi/swapchain.h" namespace { @@ -19,7 +23,7 @@ class TestDevice final : public Device { void WaitIdle() override {} bool RecreateSurface(Window&) override { return false; } void DestroySurface() override {} - std::unique_ptr CreateSwapchain(u32, u32, bool, bool) override { return {}; } + base::UniquePointer CreateSwapchain(u32, u32, bool, bool) override { return {}; } MemoryBudget memory_budget() const override { return {}; } GpuBuffer CreateBuffer(rx::u64 size, BufferUsageFlags, bool) override { @@ -30,7 +34,7 @@ class TestDevice final : public Device { GpuBuffer CreateBufferWithData(ByteSpan data, BufferUsageFlags usage) override { ++data_uploads_; GpuBuffer buffer = CreateBuffer(data.size(), usage, true); - std::memcpy(buffer.mapped, data.data(), data.size()); + base::MemCopy(buffer.mapped, data.data(), data.size()); return buffer; } void DestroyBuffer(GpuBuffer& buffer) override { @@ -55,7 +59,7 @@ class TestDevice final : public Device { void DestroyBindingLayout(BindingLayoutHandle) override {} BindingSetHandle CreateBindingSet(BindingLayoutHandle, u32) override { return {}; } void DestroyBindingSet(BindingSetHandle) override {} - void UpdateBindingSet(BindingSetHandle, std::span) override {} + void UpdateBindingSet(BindingSetHandle, base::Span) override {} AccelSizes GetBlasSizes(const BlasBuildDesc&) override { return {}; } AccelSizes GetTlasSizes(u32) override { return {}; } AccelStructHandle CreateAccelStruct(AccelStructType, rx::u64) override { return {}; } @@ -64,7 +68,7 @@ class TestDevice final : public Device { TimestampPoolHandle CreateTimestampPool(u32) override { return {}; } void DestroyTimestampPool(TimestampPoolHandle) override {} bool GetTimestamps(TimestampPoolHandle, u32, u32, rx::u64*) override { return false; } - void ImmediateSubmit(const std::function&) override {} + void ImmediateSubmit(const base::Function&) override {} CommandList* BeginFrame(u32) override { return nullptr; } PresentResult SubmitFrame(CommandList*, Swapchain&, u32) override { return PresentResult::kFailed; @@ -78,14 +82,14 @@ class TestDevice final : public Device { size_t data_uploads_ = 0; }; -bool Near(f32 a, f32 b) { return std::abs(a - b) < 1e-5f; } +bool Near(f32 a, f32 b) { return ::abs(a - b) < 1e-5f; } f32 TranslationX(const GpuBuffer& buffer, size_t index = 0) { return static_cast(buffer.mapped)[index].m[12]; } int Fail(const char* message) { - std::fprintf(stderr, "instance_store_test: FAIL: %s\n", message); + ::fprintf(stderr, "instance_store_test: FAIL: %s\n", message); return 1; } @@ -112,7 +116,7 @@ int main() { } Mat4 replacement = MakeTranslation({20.0f, 0.0f, 0.0f}); - const std::span replacement_span{&replacement, 1}; + const base::Span replacement_span{&replacement, 1}; if (!store.Replace(device, first, replacement_span, mesh_center, 2.0f)) return Fail("replace"); if (store.instance_count() != 1 || device.live_buffers() != 1) @@ -128,7 +132,7 @@ int main() { return Fail("submission did not establish instance history"); Mat4 moved = MakeTranslation({30.0f, 0.0f, 0.0f}); - if (!store.Replace(device, first, std::span{&moved, 1}, mesh_center, 2.0f)) + if (!store.Replace(device, first, base::Span{&moved, 1}, mesh_center, 2.0f)) return Fail("submitted replacement"); const InstanceStore::Group& moving = store.groups()[first.index]; if (!moving.previous_buffer || device.live_buffers() != 2 || @@ -137,7 +141,7 @@ int main() { return Fail("replacement motion streams"); moved = MakeTranslation({40.0f, 0.0f, 0.0f}); - if (!store.Replace(device, first, std::span{&moved, 1}, mesh_center, 2.0f)) + if (!store.Replace(device, first, base::Span{&moved, 1}, mesh_center, 2.0f)) return Fail("repeated replacement"); const InstanceStore::Group& repeated = store.groups()[first.index]; if (device.live_buffers() != 2 || !Near(TranslationX(repeated.previous_buffer), 20.0f) || @@ -195,6 +199,6 @@ int main() { store.Shutdown(device); if (device.live_buffers() != 0) return Fail("shutdown buffer ownership"); - std::printf("instance_store_test: PASS\n"); + ::printf("instance_store_test: PASS\n"); return 0; } diff --git a/test/inventory_test.cc b/test/inventory_test.cc index f4ceaff9..59bdd803 100644 --- a/test/inventory_test.cc +++ b/test/inventory_test.cc @@ -2,10 +2,11 @@ // equip/unequip, and the world-item lifecycle on real Jolt (drop -> settle -> // pick up / hibernate / wake) plus save/load roundtrips for inventories AND // world items (including hibernated records). -#include -#include -#include +#include +#include +#include +#include "base/containers/vector.h" #include "ecs/world.h" #include "inventory/inventory.h" #include "inventory/item_catalog.h" @@ -21,7 +22,7 @@ int g_failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++g_failures; \ } \ } while (0) @@ -147,7 +148,7 @@ void TestInventorySaveLoad() { a.Add(e1, inv1); AddItem(*a.Get(e1), catalog, gem, 3); - std::vector blob = SaveInventories(a); + base::Vector blob = SaveInventories(a); CHECK(!blob.empty()); // Load into a fresh world; entities are recreated by Guid. @@ -170,7 +171,7 @@ void TestInventorySaveLoad() { CHECK(seen == 2); // Corrupt blob is rejected. - std::vector junk = {0, 1, 2, 3}; + base::Vector junk = {0, 1, 2, 3}; CHECK(LoadInventories(b, junk) == false); } @@ -262,7 +263,7 @@ void TestHibernateWakeAndWorldSave() { CHECK(live == 0); // Save while one item is hibernated; then wake near the origin. - std::vector blob = SaveWorldItems(world, store); + base::Vector blob = SaveWorldItems(world, store); WakeWorldItemsNear(world, physics, catalog, store, Vec3{0, 0, 0}, 40.0f); CHECK(store.size() == 0); @@ -273,7 +274,7 @@ void TestHibernateWakeAndWorldSave() { CHECK(wi.body != 0); }); CHECK(woken.index != 0xffffffff); - CHECK(std::abs(world.Get(woken)->position[1] - rest_y) < 0.01f); + CHECK(::abs(world.Get(woken)->position[1] - rest_y) < 0.01f); // Load the saved blob (item was hibernated at save time) into a fresh world: // it restores as a dormant store record with the same persistent id. @@ -299,7 +300,7 @@ void TestHibernateWakeAndWorldSave() { DropItem(live_world, physics, catalog, live_dropper, 0, 1, s2, Vec3{0, 0, 0}); rx::u64 live_pid = live_world.Get(live_item)->persistent_id; WorldItemStore empty_store; - std::vector live_blob = SaveWorldItems(live_world, empty_store); + base::Vector live_blob = SaveWorldItems(live_world, empty_store); ecs::World reload; WorldItemStore reload_store; @@ -339,7 +340,7 @@ void TestCorruptWorldItemRejection() { DropItem(src, physics, catalog, dropper, 0, 1, s, Vec3{0, 0, 0}); DropItem(src, physics, catalog, dropper, 0, 1, s, Vec3{0, 0, 0}); WorldItemStore empty; - std::vector blob = SaveWorldItems(src, empty); + base::Vector blob = SaveWorldItems(src, empty); CHECK(blob.size() > 16); const u32 bodies_before = physics.dynamic_body_count(); @@ -349,7 +350,7 @@ void TestCorruptWorldItemRejection() { // (which must fail after full parse, before any commit). Every case must be // rejected with no entity and no store record created. for (size_t cut = 8; cut < blob.size(); ++cut) { - std::vector truncated(blob.begin(), blob.begin() + cut); + base::Vector truncated(blob.begin(), blob.begin() + cut); ecs::World dst; WorldItemStore store; CHECK(LoadWorldItems(dst, physics, catalog, store, truncated) == false); @@ -374,9 +375,9 @@ int main() { TestCorruptWorldItemRejection(); if (g_failures == 0) { - std::printf("inventory_test: all checks passed\n"); + ::printf("inventory_test: all checks passed\n"); return 0; } - std::fprintf(stderr, "inventory_test: %d checks failed\n", g_failures); + ::fprintf(stderr, "inventory_test: %d checks failed\n", g_failures); return 1; } diff --git a/test/jetpack_test.cc b/test/jetpack_test.cc index e445bdc1..f29b86e4 100644 --- a/test/jetpack_test.cc +++ b/test/jetpack_test.cc @@ -10,12 +10,13 @@ // (e) airborne WASD accelerates horizontally faster burning than in free fall. // (f) NaN-free under random input, and the plain jump still works pack-off. -#include -#include +#include +#include #include "character/character.h" #include "character/jetpack.h" #include "core/math.h" +#include "core/scalar.h" #include "ecs/world.h" #include "physics/physics_world.h" #include "scene/components.h" @@ -30,7 +31,7 @@ namespace { constexpr f32 kDt = 1.0f / 60.0f; int Fail(const char* what) { - std::fprintf(stderr, "jetpack_test FAIL: %s\n", what); + ::fprintf(stderr, "jetpack_test FAIL: %s\n", what); return 1; } @@ -46,7 +47,7 @@ struct Scene { physics.AddStaticBox({0, -0.5f, 0}, {200, 0.5f, 200}); // ground top at y = 0 CharacterShape shape; - const f32 half = std::max(shape.standing_height * 0.5f - shape.standing_radius, 0.01f); + const f32 half = rx::Max(shape.standing_height * 0.5f - shape.standing_radius, 0.01f); const f32 total_half = half + shape.standing_radius; const Vec3 center = feet + Vec3{0, total_half, 0}; physics::CharacterId id = physics.CreateCharacter(center, shape.standing_radius, half); @@ -87,7 +88,7 @@ struct Scene { } }; -f32 HSpeed(const Vec3& v) { return std::sqrt(v.x * v.x + v.z * v.z); } +f32 HSpeed(const Vec3& v) { return ::sqrt(v.x * v.x + v.z * v.z); } } // namespace @@ -95,7 +96,7 @@ int main() { { physics::PhysicsWorld probe; if (!probe.Initialize()) { - std::fprintf(stderr, "jetpack_test: physics stub, skipping\n"); + ::fprintf(stderr, "jetpack_test: physics stub, skipping\n"); return 0; } } @@ -113,10 +114,10 @@ int main() { s.jin().enabled = true; s.jin().thrust = true; s.Step(1); - peak = std::max(peak, s.feet_y()); + peak = rx::Max(peak, s.feet_y()); } const bool airborne = !s.state().grounded; - std::fprintf(stderr, "(a) climb: y0=%.2f peak=%.2f gain=%.2f m airborne=%d fuel=%.2f\n", y0, + ::fprintf(stderr, "(a) climb: y0=%.2f peak=%.2f gain=%.2f m airborne=%d fuel=%.2f\n", y0, peak, peak - y0, airborne, s.jst().fuel); if (peak - y0 < 1.5f) return Fail("(a) jetpack did not lift the character off"); if (peak - y0 > 80.0f) return Fail("(a) climb implausibly large"); @@ -134,13 +135,13 @@ int main() { s.jin().enabled = true; s.jin().thrust = true; s.Step(1); - peak = std::max(peak, s.feet_y()); + peak = rx::Max(peak, s.feet_y()); if (empty_step < 0 && s.jst().fuel <= 0.0f) empty_step = i; } const f32 t_empty = empty_step / 60.0f; // After the tank is dry, thrust must have died and the character fallen. const f32 y_after = s.feet_y(); - std::fprintf(stderr, "(b) empty at %.2f s (cap %.1f) thrust=%.2f peak=%.2f y_after=%.2f\n", + ::fprintf(stderr, "(b) empty at %.2f s (cap %.1f) thrust=%.2f peak=%.2f y_after=%.2f\n", t_empty, jp.fuel_capacity_s, s.jst().thrust, peak, y_after); if (empty_step < 0) return Fail("(b) tank never emptied"); if (t_empty < jp.fuel_capacity_s * 0.8f || t_empty > jp.fuel_capacity_s * 1.6f) @@ -179,7 +180,7 @@ int main() { s.Step(1); fuel_air = s.jst().fuel; } - std::fprintf(stderr, "(c) fuel low=%.2f -> grounded=%.2f ; airborne stays=%.2f\n", fuel_low, + ::fprintf(stderr, "(c) fuel low=%.2f -> grounded=%.2f ; airborne stays=%.2f\n", fuel_low, fuel_grounded, fuel_air); if (fuel_grounded <= fuel_low + 0.05f) return Fail("(c) grounded refuel did not restore fuel"); if (fuel_air > 0.5f + 1e-3f) return Fail("(c) airborne wrongly refuelled"); @@ -203,7 +204,7 @@ int main() { if (i == n_early) thrust_early = s.jst().thrust; if (i == n_spool) thrust_spool = s.jst().thrust; } - std::fprintf(stderr, "(d) thrust @ t/3=%.2f @ spool_time=%.2f (target 0.90)\n", thrust_early, + ::fprintf(stderr, "(d) thrust @ t/3=%.2f @ spool_time=%.2f (target 0.90)\n", thrust_early, thrust_spool); if (thrust_early > 0.75f) return Fail("(d) thrust rose too fast (no spool lag)"); if (thrust_spool < 0.82f || thrust_spool > 0.97f) @@ -232,7 +233,7 @@ int main() { }; const f32 drift = lateral_gain(false); const f32 powered = lateral_gain(true); - std::fprintf(stderr, "(e) lateral speed free-fall=%.2f burning=%.2f m/s\n", drift, powered); + ::fprintf(stderr, "(e) lateral speed free-fall=%.2f burning=%.2f m/s\n", drift, powered); if (powered <= drift + 0.5f) return Fail("(e) burning did not out-accelerate free-fall drift horizontally"); } @@ -244,16 +245,16 @@ int main() { s.Step(20); for (int i = 0; i < 60 * 20; ++i) { const f32 t = i * kDt; - s.jin().enabled = std::sin(t * 3.1f) > 0.0f; - s.jin().thrust = std::sin(t * 7.3f) > 0.0f; - s.intent().move = {std::sin(t * 2.0f), 0, std::cos(t * 1.3f)}; - if (std::sin(t * 5.0f) > 0.9f) s.intent().jump = true; + s.jin().enabled = ::sin(t * 3.1f) > 0.0f; + s.jin().thrust = ::sin(t * 7.3f) > 0.0f; + s.intent().move = {::sin(t * 2.0f), 0, ::cos(t * 1.3f)}; + if (::sin(t * 5.0f) > 0.9f) s.intent().jump = true; s.Step(1); - if (!std::isfinite(s.feet_y()) || !std::isfinite(s.jst().fuel) || - !std::isfinite(s.state().velocity.y)) + if (!::isfinite(s.feet_y()) || !::isfinite(s.jst().fuel) || + !::isfinite(s.state().velocity.y)) return Fail("(f) NaN under random input"); } - std::fprintf(stderr, "(f) random 20 s: finite, fuel=%.2f y=%.2f\n", s.jst().fuel, s.feet_y()); + ::fprintf(stderr, "(f) random 20 s: finite, fuel=%.2f y=%.2f\n", s.jst().fuel, s.feet_y()); // Pack OFF: a plain jump must still leave the ground and come back. Scene j; @@ -265,9 +266,9 @@ int main() { f32 jump_peak = ground_y; for (int i = 0; i < 90; ++i) { j.Step(1); - jump_peak = std::max(jump_peak, j.feet_y()); + jump_peak = rx::Max(jump_peak, j.feet_y()); } - std::fprintf(stderr, "(f) pack-off jump: ground=%.2f peak=%.2f (height ~%.2f m)\n", ground_y, + ::fprintf(stderr, "(f) pack-off jump: ground=%.2f peak=%.2f (height ~%.2f m)\n", ground_y, jump_peak, jump_peak - ground_y); if (jump_peak - ground_y < 0.6f) return Fail("(f) plain jump broken with pack off"); if (!j.state().grounded) return Fail("(f) character never landed after the jump"); @@ -299,10 +300,10 @@ int main() { f32 vmax_after = 0; for (int i = 0; i < 60 * 2; ++i) { s.Step(1); - peak_after = std::max(peak_after, s.feet_y()); - vmax_after = std::max(vmax_after, s.state().velocity.y); + peak_after = rx::Max(peak_after, s.feet_y()); + vmax_after = rx::Max(vmax_after, s.state().velocity.y); } - std::fprintf(stderr, + ::fprintf(stderr, "(g) ceiling: blocked_y=%.2f post-removal peak=%.2f (gain %.2f) vmax_up=%.2f\n", blocked_y, peak_after, peak_after - blocked_y, vmax_after); if (blocked_y < 0.5f) return Fail("(g) thrust did not press the character up under the ceiling"); @@ -312,6 +313,6 @@ int main() { if (vmax_after > 3.0f) return Fail("(g) super-jump: retained upward velocity too high"); } - std::fprintf(stderr, "jetpack_test: all checks passed\n"); + ::fprintf(stderr, "jetpack_test: all checks passed\n"); return 0; } diff --git a/test/kite_test.cc b/test/kite_test.cc index 26d4ce60..1bd2d1cd 100644 --- a/test/kite_test.cc +++ b/test/kite_test.cc @@ -10,10 +10,11 @@ // (e) anchor towed at 8 m/s in calm air flies the kite on apparent wind. // (f) 60 s of gusting wind is NaN-free and never exceeds the tension cap. -#include -#include +#include +#include #include "core/math.h" +#include "core/scalar.h" #include "physics/kite.h" #include "physics/physics_world.h" @@ -30,7 +31,7 @@ namespace { constexpr f32 kDt = 1.0f / 60.0f; int Fail(const char* what) { - std::fprintf(stderr, "kite_test FAIL: %s\n", what); + ::fprintf(stderr, "kite_test FAIL: %s\n", what); return 1; } @@ -56,7 +57,7 @@ int main() { { PhysicsWorld probe; if (!probe.Initialize()) { - std::fprintf(stderr, "kite_test: physics stub, skipping\n"); + ::fprintf(stderr, "kite_test: physics stub, skipping\n"); return 0; // physics compiled as a stub: nothing to exercise } } @@ -78,7 +79,7 @@ int main() { // Give it a few seconds to launch and climb into equilibrium. Run(world, kite, {}, 60 * 8); const KiteState s = kite.state(); - std::fprintf(stderr, + ::fprintf(stderr, "(a) equilibrium: altitude=%.2f m tension=%.1f N airspeed=%.2f m/s " "alpha=%.1f deg taut=%d line=%.1f m\n", s.altitude_m, s.tension_n, s.airspeed_mps, s.alpha_deg, s.taut, s.line_length_m); @@ -95,11 +96,11 @@ int main() { for (int i = 0; i < 60 * 30; ++i) { Step(world, kite, {}); const f32 a = kite.state().altitude_m; - if (!std::isfinite(a)) return Fail("(a) NaN altitude"); - lo = std::min(lo, a); - hi = std::max(hi, a); + if (!::isfinite(a)) return Fail("(a) NaN altitude"); + lo = rx::Min(lo, a); + hi = rx::Max(hi, a); } - std::fprintf(stderr, "(a) 30 s hover band: [%.2f, %.2f] m\n", lo, hi); + ::fprintf(stderr, "(a) 30 s hover band: [%.2f, %.2f] m\n", lo, hi); if (lo < line * 0.4f) return Fail("(a) kite fell out of the sky during hover"); } @@ -114,7 +115,7 @@ int main() { const f32 flying = Run(world, kite, {}, 60 * 8).altitude_m; // aloft first world.set_wind({0, 0, 0}); // wind dies const f32 settled = Run(world, kite, {}, 60 * 20).altitude_m; - std::fprintf(stderr, "(b) altitude flying=%.2f m -> settled=%.2f m\n", flying, settled); + ::fprintf(stderr, "(b) altitude flying=%.2f m -> settled=%.2f m\n", flying, settled); if (settled >= flying - 5.0f) return Fail("(b) kite did not descend when the wind died"); // Damped by its own form drag, a lift-less kite sinks slowly and settles near // the ground (it hangs/rests low, not pinned to y=0). @@ -139,8 +140,8 @@ int main() { }; const f32 left = steered_x(-1.0f); const f32 right = steered_x(+1.0f); - std::fprintf(stderr, "(c) steer dX left=%.2f m right=%.2f m\n", left, right); - if (std::fabs(left) < 0.5f || std::fabs(right) < 0.5f) + ::fprintf(stderr, "(c) steer dX left=%.2f m right=%.2f m\n", left, right); + if (::fabs(left) < 0.5f || ::fabs(right) < 0.5f) return Fail("(c) steering produced no lateral displacement"); if ((left > 0) == (right > 0)) return Fail("(c) left/right steer did not oppose"); } @@ -162,11 +163,11 @@ int main() { f32 min_alt = alt0; for (int i = 0; i < 60 * 5; ++i) { Step(world, kite, in); - min_alt = std::min(min_alt, kite.state().altitude_m); + min_alt = rx::Min(min_alt, kite.state().altitude_m); } const f32 line1 = kite.line_length(); const f32 alt1 = kite.state().altitude_m; - std::fprintf(stderr, "(d) reel-in line %.1f -> %.1f m altitude %.2f -> %.2f m (min %.2f)\n", + ::fprintf(stderr, "(d) reel-in line %.1f -> %.1f m altitude %.2f -> %.2f m (min %.2f)\n", line0, line1, alt0, alt1, min_alt); if (line1 >= line0 - 1.0f) return Fail("(d) line did not shorten"); if (alt1 >= alt0 - 0.5f) return Fail("(d) altitude did not follow the shorter line down"); @@ -194,10 +195,10 @@ int main() { kite.set_anchor(anc); Step(world, kite, in); const f32 a = kite.state().altitude_m; // height above the (moving) anchor - if (!std::isfinite(a)) return Fail("(e) NaN while towed"); - max_alt = std::max(max_alt, a); + if (!::isfinite(a)) return Fail("(e) NaN while towed"); + max_alt = rx::Max(max_alt, a); } - std::fprintf(stderr, "(e) towed max altitude=%.2f m tension=%.1f N\n", max_alt, + ::fprintf(stderr, "(e) towed max altitude=%.2f m tension=%.1f N\n", max_alt, kite.state().tension_n); if (max_alt < 2.0f) return Fail("(e) towed kite never left the ground"); if (kite.state().tension_n > desc.tether_max_tension) @@ -216,22 +217,22 @@ int main() { const f32 t = i * kDt; // Violent gusting: base wind plus large multi-frequency swings and a // rotating cross-component. - const f32 base = 8.0f + 7.0f * std::sin(t * 1.3f) + 4.0f * std::sin(t * 5.1f + 0.7f); - const f32 cross = 6.0f * std::sin(t * 0.9f + 2.0f); - world.set_wind({cross, 2.0f * std::sin(t * 3.3f), base}); + const f32 base = 8.0f + 7.0f * ::sin(t * 1.3f) + 4.0f * ::sin(t * 5.1f + 0.7f); + const f32 cross = 6.0f * ::sin(t * 0.9f + 2.0f); + world.set_wind({cross, 2.0f * ::sin(t * 3.3f), base}); // Also jerk the anchor around to stress the one-sided spring. - kite.set_anchor(Vec3{2.0f * std::sin(t * 2.0f), 0.0f, 2.0f * std::cos(t * 2.0f)}); + kite.set_anchor(Vec3{2.0f * ::sin(t * 2.0f), 0.0f, 2.0f * ::cos(t * 2.0f)}); KiteInput in; - in.steer = std::sin(t * 4.0f); + in.steer = ::sin(t * 4.0f); Step(world, kite, in); const KiteState s = kite.state(); - if (!std::isfinite(s.position.x) || !std::isfinite(s.position.y) || - !std::isfinite(s.position.z) || !std::isfinite(s.tension_n) || - !std::isfinite(s.alpha_deg)) + if (!::isfinite(s.position.x) || !::isfinite(s.position.y) || + !::isfinite(s.position.z) || !::isfinite(s.tension_n) || + !::isfinite(s.alpha_deg)) return Fail("(f) NaN under gusting"); - max_tension = std::max(max_tension, s.tension_n); + max_tension = rx::Max(max_tension, s.tension_n); } - std::fprintf(stderr, "(f) 60 s gust: max tension=%.1f N (cap %.0f)\n", max_tension, + ::fprintf(stderr, "(f) 60 s gust: max tension=%.1f N (cap %.0f)\n", max_tension, desc.tether_max_tension); if (max_tension > desc.tether_max_tension + 1e-3f) return Fail("(f) tension exceeded the documented cap"); @@ -258,11 +259,11 @@ int main() { for (int i = 0; i < 30; ++i) world.Update(kDt); // crash-free after removal PhysicsWorld::RayHit hit; const bool after = world.SphereCast(Vec3{p.x, p.y + 3.0f, p.z}, Vec3{0, -1, 0}, 6.0f, 0.6f, &hit); - std::fprintf(stderr, "(g) sail cast before destroy=%d after=%d\n", before, after); + ::fprintf(stderr, "(g) sail cast before destroy=%d after=%d\n", before, after); if (!before) return Fail("(g) sail body not present before destroy"); if (after) return Fail("(g) sail body still present after destroy"); } - std::fprintf(stderr, "kite_test: all checks passed\n"); + ::fprintf(stderr, "kite_test: all checks passed\n"); return 0; } diff --git a/test/lens_flare_test.cc b/test/lens_flare_test.cc index c4581a17..83eddd2b 100644 --- a/test/lens_flare_test.cc +++ b/test/lens_flare_test.cc @@ -1,10 +1,12 @@ -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include "base/algorithm.h" +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "core/scalar.h" #include "render/post/post.h" using namespace rx; @@ -13,13 +15,13 @@ using namespace rx::render; int main() { constexpr u32 w = 128, h = 96; DeviceDesc desc; - const char* backend = std::getenv("RX_RHI"); - desc.backend = backend && std::strcmp(backend, "d3d12") == 0 ? Backend::kD3D12 : Backend::kVulkan; + const char* backend = ::getenv("RX_RHI"); + desc.backend = backend && ::strcmp(backend, "d3d12") == 0 ? Backend::kD3D12 : Backend::kVulkan; desc.request_raytracing = false; desc.enable_validation = true; auto device = Device::CreateOffscreen(desc); if (!device || device->is_stub()) { - std::printf("lens_flare_test: SKIP, GPU unavailable\n"); + ::printf("lens_flare_test: SKIP, GPU unavailable\n"); return 77; } auto post = PostPass::Create(*device, Format::kRGBA32Float); @@ -29,37 +31,37 @@ int main() { GpuImage output = device->CreateImage2D(Format::kRGBA32Float, {w,h}, kTextureUsageColorTarget | kTextureUsageTransferSrc); GpuBuffer exposure = device->CreateBuffer(sizeof(f32), kBufferUsageStorage, true); if (!scene || !flare || !output || !exposure.mapped) return 1; - auto upload = [&](GpuImage image, const std::vector& data, ResourceState state) { + auto upload = [&](GpuImage image, const base::Vector& data, ResourceState state) { GpuBuffer staging = device->CreateBufferWithData( - {reinterpret_cast(data.data()), data.size() * sizeof(f32)}, kBufferUsageTransferSrc); + ByteSpan(reinterpret_cast(data.data()), data.size() * sizeof(f32)), kBufferUsageTransferSrc); device->ImmediateSubmit([&](CommandList& cmd) { cmd.Barrier(Transition(image, state, ResourceState::kCopyDst)); BufferTextureCopy copy{.extent = {w,h}}; - cmd.CopyBufferToTexture(staging, image, {©,1}); + cmd.CopyBufferToTexture(staging, image, base::Span(©, 1)); cmd.Barrier(Transition(image, ResourceState::kCopyDst, ResourceState::kShaderReadFragment)); }); device->DestroyBuffer(staging); }; - std::vector data(w*h*4), pixels(w*h*4), reference; + base::Vector data(w*h*4), pixels(w*h*4), reference; upload(scene, data, ResourceState::kUndefined); int failures = 0; ResourceState flare_state = ResourceState::kUndefined, output_state = ResourceState::kUndefined; for (u32 mode = 0; mode < 6; ++mode) { - std::fill(data.begin(), data.end(), 0); + base::Fill(data.begin(), data.end(), 0.f); const f32 gain = mode == 2 ? 8 : 1; // A bright object away from the optical axis. No bloom or scene energy is // present, so every output pixel measures the production flare itself. const u32 left = mode == 5 ? 0 : 92; for (u32 y = 42; y < 54; ++y) for (u32 x = left; x < left + 12; ++x) for (u32 c = 0; c < 3; ++c) data[(y*w+x)*4+c] = (mode == 0 ? 2.5f : 16.f) / gain; - if (mode == 3) std::fill(data.begin(), data.end(), 0); + if (mode == 3) base::Fill(data.begin(), data.end(), 0.f); upload(flare, data, flare_state); flare_state = ResourceState::kShaderReadFragment; - std::memcpy(exposure.mapped, &gain, sizeof(gain)); + base::MemCopy(exposure.mapped, &gain, sizeof(gain)); device->FlushBuffer(exposure, 0, sizeof(gain)); device->ImmediateSubmit([&](CommandList& cmd) { cmd.Barrier(Transition(output, output_state, ResourceState::kColorTarget)); - PassContext ctx{.cmd = &cmd, .device = device.get()}; + PassContext ctx{.cmd = &cmd, .device = device.Get_UseOnlyIfYouKnowWhatYouareDoing()}; PostPass::Params params; params.tonemap = 2; params.output_transfer = 2; @@ -80,22 +82,22 @@ int main() { for (u32 c = 0; c < 3; ++c) { const u32 index = (y*w+x)*4+c; const f32 value = pixels[index]; - if (!std::isfinite(value)) ++failures; - sum += std::abs(value); - if (!expected) outside += std::abs(value); - if (mode == 2) difference = std::max(difference, double(std::abs(value-reference[index]))); + if (!::isfinite(value)) ++failures; + sum += ::abs(value); + if (!expected) outside += ::abs(value); + if (mode == 2) difference = rx::Max(difference, double(::abs(value-reference[index]))); } } - std::printf("flare mode=%u energy=%g outside_ghosts=%g exposure_error=%g\n", mode, sum, outside, difference); + ::printf("flare mode=%u energy=%g outside_ghosts=%g exposure_error=%g\n", mode, sum, outside, difference); bool ok = true; if (mode == 0 || mode == 3 || mode == 4) ok = sum < 1e-6; if (mode == 1) { ok = sum > .1 && outside < 1e-5; reference = pixels; } if (mode == 2) ok = difference < 1e-5; if (mode == 5) ok = sum > .1 && outside < 1e-5; - if (!ok) { ++failures; std::printf("FAIL: flare highlight selection, footprint, or exposure invariance\n"); } + if (!ok) { ++failures; ::printf("FAIL: flare highlight selection, footprint, or exposure invariance\n"); } } device->WaitIdle(); - post.reset(); + post.Reset(); device->DestroyBuffer(exposure); device->DestroyImage(scene); device->DestroyImage(flare); diff --git a/test/locomotion_math_test.cc b/test/locomotion_math_test.cc index 75de15f7..817087b9 100644 --- a/test/locomotion_math_test.cc +++ b/test/locomotion_math_test.cc @@ -3,8 +3,9 @@ // terrain is faked through a synthetic downward probe. Hand-rolled Check/Near // harness in the rx test style (see test/camera_test.cc). -#include -#include +#include +#include +#include #include "core/math.h" #include "core/types.h" @@ -23,23 +24,23 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "locomotion_math_test: FAIL: %s\n", message); + ::fprintf(stderr, "locomotion_math_test: FAIL: %s\n", message); ++failures; } void Near(f32 actual, f32 expected, const char* message, f32 epsilon = 1e-4f) { - if (std::abs(actual - expected) <= epsilon) return; - std::fprintf(stderr, "locomotion_math_test: FAIL: %s (got %.6f, expected %.6f)\n", message, + if (::abs(actual - expected) <= epsilon) return; + ::fprintf(stderr, "locomotion_math_test: FAIL: %s (got %.6f, expected %.6f)\n", message, actual, expected); ++failures; } bool Finite(const Vec3& v) { - return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z); + return ::isfinite(v.x) && ::isfinite(v.y) && ::isfinite(v.z); } bool Finite(const Quat& q) { - return std::isfinite(q.x) && std::isfinite(q.y) && std::isfinite(q.z) && std::isfinite(q.w); + return ::isfinite(q.x) && ::isfinite(q.y) && ::isfinite(q.z) && ::isfinite(q.w); } // synthetic terrain probes @@ -214,7 +215,7 @@ void TestGaitRampSmooth() { for (int i = 0; i < 400; ++i) { clock.Update(m, run, params, false, dt); const f32 rate = clock.state().phase_rate; - if (prev_rate > 0 && std::abs(rate - prev_rate) / prev_rate > 0.2f) smooth = false; + if (prev_rate > 0 && ::abs(rate - prev_rate) / prev_rate > 0.2f) smooth = false; prev_rate = rate; } Check(smooth, "phase rate ramps smoothly (<20% per tick)"); @@ -230,7 +231,7 @@ void TestCapturePoint() { const Vec3 v{0.5f, 0, -0.3f}; const Vec3 cp1 = CapturePoint(com, v, 9.81f, 0.95f); - const f32 omega = std::sqrt(9.81f / 0.95f); + const f32 omega = ::sqrt(9.81f / 0.95f); Near(cp1.x - com.x, v.x / omega, "capture offset x = v/sqrt(g/h)"); Near(cp1.z - com.z, v.z / omega, "capture offset z = v/sqrt(g/h)"); @@ -359,7 +360,7 @@ void TestFootstepPlanner() { // Rotation angle of a quaternion delta from identity (radians, [0,pi]). f32 QuatAngle(const Quat& q) { const Quat n = Normalize(q); - return 2.0f * std::acos(std::abs(n.w) > 1.0f ? 1.0f : std::abs(n.w)); + return 2.0f * ::acos(::abs(n.w) > 1.0f ? 1.0f : ::abs(n.w)); } // Independent forward kinematics matching the module's chain convention: the @@ -415,7 +416,7 @@ void TestLegIkForwardKinematics() { const Vec3 target{x, y, z}; const Vec3 ankle_target = target + flat_normal * foot_height; const LegIkResult r = SolveLegIk(target, flat_normal, upper, lower, foot_height); - Check(Finite(r.hip) && Finite(r.ankle) && std::isfinite(r.knee_flexion), "leg IK finite"); + Check(Finite(r.hip) && Finite(r.ankle) && ::isfinite(r.knee_flexion), "leg IK finite"); Check(r.knee_flexion >= 0.0f, "knee flexion non-negative"); Check(!r.clamped, "reachable target not clamped"); const Vec3 ankle = LegForwardAnkle(r, upper, lower); @@ -457,13 +458,13 @@ void TestLegIkUnreachable() { Near(r.knee_flexion, 0.0f, "unreachable leg is ~straight", 0.05f); const Vec3 sole = LegForwardKinematics(r, upper, lower, foot_height); const f32 cos_angle = Dot(Normalize(sole), Normalize(target)); - const f32 angle = std::acos(cos_angle > 1.0f ? 1.0f : cos_angle); + const f32 angle = ::acos(cos_angle > 1.0f ? 1.0f : cos_angle); Check(angle < 2.0f * 3.14159265f / 180.0f, "FK aims within 2deg of the target direction"); } void TestLegIkDegenerate() { const LegIkResult r = SolveLegIk({0, 0, 0}, {0, 1, 0}, 0.45f, 0.40f, 0.054f); - Check(Finite(r.hip) && Finite(r.ankle) && std::isfinite(r.knee_flexion), "degenerate finite"); + Check(Finite(r.hip) && Finite(r.ankle) && ::isfinite(r.knee_flexion), "degenerate finite"); Check(r.clamped, "zero-length target flagged clamped"); } @@ -522,7 +523,7 @@ void TestWholeBodyStanding() { for (u32 j = 0; j < kRigJointCount; ++j) { Check(Finite(out.joint_target[j]), "joint target finite"); - Check(std::isfinite(out.joint_drive_scale[j]), "drive scale finite"); + Check(::isfinite(out.joint_drive_scale[j]), "drive scale finite"); } Check(Finite(out.root_assist_force) && Finite(out.root_assist_torque), "root assists finite"); @@ -660,9 +661,9 @@ int main() { TestWholeBodyLean(); if (failures != 0) { - std::fprintf(stderr, "locomotion_math_test: %d failure(s)\n", failures); + ::fprintf(stderr, "locomotion_math_test: %d failure(s)\n", failures); return 1; } - std::printf("locomotion_math_test: PASS\n"); + ::printf("locomotion_math_test: PASS\n"); return 0; } diff --git a/test/locomotion_test.cc b/test/locomotion_test.cc index 0c2339cf..e7f647e5 100644 --- a/test/locomotion_test.cc +++ b/test/locomotion_test.cc @@ -4,10 +4,12 @@ // ContactEstimator hysteresis. Style follows test/character_test.cc: hand-rolled // Check/Near and a failure counter, plain main returning the count. -#include -#include +#include +#include +#include #include "core/math.h" +#include "core/scalar.h" #include "locomotion/controller.h" #include "locomotion/estimator.h" #include "locomotion/rig.h" @@ -26,13 +28,13 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "locomotion_test: FAIL: %s\n", message); + ::fprintf(stderr, "locomotion_test: FAIL: %s\n", message); ++failures; } void Near(f32 actual, f32 expected, const char* message, f32 epsilon = 1e-2f) { - if (std::abs(actual - expected) <= epsilon) return; - std::fprintf(stderr, "locomotion_test: FAIL: %s (got %.4f, expected %.4f)\n", message, actual, + if (::abs(actual - expected) <= epsilon) return; + ::fprintf(stderr, "locomotion_test: FAIL: %s (got %.4f, expected %.4f)\n", message, actual, expected); ++failures; } @@ -88,19 +90,19 @@ Vec3 BodyPos(const physics::PhysicsWorld& phys, physics::BodyId id) { } bool FiniteVec(const Vec3& v) { - return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z); + return ::isfinite(v.x) && ::isfinite(v.y) && ::isfinite(v.z); } bool MeasurementsFinite(const CharacterMeasurements& m) { - bool ok = FiniteVec(m.root_position) && std::isfinite(m.root_rotation.x) && - std::isfinite(m.root_rotation.w) && FiniteVec(m.root_linear_velocity) && + bool ok = FiniteVec(m.root_position) && ::isfinite(m.root_rotation.x) && + ::isfinite(m.root_rotation.w) && FiniteVec(m.root_linear_velocity) && FiniteVec(m.root_angular_velocity) && FiniteVec(m.com_position) && FiniteVec(m.com_velocity) && FiniteVec(m.ground_normal) && - std::isfinite(m.estimated_body_height); + ::isfinite(m.estimated_body_height); for (u32 f = 0; f < kFootCount; ++f) { ok = ok && FiniteVec(m.foot[f].position) && FiniteVec(m.foot[f].velocity) && - FiniteVec(m.foot[f].contact_normal) && std::isfinite(m.foot[f].contact_impulse) && - std::isfinite(m.foot[f].slip_speed); + FiniteVec(m.foot[f].contact_normal) && ::isfinite(m.foot[f].contact_impulse) && + ::isfinite(m.foot[f].slip_speed); } return ok; } @@ -145,7 +147,7 @@ void TestBuild() { Check(sole_r.y > -0.01f && sole_r.y < 0.05f, "right sole near y in [0, 0.05]"); bool binds_ok = true; - for (u32 j = 0; j < kRigJointCount; ++j) binds_ok = binds_ok && std::abs(rig.bind_constraint[j].w) > 0.99f; + for (u32 j = 0; j < kRigJointCount; ++j) binds_ok = binds_ok && ::abs(rig.bind_constraint[j].w) > 0.99f; Check(binds_ok, "every bind constraint orientation is near identity (|w| > 0.99)"); Check(rig.leg_length > 0 && rig.upper_leg_length > 0 && rig.lower_leg_length > 0 && @@ -193,10 +195,10 @@ void TestStandingHold() { // COM planar position within the feet span (plus a small margin). const Vec3 fl = m.foot[0].position; const Vec3 fr = m.foot[1].position; - const f32 min_x = std::min(fl.x, fr.x) - 0.15f; - const f32 max_x = std::max(fl.x, fr.x) + 0.15f; - const f32 min_z = std::min(fl.z, fr.z) - 0.25f; - const f32 max_z = std::max(fl.z, fr.z) + 0.25f; + const f32 min_x = rx::Min(fl.x, fr.x) - 0.15f; + const f32 max_x = rx::Max(fl.x, fr.x) + 0.15f; + const f32 min_z = rx::Min(fl.z, fr.z) - 0.25f; + const f32 max_z = rx::Max(fl.z, fr.z) + 0.25f; Check(m.com_position.x > min_x && m.com_position.x < max_x, "COM x within the feet span"); Check(m.com_position.z > min_z && m.com_position.z < max_z, "COM z within the feet span"); @@ -464,19 +466,19 @@ void CheckFinite(const physics::PhysicsWorld& phys, const LocomotionController& phys.GetBodyTransform(rig.body[i], &p, r); Vec3 lin, ang; phys.GetBodyVelocity(rig.body[i], &lin, &ang); - ok = ok && FiniteVec(p) && std::isfinite(r[0]) && std::isfinite(r[1]) && std::isfinite(r[2]) && - std::isfinite(r[3]) && FiniteVec(lin) && FiniteVec(ang); + ok = ok && FiniteVec(p) && ::isfinite(r[0]) && ::isfinite(r[1]) && ::isfinite(r[2]) && + ::isfinite(r[3]) && FiniteVec(lin) && FiniteVec(ang); } const DebugState& d = c.debug(); ok = ok && FiniteVec(d.desired_velocity) && FiniteVec(d.controlled_facing) && FiniteVec(d.measured_velocity) && FiniteVec(d.com_position) && FiniteVec(d.com_velocity) && FiniteVec(d.capture_point) && - FiniteVec(d.support_center) && std::isfinite(d.gait_phase) && - std::isfinite(d.max_torque_saturation) && std::isfinite(d.mode_time); + FiniteVec(d.support_center) && ::isfinite(d.gait_phase) && + ::isfinite(d.max_torque_saturation) && ::isfinite(d.mode_time); for (u32 f = 0; f < kFootCount; ++f) ok = ok && FiniteVec(d.foot_target[f]) && FiniteVec(d.swing_position[f]); if (!ok) { - std::fprintf(stderr, "locomotion_test: FAIL: non-finite state at %s\n", where); + ::fprintf(stderr, "locomotion_test: FAIL: non-finite state at %s\n", where); ++failures; } } @@ -523,10 +525,10 @@ void TestStand() { if (c.mode() == ControlMode::kCorrectiveStep) ++corrective; if (c.mode() == ControlMode::kControlledFall || c.mode() == ControlMode::kGrounded) ++fell; const f32 py = PelvisY(c, s.physics); - min_pelvis = std::min(min_pelvis, py); - max_pelvis = std::max(max_pelvis, py); + min_pelvis = rx::Min(min_pelvis, py); + max_pelvis = rx::Max(max_pelvis, py); const Vec3 com = c.measurements().com_position; - max_drift = std::max(max_drift, std::sqrt((com.x - com0.x) * (com.x - com0.x) + + max_drift = rx::Max(max_drift, ::sqrt((com.x - com0.x) * (com.x - com0.x) + (com.z - com0.z) * (com.z - com0.z))); } @@ -569,15 +571,15 @@ void TestPushes() { StepN(s, c, intent, mods, 120); // 2 s recovery window char msg[96]; - std::snprintf(msg, sizeof msg, "push %s recovers to kStable", names[d]); + ::snprintf(msg, sizeof msg, "push %s recovers to kStable", names[d]); Check(c.mode() == ControlMode::kStable, msg); const f32 py = PelvisY(c, s.physics); - std::snprintf(msg, sizeof msg, "push %s pelvis recovers > 0.85x nominal (got %.2f)", names[d], + ::snprintf(msg, sizeof msg, "push %s pelvis recovers > 0.85x nominal (got %.2f)", names[d], py); Check(py > 0.85f * nominal, msg); const Vec3 v = c.measurements().com_velocity; - const f32 planar = std::sqrt(v.x * v.x + v.z * v.z); - std::snprintf(msg, sizeof msg, "push %s planar COM speed < 0.3 (got %.2f)", names[d], planar); + const f32 planar = ::sqrt(v.x * v.x + v.z * v.z); + ::snprintf(msg, sizeof msg, "push %s planar COM speed < 0.3 (got %.2f)", names[d], planar); Check(planar < 0.3f, msg); CheckFinite(s.physics, c, "push"); } @@ -627,9 +629,9 @@ void TestWalk() { s.physics.Update(kDt); if (c.mode() == ControlMode::kControlledFall || c.mode() == ControlMode::kGrounded) fell = true; - min_pelvis = std::min(min_pelvis, PelvisY(c, s.physics)); + min_pelvis = rx::Min(min_pelvis, PelvisY(c, s.physics)); const Vec3 com = c.measurements().com_position; - peak_forward = std::max(peak_forward, com0.z - com.z); // forward = -Z + peak_forward = rx::Max(peak_forward, com0.z - com.z); // forward = -Z if (i >= 30 && i < active_end) { vz_sum += c.measurements().com_velocity.z; ++vz_count; @@ -642,21 +644,21 @@ void TestWalk() { char msg[112]; if (v < 1.0f) { // Achievable: honest stable low-speed walk. - std::snprintf(msg, sizeof msg, "walk v=%.1f never falls (achievable regime)", v); + ::snprintf(msg, sizeof msg, "walk v=%.1f never falls (achievable regime)", v); Check(!fell, msg); - std::snprintf(msg, sizeof msg, "walk v=%.1f pelvis > 0.75x nominal (got %.2f)", v, min_pelvis); + ::snprintf(msg, sizeof msg, "walk v=%.1f pelvis > 0.75x nominal (got %.2f)", v, min_pelvis); Check(min_pelvis > 0.75f * nominal, msg); - std::snprintf(msg, sizeof msg, "walk v=%.1f moves forward in the active window (vz=%.2f)", v, + ::snprintf(msg, sizeof msg, "walk v=%.1f moves forward in the active window (vz=%.2f)", v, -mean_vz); Check(-mean_vz > 0.10f, msg); - std::snprintf(msg, sizeof msg, "walk v=%.1f net forward progress > 0.15 m (got %.2f)", v, + ::snprintf(msg, sizeof msg, "walk v=%.1f net forward progress > 0.15 m (got %.2f)", v, net_travel); Check(net_travel > 0.15f, msg); } else { // Not achievable: assert graceful handling; the mode machine falls rather // than exploding. (Sustained tracking of this speed is not achieved; peak // forward reached %.2f m before the controller gave up.) - std::snprintf(msg, sizeof msg, + ::snprintf(msg, sizeof msg, "walk v=%.1f cannot be sustained; controller falls gracefully (peak fwd " "%.2f m)", v, peak_forward); @@ -718,7 +720,7 @@ void TestStartStop() { Check(!fell, "start/stop never falls"); Check(c.mode() == ControlMode::kStable, "settles to kStable after stop"); const Vec3 vs = c.measurements().com_velocity; - Check(std::sqrt(vs.x * vs.x + vs.z * vs.z) < 0.3f, "planar speed < 0.3 after stop"); + Check(::sqrt(vs.x * vs.x + vs.z * vs.z) < 0.3f, "planar speed < 0.3 after stop"); // Second walk: verify the gait RE-ENGAGES on the renewed command. Net forward // distance is not asserted (relaxed from the spec's "> 0.7 m/s") because by @@ -731,8 +733,8 @@ void TestStartStop() { s.physics.Update(kDt); if (c.mode() == ControlMode::kControlledFall || c.mode() == ControlMode::kGrounded) fell = true; - phase_lo = std::min(phase_lo, c.debug().gait_phase); - phase_hi = std::max(phase_hi, c.debug().gait_phase); + phase_lo = rx::Min(phase_lo, c.debug().gait_phase); + phase_hi = rx::Max(phase_hi, c.debug().gait_phase); } Check(!fell, "start/stop never falls (second walk)"); Check(phase_hi - phase_lo > 0.3f, "commanding walk again re-engages the gait (phase advances)"); @@ -781,7 +783,7 @@ void TestTurn() { for (int i = 0; i < 5 * 60; ++i) { c.Tick(left, mods, kDt); s.physics.Update(kDt); - peak_neg_x = std::max(peak_neg_x, com_turn.x - c.measurements().com_position.x); + peak_neg_x = rx::Max(peak_neg_x, com_turn.x - c.measurements().com_position.x); } (void)fell; Check(peak_neg_x > 0.3f, "after the turn the body drives toward -X (peak displacement)"); @@ -808,7 +810,7 @@ void TestTurnRate() { c.Tick(left, mods, kDt); s.physics.Update(kDt); const Vec3 first = c.debug().controlled_facing; - const f32 first_angle = std::atan2(Cross(Vec3{0, 0, -1}, first).y, + const f32 first_angle = ::atan2(Cross(Vec3{0, 0, -1}, first).y, Dot(Vec3{0, 0, -1}, first)); Check(first_angle > 0, "turn target starts moving toward requested facing"); Check(first_angle <= params.max_turn_rate * kDt + 1e-4f, @@ -855,7 +857,7 @@ void TestUnrecoverablePush() { for (int i = 0; i < total; ++i) { c.Tick(intent, mods, kDt); s.physics.Update(kDt); - max_ke = std::max(max_ke, KineticEnergy(s.physics, c)); + max_ke = rx::Max(max_ke, KineticEnergy(s.physics, c)); if (c.mode() == ControlMode::kControlledFall && !entered_fall) { entered_fall = true; fall_tick = i; @@ -869,7 +871,7 @@ void TestUnrecoverablePush() { Check(entered_fall && fall_tick < 60, "enters kControlledFall within 1 s"); Check(reached_grounded && grounded_tick < 4 * 60, "reaches kGrounded within 4 s"); char msg[96]; - std::snprintf(msg, sizeof msg, "KE proxy never exceeds 3x push value (%.0f vs %.0f)", max_ke, + ::snprintf(msg, sizeof msg, "KE proxy never exceeds 3x push value (%.0f vs %.0f)", max_ke, ke_at_push); Check(max_ke < 3.0f * ke_at_push + 1.0f, msg); Check(PelvisY(c, s.physics) < 0.6f * nominal, "ends below 0.6x nominal pelvis height"); @@ -904,8 +906,8 @@ void TestDebugState() { s.physics.Update(kDt); const DebugState& d = c.debug(); cp_finite = cp_finite && FiniteVec(d.capture_point); - phase_min = std::min(phase_min, d.gait_phase); - phase_max = std::max(phase_max, d.gait_phase); + phase_min = rx::Min(phase_min, d.gait_phase); + phase_max = rx::Max(phase_max, d.gait_phase); if (Length(d.foot_target[0]) > 1e-3f || Length(d.foot_target[1]) > 1e-3f) foot_targets_nonzero = true; if (c.mode() != ControlMode::kStable && c.mode() != ControlMode::kCorrectiveStep) @@ -976,9 +978,9 @@ int main() { TestControllerResetPath(); if (failures == 0) { - std::printf("locomotion_test: all checks passed\n"); + ::printf("locomotion_test: all checks passed\n"); return 0; } - std::fprintf(stderr, "locomotion_test: %d checks failed\n", failures); + ::fprintf(stderr, "locomotion_test: %d checks failed\n", failures); return 1; } diff --git a/test/materialx_test.cc b/test/materialx_test.cc index 9e602f6d..b45e298a 100644 --- a/test/materialx_test.cc +++ b/test/materialx_test.cc @@ -6,39 +6,37 @@ // cannot evaluate leaves the slot empty rather than guessing, and that the // constant-input path a hand-written standard_surface uses still works. -#include -#include -#include -#include -#include +#include +#include #include "asset/materialx.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" namespace { using namespace rx; -namespace fs = std::filesystem; +namespace fs = rx::fs; int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "materialx_test: FAIL: %s\n", message); + ::fprintf(stderr, "materialx_test: FAIL: %s\n", message); ++failures; } -fs::path Write(const char* name, const std::string& text) { - const fs::path path = fs::temp_directory_path() / name; - std::ofstream out(path, std::ios::binary); - out << text; +base::String Write(const char* name, const base::String& text) { + const base::String path = fs::Join(fs::TempDirectory(), name); + fs::WriteTextFile(path, text); return path; } // Filenames resolve against the document, so every expectation is the temp // directory plus the name the document wrote. -std::string Beside(const char* file) { - return (fs::temp_directory_path() / file).lexically_normal().string(); +base::String Beside(const char* file) { + return fs::LexicallyNormal(fs::Join(fs::TempDirectory(), file)); } // What ambientCG ships: MaterialX 1.39, a flat list of nodes at the root, an @@ -103,10 +101,10 @@ constexpr char kConstantsOnly[] = R"( )"; void TestOpenPbrImages() { - const fs::path path = Write("rx_openpbr.mtlx", kOpenPbrWithImages); + const base::String path = Write("rx_openpbr.mtlx", kOpenPbrWithImages); asset::Material material; asset::MaterialXMaps maps; - Check(asset::LoadMaterialX(path.string(), &material, &maps), + Check(asset::LoadMaterialX(path, &material, &maps), "an open_pbr_surface document loads (a texture library ships nothing else)"); Check(maps.base_color == Beside("stone_color.png"), "base colour resolves beside the document"); // Through the normalmap node, which only converts the encoding the engine @@ -116,7 +114,7 @@ void TestOpenPbrImages() { Check(maps.metallic.empty(), "a constant input leaves its map slot empty"); // OpenPBR spells it base_metalness; the engine field is the same one // standard_surface's metalness fills. - Check(std::fabs(material.metallic_factor - 0.25f) < 1e-6f, + Check(::fabs(material.metallic_factor - 0.25f) < 1e-6f, "the OpenPBR spelling of a constant input maps onto the same field"); // The shader multiplies each map by its factor. The OpenPBR defaults seeded // before parsing (0.8 base colour, 0.3 roughness) must not survive onto a @@ -124,14 +122,14 @@ void TestOpenPbrImages() { Check(material.base_color_factor[0] == 1.0f && material.base_color_factor[2] == 1.0f, "a mapped base colour leaves its factor at 1"); Check(material.roughness_factor == 1.0f, "a mapped roughness leaves its factor at 1"); - fs::remove(path); + fs::Remove(path); } void TestNodegraphAndUnsupportedNode() { - const fs::path path = Write("rx_nodegraph.mtlx", kNodegraphStandardSurface); + const base::String path = Write("rx_nodegraph.mtlx", kNodegraphStandardSurface); asset::Material material; asset::MaterialXMaps maps; - Check(asset::LoadMaterialX(path.string(), &material, &maps), + Check(asset::LoadMaterialX(path, &material, &maps), "a nodegraph-style standard_surface document loads"); Check(maps.base_color == Beside("brick_color.png"), "a nodegraph output resolves to the image behind it"); @@ -139,24 +137,24 @@ void TestNodegraphAndUnsupportedNode() { // empty (and warning by name) is the honest answer; binding the operand as if // it were the result would render a material nobody authored. Check(maps.roughness.empty(), "a connection through an unevaluated node binds nothing"); - Check(std::fabs(material.ior - 1.7f) < 1e-6f, "constants beside a connection still load"); - fs::remove(path); + Check(::fabs(material.ior - 1.7f) < 1e-6f, "constants beside a connection still load"); + fs::Remove(path); } void TestConstantsStillLoad() { - const fs::path path = Write("rx_constants.mtlx", kConstantsOnly); + const base::String path = Write("rx_constants.mtlx", kConstantsOnly); asset::Material material; - Check(asset::LoadMaterialX(path.string(), &material, nullptr), + Check(asset::LoadMaterialX(path, &material, nullptr), "a constants-only document loads with no maps requested"); // base is a weight on base_color, not a field of its own. - Check(std::fabs(material.base_color_factor[0] - 0.1f) < 1e-6f && - std::fabs(material.base_color_factor[2] - 0.3f) < 1e-6f, + Check(::fabs(material.base_color_factor[0] - 0.1f) < 1e-6f && + ::fabs(material.base_color_factor[2] - 0.3f) < 1e-6f, "base weight multiplies base_color"); - Check(std::fabs(material.metallic_factor - 1.0f) < 1e-6f && - std::fabs(material.roughness_factor - 0.3f) < 1e-6f && - std::fabs(material.clearcoat - 0.75f) < 1e-6f, + Check(::fabs(material.metallic_factor - 1.0f) < 1e-6f && + ::fabs(material.roughness_factor - 0.3f) < 1e-6f && + ::fabs(material.clearcoat - 0.75f) < 1e-6f, "the standard_surface constants map onto the engine's lobes"); - fs::remove(path); + fs::Remove(path); } } // namespace @@ -166,7 +164,7 @@ int main() { TestNodegraphAndUnsupportedNode(); TestConstantsStillLoad(); if (failures == 0) { - std::puts("materialx_test: PASS"); + ::puts("materialx_test: PASS"); return 0; } return 1; diff --git a/test/memory_test.cc b/test/memory_test.cc index b31a0c74..8937ff0c 100644 --- a/test/memory_test.cc +++ b/test/memory_test.cc @@ -2,15 +2,16 @@ // chunk pool, small vector and the memory config. Pure CPU, no GPU needed. // The tracker assertions only run when the new/delete override is compiled in // (RX_MIMALLOC=ON); otherwise they are skipped so the test still passes. -#include -#include -#include -#include +#include +#include #if defined(RX_MIMALLOC) #include #endif +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/strings/xstring.h" #include "core/memory/chunk_pool.h" #include "core/memory/frame_arena.h" #include "core/memory/memory_config.h" @@ -24,7 +25,7 @@ int g_failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++g_failures; \ } \ } while (0) @@ -33,7 +34,7 @@ rx::mem::CategoryStats FindCategory(const char* name) { rx::mem::CategoryStats stats[rx::mem::kMaxCategories]; const rx::u32 count = rx::mem::SnapshotCategories(stats, rx::mem::kMaxCategories); for (rx::u32 i = 0; i < count; ++i) { - if (std::strcmp(stats[i].name, name) == 0) return stats[i]; + if (::strcmp(stats[i].name, name) == 0) return stats[i]; } return {}; } @@ -44,7 +45,7 @@ void TestTracker() { CHECK(rx::mem::RegisterCategory("test-cat") == category); // idempotent by name if (!rx::mem::TrackingActive()) { - std::puts("tracker: RX_MIMALLOC off, skipping counter checks"); + ::puts("tracker: RX_MIMALLOC off, skipping counter checks"); return; } @@ -119,7 +120,7 @@ void TestChunkPool() { CHECK(a != nullptr && b != nullptr && a != b); CHECK(reinterpret_cast(a) % rx::mem::ChunkPool::kChunkAlign == 0); // Chunks are writable across their whole extent. - std::memset(a, 0xab, rx::mem::ChunkPool::kChunkSize); + base::MemSet(a, 0xab, rx::mem::ChunkPool::kChunkSize); const size_t total = pool.stats().total_chunks; CHECK(pool.stats().free_chunks == total - 2); @@ -137,24 +138,6 @@ struct Probe { ~Probe() { --live; } }; -struct ThrowingProbe { - static inline int live = 0; - static inline bool throw_on_construct = false; - static inline bool throw_on_move = false; - int value = 0; - - explicit ThrowingProbe(int v) : value(v) { - if (throw_on_construct) throw std::runtime_error("construct"); - ++live; - } - ThrowingProbe(ThrowingProbe&& other) : value(other.value) { - if (throw_on_move) throw std::runtime_error("move"); - ++live; - } - ThrowingProbe(const ThrowingProbe&) = delete; - ~ThrowingProbe() { --live; } -}; - void TestSmallVector() { { rx::mem::SmallVector vec; @@ -165,7 +148,7 @@ void TestSmallVector() { CHECK(vec.data() != inline_data); for (int i = 0; i < 32; ++i) CHECK(vec[static_cast(i)].value == i); - rx::mem::SmallVector moved(std::move(vec)); + rx::mem::SmallVector moved(base::move(vec)); CHECK(moved.size() == 32); CHECK(vec.size() == 0); CHECK(Probe::live == 32); @@ -178,36 +161,13 @@ void TestSmallVector() { ints.clear(); CHECK(ints.empty()); - rx::mem::SmallVector strings; + rx::mem::SmallVector strings; strings.emplace_back("alpha"); strings.emplace_back("beta"); strings.emplace_back(strings[0]); // aliased argument across growth CHECK(strings.size() == 3); CHECK(strings[0] == "alpha" && strings[2] == "alpha"); - { - rx::mem::SmallVector throwing; - throwing.emplace_back(7); - ThrowingProbe::throw_on_construct = true; - try { - throwing.emplace_back(8); - CHECK(false); - } catch (const std::runtime_error&) { - } - ThrowingProbe::throw_on_construct = false; - CHECK(throwing.size() == 1 && ThrowingProbe::live == 1); - - ThrowingProbe::throw_on_move = true; - try { - throwing.emplace_back(9); - CHECK(false); - } catch (const std::runtime_error&) { - } - ThrowingProbe::throw_on_move = false; - CHECK(throwing.size() == 1 && ThrowingProbe::live == 1); - CHECK(throwing[0].value == 7); - } - CHECK(ThrowingProbe::live == 0); } void TestConfig() { @@ -247,9 +207,9 @@ int main() { TestSmallVector(); TestConfig(); if (g_failures) { - std::fprintf(stderr, "memory_test: %d failure(s)\n", g_failures); + ::fprintf(stderr, "memory_test: %d failure(s)\n", g_failures); return 1; } - std::puts("memory_test: ok"); + ::puts("memory_test: ok"); return 0; } diff --git a/test/morph_test.cc b/test/morph_test.cc index e0ce37ba..f80293ca 100644 --- a/test/morph_test.cc +++ b/test/morph_test.cc @@ -12,20 +12,22 @@ // reference. Vulkan only - the shader reads through buffer device addresses // like the skinned path - and skips cleanly when no driver is present. -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include "anim/morph.h" #include "asset/gltf_loader.h" #include "render/rhi/command_list.h" #include "render/rhi/device.h" +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" +#include "core/scalar.h" #include "shaders/morph_apply_cs_hlsl.h" using namespace rx; @@ -33,7 +35,7 @@ using namespace rx; namespace { int Fail(const char* msg) { - std::fprintf(stderr, "morph_test: FAIL: %s\n", msg); + ::fprintf(stderr, "morph_test: FAIL: %s\n", msg); return 1; } @@ -75,19 +77,19 @@ constexpr char kNamedTargetsGltf[] = R"({ "buffers": [{"byteLength": 132, "uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgD8AAIA+AABAPwAAgD8AAAAA"}] })"; -bool Near(f32 a, f32 b, f32 tolerance = 1e-5f) { return std::abs(a - b) <= tolerance; } +bool Near(f32 a, f32 b, f32 tolerance = 1e-5f) { return ::abs(a - b) <= tolerance; } int TestNamedTargets() { - std::filesystem::path path = - std::filesystem::temp_directory_path() / "rx_morph_named_targets.gltf"; - std::FILE* file = std::fopen(path.string().c_str(), "wb"); + base::String path = + rx::fs::Join(rx::fs::TempDirectory(), "rx_morph_named_targets.gltf"); + FILE* file = ::fopen(path.c_str(), "wb"); if (!file) return Fail("cannot write the generated gltf"); - std::fwrite(kNamedTargetsGltf, 1, sizeof(kNamedTargetsGltf) - 1, file); - std::fclose(file); + ::fwrite(kNamedTargetsGltf, 1, sizeof(kNamedTargetsGltf) - 1, file); + ::fclose(file); asset::ImportedScene scene; - bool loaded = asset::LoadGltfScene(path.string(), &scene); - std::filesystem::remove(path); + bool loaded = asset::LoadGltfScene(path, &scene); + rx::fs::Remove(path); if (!loaded || scene.meshes.size() != 1) return Fail("generated gltf did not load"); const asset::Mesh& mesh = scene.meshes[0]; @@ -137,7 +139,7 @@ int TestAnimatedMorphCube(const char* path) { if (target.position_deltas.size() != verts * 3) return Fail("position delta size"); if (target.normal_deltas.size() != verts * 3) return Fail("normal delta size"); if (target.tangent_deltas.size() != verts * 3) return Fail("tangent delta size"); - for (f32 d : target.position_deltas) magnitude = std::max(magnitude, std::abs(d)); + for (f32 d : target.position_deltas) magnitude = rx::Max(magnitude, ::abs(d)); } if (magnitude <= 0) return Fail("all position deltas are zero"); @@ -174,9 +176,9 @@ int TestAnimatedMorphCube(const char* path) { // and compares against the CPU accumulation. int TestGpuEvaluation(const char* path) { using namespace rx::render; - if (const char* rhi = std::getenv("RX_RHI")) { - if (std::strcmp(rhi, "vulkan") != 0) { - std::printf("morph_test: gpu section is vulkan-only (bda compute), skipping\n"); + if (const char* rhi = ::getenv("RX_RHI")) { + if (::strcmp(rhi, "vulkan") != 0) { + ::printf("morph_test: gpu section is vulkan-only (bda compute), skipping\n"); return 0; } } @@ -191,35 +193,35 @@ int TestGpuEvaluation(const char* path) { DeviceDesc desc; desc.backend = Backend::kVulkan; desc.request_raytracing = false; - std::unique_ptr device = Device::CreateOffscreen(desc); + base::UniquePointer device = Device::CreateOffscreen(desc); if (!device) return Fail("CreateOffscreen returned null"); if (device->is_stub()) { - std::printf("morph_test: no vulkan driver, skipping the gpu section\n"); + ::printf("morph_test: no vulkan driver, skipping the gpu section\n"); return 0; } - std::printf("morph_test: device '%s'\n", device->caps().adapter_name.c_str()); + ::printf("morph_test: device '%s'\n", device->caps().adapter_name.c_str()); // Delta buffer in the GpuMesh::morph_deltas layout. - std::vector deltas(static_cast(targets) * verts * 9, 0.0f); + base::Vector deltas(static_cast(targets) * verts * 9, 0.0f); for (u32 t = 0; t < targets; ++t) { const asset::MorphTarget& target = mesh.morph_targets[t]; for (u32 v = 0; v < verts; ++v) { f32* out = &deltas[(static_cast(t) * verts + v) * 9]; - std::memcpy(out, &target.position_deltas[v * 3], sizeof(f32) * 3); + base::MemCopy(out, &target.position_deltas[v * 3], sizeof(f32) * 3); if (!target.normal_deltas.empty()) { - std::memcpy(out + 3, &target.normal_deltas[v * 3], sizeof(f32) * 3); + base::MemCopy(out + 3, &target.normal_deltas[v * 3], sizeof(f32) * 3); } if (!target.tangent_deltas.empty()) { - std::memcpy(out + 6, &target.tangent_deltas[v * 3], sizeof(f32) * 3); + base::MemCopy(out + 6, &target.tangent_deltas[v * 3], sizeof(f32) * 3); } } } - std::vector base(static_cast(verts) * 9); + base::Vector base(static_cast(verts) * 9); for (u32 v = 0; v < verts; ++v) { const asset::Vertex& vertex = lod.vertices[v]; - std::memcpy(&base[v * 9 + 0], vertex.position, sizeof(f32) * 3); - std::memcpy(&base[v * 9 + 3], vertex.normal, sizeof(f32) * 3); - std::memcpy(&base[v * 9 + 6], vertex.tangent, sizeof(f32) * 3); + base::MemCopy(&base[v * 9 + 0], vertex.position, sizeof(f32) * 3); + base::MemCopy(&base[v * 9 + 3], vertex.normal, sizeof(f32) * 3); + base::MemCopy(&base[v * 9 + 6], vertex.tangent, sizeof(f32) * 3); } struct Pair { u32 target; @@ -275,10 +277,10 @@ int TestGpuEvaluation(const char* path) { for (const Pair& pair : pairs) { expected += pair.weight * deltas[(static_cast(pair.target) * verts + v) * 9 + c]; } - worst = std::max(worst, std::abs(result[v * 9 + c] - expected)); + worst = rx::Max(worst, ::abs(result[v * 9 + c] - expected)); } } - std::printf("morph_test: gpu vs cpu max error %g\n", worst); + ::printf("morph_test: gpu vs cpu max error %g\n", worst); if (worst > 1e-5f) return Fail("gpu evaluation diverges from the cpu reference"); device->DestroyPipeline(pipeline); @@ -296,6 +298,6 @@ int main(int argc, char** argv) { if (int rc = TestNamedTargets()) return rc; if (int rc = TestAnimatedMorphCube(argv[1])) return rc; if (int rc = TestGpuEvaluation(argv[1])) return rc; - std::printf("morph_test: PASS\n"); + ::printf("morph_test: PASS\n"); return 0; } diff --git a/test/nav_test.cc b/test/nav_test.cc index 0caa82c1..9ec82d9b 100644 --- a/test/nav_test.cc +++ b/test/nav_test.cc @@ -8,8 +8,8 @@ #include "nav/path.h" #include "nav/query.h" -#include -#include +#include +#include #include "ecs/world.h" #include "scene/components.h" @@ -23,13 +23,13 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "nav_test: FAIL: %s\n", message); + ::fprintf(stderr, "nav_test: FAIL: %s\n", message); ++failures; } void Near(f32 actual, f32 expected, const char* message, f32 epsilon = 1e-3f) { - if (std::fabs(actual - expected) <= epsilon) return; - std::fprintf(stderr, "nav_test: FAIL: %s (got %.4f, expected %.4f)\n", message, actual, + if (::fabs(actual - expected) <= epsilon) return; + ::fprintf(stderr, "nav_test: FAIL: %s (got %.4f, expected %.4f)\n", message, actual, expected); ++failures; } @@ -84,7 +84,7 @@ void TestBuildAndQueries(NavMesh& mesh) { Check(mesh.Area(mesh.CellAt({14, 0, 8})) == kAreaRock, "rock band painted by sampler"); Check(mesh.Area(mesh.CellAt({16, 0, 25})) == kAreaWater, "river present"); f32 h = 1; - Check(mesh.HeightAt(10, 10, &h) && std::fabs(h) < 1e-3f, "surface height on open ground"); + Check(mesh.HeightAt(10, 10, &h) && ::fabs(h) < 1e-3f, "surface height on open ground"); const CellRef clamp = mesh.ClampToWalkable({5.0f, 0, 5.0f}, 3.0f); Check(clamp.valid() && mesh.Walkable(clamp), "off-mesh point clamps to walkable cell"); @@ -399,6 +399,6 @@ int main() { NavMesh mesh = MakeWorld(); TestTileRemoval(mesh); } - if (failures == 0) std::printf("nav_test: all checks passed\n"); + if (failures == 0) ::printf("nav_test: all checks passed\n"); return failures; } diff --git a/test/net_test.cc b/test/net_test.cc index e0e68a42..31a73740 100644 --- a/test/net_test.cc +++ b/test/net_test.cc @@ -3,9 +3,10 @@ // streaming bubbles (membership hysteresis + sticky deterministic ownership). // Pure CPU logic, no GPU, driver or transport involved. -#include -#include +#include +#include +#include "base/containers/vector.h" #include "ecs/world.h" #include "net/bubble.h" #include "net/protocol.h" @@ -20,7 +21,7 @@ int g_failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++g_failures; \ } \ } while (0) @@ -73,7 +74,7 @@ void TestWireRoundtrip() { w.F32(3.5f); w.Bool(true); w.Str("bubble"); - std::vector buf = w.Take(); + base::Vector buf = w.Take(); net::ByteReader r(buf.data(), buf.size()); CHECK_EQ(r.U8(), 7); @@ -107,7 +108,7 @@ void TestProtocolRoundtrip() { accept.server_tick = 1234; accept.client_id = 3; accept.snapshot_rate = 20; - std::vector blob = accept.Encode(); + base::Vector blob = accept.Encode(); auto decoded = net::JoinAccept::Decode(blob.data(), blob.size()); CHECK(decoded && decoded->player_entity == 99 && decoded->server_tick == 1234 && decoded->client_id == 3 && decoded->snapshot_rate == 20); @@ -122,7 +123,7 @@ void TestProtocolRoundtrip() { e.user_tag = 0xdef; snap.entities.push_back(e); snap.despawned.push_back(11); - std::vector blob = snap.Encode(); + base::Vector blob = snap.Encode(); auto decoded = net::Snapshot::Decode(blob.data(), blob.size()); CHECK(decoded && decoded->server_tick == 77 && decoded->full); CHECK(decoded->entities.size() == 1 && decoded->entities[0] == e); @@ -139,7 +140,7 @@ void TestProtocolRoundtrip() { b.entity_count = 7; b.owned_count = 4; bubbles.push_back(b); - std::vector blob = net::EncodeBubbleSync(bubbles); + base::Vector blob = net::EncodeBubbleSync(bubbles); auto decoded = net::DecodeBubbleSync(blob.data(), blob.size()); CHECK(decoded && decoded->size() == 1 && (*decoded)[0] == b); } @@ -149,7 +150,7 @@ void TestProtocolRoundtrip() { input.move_x = -1; input.yaw = 0.5f; input.buttons = 3; - std::vector blob = input.Encode(); + base::Vector blob = input.Encode(); auto decoded = net::PlayerInput::Decode(blob.data(), blob.size()); CHECK(decoded && decoded->client_tick == 9 && decoded->move_x == -1.0f && decoded->yaw == 0.5f && decoded->buttons == 3); @@ -287,9 +288,9 @@ void TestInterpolation() { net::TickInterpolation(world, 0.5f); const scene::Transform* now = world.Get(e); - CHECK(std::fabs(now->position[0] - 5.0f) < 1e-4f); + CHECK(::fabs(now->position[0] - 5.0f) < 1e-4f); const net::ReplicatedGait* gait = world.Get(e); - CHECK(gait->moving && std::fabs(gait->speed - 10.0f) < 1e-3f); + CHECK(gait->moving && ::fabs(gait->speed - 10.0f) < 1e-3f); net::TickInterpolation(world, 0.6f); now = world.Get(e); @@ -434,9 +435,9 @@ int main() { TestBubbleOwnership(); TestBubbleCounts(); if (g_failures == 0) { - std::printf("net_test: all passed\n"); + ::printf("net_test: all passed\n"); return 0; } - std::printf("net_test: %d failure(s)\n", g_failures); + ::printf("net_test: %d failure(s)\n", g_failures); return 1; } diff --git a/test/offscreen_test.cc b/test/offscreen_test.cc index 594e8e0a..32ff5022 100644 --- a/test/offscreen_test.cc +++ b/test/offscreen_test.cc @@ -7,15 +7,16 @@ // (exit 0) when no driver is present: CreateOffscreen then returns a // null-backend stub. Run under vkrun to exercise the real GPU path. -#include -#include -#include -#include +#include +#include +#include #include "render/rhi/command_list.h" #include "render/rhi/device.h" // Build-embedded test shaders (test/shaders/*.slang -> generated/shaders/*.h). +#include "base/containers/vector.h" +#include "base/memory/unique_pointer.h" #include "shaders/offscreen_tri_vs_slang.h" #include "shaders/offscreen_tri_ps_slang.h" @@ -24,7 +25,7 @@ using namespace rx::render; namespace { int Fail(const char* msg) { - std::fprintf(stderr, "offscreen_test: FAIL: %s\n", msg); + ::fprintf(stderr, "offscreen_test: FAIL: %s\n", msg); return 1; } @@ -32,22 +33,22 @@ int Fail(const char* msg) { int main() { DeviceDesc desc; - const char* rhi = std::getenv("RX_RHI"); - desc.backend = (rhi && std::strcmp(rhi, "d3d12") == 0) ? Backend::kD3D12 : Backend::kVulkan; + const char* rhi = ::getenv("RX_RHI"); + desc.backend = (rhi && ::strcmp(rhi, "d3d12") == 0) ? Backend::kD3D12 : Backend::kVulkan; desc.enable_validation = true; desc.request_raytracing = false; // not needed; keeps the adapter requirements minimal - std::unique_ptr device = Device::CreateOffscreen(desc); + base::UniquePointer device = Device::CreateOffscreen(desc); if (!device) return Fail("CreateOffscreen returned null"); if (device->is_stub()) { // No driver for the requested backend on this machine (plain ctest without // vkrun). A skip, not a failure - the real pixel path is proven under vkrun. - std::printf("offscreen_test: no %s driver, skipping (null backend)\n", + ::printf("offscreen_test: no %s driver, skipping (null backend)\n", BackendName(desc.backend)); return 77; } - std::printf("offscreen_test: device '%s'\n", device->caps().adapter_name.c_str()); + ::printf("offscreen_test: device '%s'\n", device->caps().adapter_name.c_str()); constexpr u32 kW = 64, kH = 64; const Format kFmt = Format::kRGBA8Unorm; @@ -76,14 +77,14 @@ int main() { color.clear[1] = kBg[1]; color.clear[2] = kBg[2]; color.clear[3] = kBg[3]; - cmd->BeginRendering({.extent = {kW, kH}, .colors = {&color, 1}}); + cmd->BeginRendering({.extent = {kW, kH}, .colors = base::Span(&color, 1)}); cmd->BindPipeline(pipeline); cmd->Draw(3); cmd->EndRendering(); device->SubmitFrame(cmd); // swapchainless device->WaitIdle(); - std::vector pixels(static_cast(kW) * kH * 4); + base::Vector pixels(static_cast(kW) * kH * 4); if (!device->ReadbackImage(image, ResourceState::kColorTarget, pixels.data(), pixels.size())) return Fail("ReadbackImage returned false"); @@ -93,18 +94,18 @@ int main() { // Centre: the red triangle. R high, B low. const u8* c = at(kW / 2, kH / 2); - std::printf("offscreen_test: center rgba = %u %u %u %u\n", c[0], c[1], c[2], c[3]); + ::printf("offscreen_test: center rgba = %u %u %u %u\n", c[0], c[1], c[2], c[3]); if (!(c[0] > 200 && c[2] < 60)) return Fail("center pixel is not the red triangle"); // A corner: the cleared blue background. B high, R low. const u8* corner = at(0, 0); - std::printf("offscreen_test: corner rgba = %u %u %u %u\n", corner[0], corner[1], corner[2], + ::printf("offscreen_test: corner rgba = %u %u %u %u\n", corner[0], corner[1], corner[2], corner[3]); if (!(corner[2] > 200 && corner[0] < 60)) return Fail("corner pixel is not the background"); device->DestroyPipeline(pipeline); device->DestroyImage(image); - std::printf("offscreen_test: PASS\n"); + ::printf("offscreen_test: PASS\n"); return 0; } diff --git a/test/openpbr_test.cc b/test/openpbr_test.cc index 5d9e63e0..6e18bf32 100644 --- a/test/openpbr_test.cc +++ b/test/openpbr_test.cc @@ -3,12 +3,14 @@ // that apply to unauthored inputs, and the guarantee that a legacy // standard_surface document is not silently reinterpreted as OpenPBR. -#include -#include -#include -#include +#include +#include +#include #include "asset/materialx.h" +#include "base/containers/vector.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" namespace { @@ -86,20 +88,20 @@ int failures = 0; void Check(bool condition, const char *message) { if (condition) return; - std::fprintf(stderr, "openpbr_test: FAIL: %s\n", message); + ::fprintf(stderr, "openpbr_test: FAIL: %s\n", message); ++failures; } -bool Write(const std::filesystem::path &path, const char *text, size_t size) { - std::FILE *file = std::fopen(path.string().c_str(), "wb"); +bool Write(const base::String &path, const char *text, size_t size) { + FILE *file = ::fopen(path.c_str(), "wb"); if (!file) return false; - const bool ok = std::fwrite(text, 1, size, file) == size; - std::fclose(file); + const bool ok = ::fwrite(text, 1, size, file) == size; + ::fclose(file); return ok; } -bool Near(f32 value, f32 expected) { return std::fabs(value - expected) < 1e-4f; } +bool Near(f32 value, f32 expected) { return ::fabs(value - expected) < 1e-4f; } bool Near3(const f32 *value, f32 x, f32 y, f32 z) { return Near(value[0], x) && Near(value[1], y) && Near(value[2], z); @@ -107,13 +109,13 @@ bool Near3(const f32 *value, f32 x, f32 y, f32 z) { // Writes `text` to a temp .mtlx, loads it, removes it. Returns load success. bool LoadDoc(const char *name, const char *text, size_t size, asset::Material *out) { - const std::filesystem::path path = std::filesystem::temp_directory_path() / name; + const base::String path = rx::fs::Join(rx::fs::TempDirectory(), name); if (!Write(path, text, size)) { - std::fprintf(stderr, "openpbr_test: cannot create fixture %s\n", name); + ::fprintf(stderr, "openpbr_test: cannot create fixture %s\n", name); return false; } - const bool loaded = asset::LoadMaterialX(path.string(), out); - std::filesystem::remove(path); + const bool loaded = asset::LoadMaterialX(path, out); + rx::fs::Remove(path); return loaded; } @@ -229,7 +231,7 @@ void CheckUnknownShaderRejected() { Check(!loaded, "a document with neither surface node is rejected"); } -bool Finite(f32 v) { return std::isfinite(v); } +bool Finite(f32 v) { return ::isfinite(v); } bool Finite3(const f32 *v) { return Finite(v[0]) && Finite(v[1]) && Finite(v[2]); } @@ -238,31 +240,35 @@ bool Finite3(const f32 *v) { return Finite(v[0]) && Finite(v[1]) && Finite(v[2]) // the failure mode the hand-written fixtures cannot: a real authored document // that parses into something the renderer would choke on. void CheckExampleCorpus() { - const char *dir_env = std::getenv("RX_OPENPBR_EXAMPLES"); + const char *dir_env = ::getenv("RX_OPENPBR_EXAMPLES"); if (dir_env == nullptr) return; - const std::filesystem::path dir(dir_env); - if (!std::filesystem::is_directory(dir)) { + const base::String dir(dir_env); + if (!rx::fs::IsDirectory(dir)) { Check(false, "RX_OPENPBR_EXAMPLES points at a directory"); return; } + base::Vector entries; + if (!rx::fs::ListDirectory(dir, &entries)) { + Check(false, "RX_OPENPBR_EXAMPLES can be listed"); + return; + } int loaded = 0, rejected = 0; - for (const auto &entry : std::filesystem::directory_iterator(dir)) { - if (entry.path().extension() != ".mtlx") + for (const rx::fs::DirEntry &entry : entries) { + if (rx::fs::Extension(entry.path) != ".mtlx") continue; + const base::String name(rx::fs::Filename(entry.path)); asset::Material m; - if (!asset::LoadMaterialX(entry.path().string(), &m)) { - std::fprintf(stderr, "openpbr_test: rejected %s\n", - entry.path().filename().string().c_str()); + if (!asset::LoadMaterialX(entry.path, &m)) { + ::fprintf(stderr, "openpbr_test: rejected %s\n", name.c_str()); ++rejected; continue; } ++loaded; - const std::string name = entry.path().filename().string(); const auto fail = [&](const char *what) { - std::fprintf(stderr, "openpbr_test: FAIL: %s: %s\n", name.c_str(), what); + ::fprintf(stderr, "openpbr_test: FAIL: %s: %s\n", name.c_str(), what); ++failures; }; if (!Finite3(m.base_color_factor) || !Finite(m.base_color_factor[3])) @@ -292,12 +298,12 @@ void CheckExampleCorpus() { Check(rejected == 0, "every example material parses"); Check(loaded > 50, "the example corpus is actually present and populated"); - std::printf("openpbr_test: swept %d example material(s)\n", loaded); + ::printf("openpbr_test: swept %d example material(s)\n", loaded); // Two spot checks against values read straight out of the source documents, // so the sweep cannot pass by importing everything as defaults. asset::Material brass; - if (asset::LoadMaterialX((dir / "open_pbr_brass.mtlx").string(), &brass)) { + if (asset::LoadMaterialX(rx::fs::Join(dir, "open_pbr_brass.mtlx"), &brass)) { Check(Near3(brass.base_color_factor, 0.844f, 0.782f, 0.473f) && Near(brass.metallic_factor, 1.0f) && Near(brass.roughness_factor, 0.02f), @@ -308,7 +314,7 @@ void CheckExampleCorpus() { "brass keeps its greater-than-one specular edge tint"); } asset::Material carpaint; - if (asset::LoadMaterialX((dir / "open_pbr_carpaint.mtlx").string(), &carpaint)) { + if (asset::LoadMaterialX(rx::fs::Join(dir, "open_pbr_carpaint.mtlx"), &carpaint)) { Check(Near(carpaint.clearcoat, 1.0f) && Near(carpaint.clearcoat_roughness, 0.02f) && Near(carpaint.coat_ior, 1.6f), "car paint imports its coat"); @@ -327,9 +333,9 @@ int main() { CheckExampleCorpus(); if (failures == 0) { - std::puts("openpbr_test: PASS"); + ::puts("openpbr_test: PASS"); return 0; } - std::fprintf(stderr, "openpbr_test: %d failure(s)\n", failures); + ::fprintf(stderr, "openpbr_test: %d failure(s)\n", failures); return 1; } diff --git a/test/path_alpha_test.cc b/test/path_alpha_test.cc index 7d189791..3992bbfa 100644 --- a/test/path_alpha_test.cc +++ b/test/path_alpha_test.cc @@ -1,12 +1,13 @@ -#include -#include -#include +#include +#include +#include #include "asset/mesh.h" #include "render/core/bindless.h" #include "shaders/path_alpha_recon_cs_hlsl.h" #include "shaders/path_alpha_di_cs_hlsl.h" #ifdef RX_HAS_NRD +#include "base/memory/mem_ops.h" #include "shaders/path_alpha_nrd_cs_hlsl.h" #endif @@ -15,13 +16,13 @@ using namespace rx::render; int main() { DeviceDesc desc; - const char* backend = std::getenv("RX_RHI"); - desc.backend = backend && std::strcmp(backend, "d3d12") == 0 + const char* backend = ::getenv("RX_RHI"); + desc.backend = backend && ::strcmp(backend, "d3d12") == 0 ? Backend::kD3D12 : Backend::kVulkan; desc.enable_validation = true; auto device = Device::CreateOffscreen(desc); if (!device || device->is_stub()) { - std::printf("path_alpha_test: SKIP, GPU unavailable\n"); + ::printf("path_alpha_test: SKIP, GPU unavailable\n"); return 77; } auto bindless = BindlessRegistry::Create(*device); @@ -31,10 +32,10 @@ int main() { {.position = {0, 1, 0}}}; const u32 indices[3] = {0, 1, 2}; GpuBuffer vb = device->CreateBufferWithData( - {reinterpret_cast(vertices), sizeof(vertices)}, + ByteSpan(reinterpret_cast(vertices), sizeof(vertices)), kBufferUsageStorage | kBufferUsageDeviceAddress); GpuBuffer ib = device->CreateBufferWithData( - {reinterpret_cast(indices), sizeof(indices)}, + ByteSpan(reinterpret_cast(indices), sizeof(indices)), kBufferUsageStorage | kBufferUsageDeviceAddress); const f32 alphas[8] = {0, .49f, .5f, 1, 0, 0, .75f, .75f}; const f32 cutoffs[8] = {.5f, .5f, .5f, .5f, .5f, 0, .8f, .7f}; @@ -69,10 +70,10 @@ int main() { }); device->InvalidateBuffer(readback, 0, sizeof(expected)); u32 actual[8]; - std::memcpy(actual, readback.mapped, sizeof(actual)); + base::MemCopy(actual, readback.mapped, sizeof(actual)); for (u32 i = 0; i < 8; ++i) { if (actual[i] == expected[i]) continue; - std::printf("FAIL: %s alpha=%g cutoff=%g got=%u expected=%u\n", + ::printf("FAIL: %s alpha=%g cutoff=%g got=%u expected=%u\n", name, alphas[i], cutoffs[i], actual[i], expected[i]); ++failures; } @@ -84,11 +85,11 @@ int main() { run(RX_SHADER(k_path_alpha_nrd_cs_hlsl), "nrd"); #endif device->WaitIdle(); - bindless.reset(); + bindless.Reset(); device->DestroyBuffer(vb); device->DestroyBuffer(ib); device->DestroyBuffer(result); device->DestroyBuffer(readback); - std::printf("path_alpha_test: %d failures\n", failures); + ::printf("path_alpha_test: %d failures\n", failures); return failures ? 1 : 0; } diff --git a/test/path_material_test.cc b/test/path_material_test.cc index c999a6a4..3a8a7f94 100644 --- a/test/path_material_test.cc +++ b/test/path_material_test.cc @@ -1,4 +1,4 @@ -#include +#include #include "asset/material.h" #include "asset/mesh.h" @@ -14,7 +14,7 @@ int main() { desc.enable_validation = true; if (!renderer.InitializeOffscreen(desc, 32, 32)) return 1; if (!renderer.caps() || !renderer.caps()->ray_query) { - std::printf("path_material_test: SKIP, ray queries unavailable\n"); + ::printf("path_material_test: SKIP, ray queries unavailable\n"); return 77; } RenderSettings& settings = renderer.settings(); @@ -67,12 +67,12 @@ int main() { view.camera_cut = true; renderer.RenderFrame(view); const u32 cut_samples = renderer.path_trace_samples(); - std::printf("rigid motion: static=%u moving=%u stopped=%u cut=%u\n", + ::printf("rigid motion: static=%u moving=%u stopped=%u cut=%u\n", static_samples, moving_samples, stopped_samples, cut_samples); renderer.Shutdown(); const bool ok = accumulated > 0 && restarted > 0 && restarted < accumulated && resumed == 2 * restarted && !missing_updated && after_missing == 3 * restarted; - std::printf("path_material_test: accumulated=%u restarted=%u resumed=%u after_missing=%u\n", + ::printf("path_material_test: accumulated=%u restarted=%u resumed=%u after_missing=%u\n", accumulated, restarted, resumed, after_missing); return ok && static_samples == 2 * moving_samples && stopped_samples == static_samples && cut_samples == moving_samples ? 0 : 1; diff --git a/test/path_motion_test.cc b/test/path_motion_test.cc index c24dcfc4..078b80cb 100644 --- a/test/path_motion_test.cc +++ b/test/path_motion_test.cc @@ -1,7 +1,7 @@ -#include -#include -#include -#include +#include +#include +#include +#include #include "asset/mesh.h" #include "render/core/bindless.h" @@ -10,6 +10,9 @@ #include "render/rhi/device.h" #include "shaders/path_motion_recon_cs_hlsl.h" #ifdef RX_HAS_NRD +#include "base/algorithm.h" +#include "base/memory/mem_ops.h" +#include "core/scalar.h" #include "shaders/path_motion_nrd_cs_hlsl.h" #endif @@ -19,7 +22,7 @@ using namespace rx::render; int main() { int failures = 0; auto check = [&](bool ok, const char* message) { - if (!ok) { std::printf("FAIL: %s\n", message); ++failures; } + if (!ok) { ::printf("FAIL: %s\n", message); ++failures; } }; PathSceneHistory history; base::Vector instances(2); @@ -38,14 +41,14 @@ int main() { check(instances[0].history_id == first && instances[0].previous_mesh == 7, "rigid motion retains valid surface history"); instances[0].previous_transform = instances[0].transform; - std::swap(instances[0], instances[1]); + base::Swap(instances[0], instances[1]); check(history.Update(instances, {}), "draw reorder resets reference accumulation"); check(instances[0].history_id != first && instances[0].history_id != second && instances[0].previous_mesh == 0xffffffffu, "reordered instances cannot inherit another object's history"); Mat4 bone = Mat4::Identity(); - history.Update(instances, {&bone, 1}); + history.Update(instances, base::Span(&bone, 1)); bone = MakeTranslation({0, 0, 1}); - check(history.Update(instances, {&bone, 1}), "pose changes reset reference accumulation"); + check(history.Update(instances, base::Span(&bone, 1)), "pose changes reset reference accumulation"); instances.clear(); check(history.Update(instances, {}), "removed geometry resets reference accumulation"); @@ -54,7 +57,7 @@ int main() { desc.request_raytracing = true; auto device = Device::CreateOffscreen(desc); if (!device || device->is_stub() || !device->caps().ray_query) { - std::printf("path_motion_test: GPU checks SKIP, %d CPU failures\n", failures); + ::printf("path_motion_test: GPU checks SKIP, %d CPU failures\n", failures); return failures ? 1 : 77; } auto rt = RayTracingContext::Create(*device); @@ -66,10 +69,10 @@ int main() { const u32 indices[3] = {0, 1, 2}; GpuMesh mesh; mesh.vertices = device->CreateBufferWithData( - {reinterpret_cast(vertices), sizeof(vertices)}, + ByteSpan(reinterpret_cast(vertices), sizeof(vertices)), kBufferUsageStorage | kBufferUsageDeviceAddress | kBufferUsageAccelBuildInput); mesh.indices = device->CreateBufferWithData( - {reinterpret_cast(indices), sizeof(indices)}, + ByteSpan(reinterpret_cast(indices), sizeof(indices)), kBufferUsageStorage | kBufferUsageDeviceAddress | kBufferUsageAccelBuildInput); mesh.vertex_count = mesh.index_count = 3; mesh.submeshes.push_back({.index_count = 3}); @@ -78,7 +81,7 @@ int main() { const u32 current_mesh = bindless->RegisterMesh(mesh.vertices, mesh.indices, &geometry, 1); for (auto& vertex : vertices) vertex.position[0] -= .25f; GpuBuffer previous_vertices = device->CreateBufferWithData( - {reinterpret_cast(vertices), sizeof(vertices)}, kBufferUsageStorage | kBufferUsageDeviceAddress); + ByteSpan(reinterpret_cast(vertices), sizeof(vertices)), kBufferUsageStorage | kBufferUsageDeviceAddress); const u32 previous_mesh = bindless->RegisterMesh(previous_vertices, mesh.indices, &geometry, 1); if (!rt->BuildBlas(1, mesh) || !rt->ReserveTlas(0, 2)) return 1; GpuBuffer result = device->CreateBuffer(8 * sizeof(f32), kBufferUsageStorage | kBufferUsageTransferSrc); @@ -110,14 +113,14 @@ int main() { }); device->InvalidateBuffer(readback, 0, 8 * sizeof(f32)); f32 data[8]; - std::memcpy(data, readback.mapped, sizeof(data)); - std::printf("%s previous=%u pos=(%g,%g,%g) motion=(%g,%g) id=%g\n", + base::MemCopy(data, readback.mapped, sizeof(data)); + ::printf("%s previous=%u pos=(%g,%g,%g) motion=(%g,%g) id=%g\n", recon ? "recon" : "nrd", previous, data[0], data[1], data[2], data[4], data[5], data[6]); check(data[7] == 1 && data[3] == (valid ? 1 : 0), "trace hit and motion validity"); if (valid) { - check(std::abs(data[0] - expected_x) < 1e-5f && std::abs(data[1] - .5f) < 1e-5f, + check(::abs(data[0] - expected_x) < 1e-5f && ::abs(data[1] - .5f) < 1e-5f, "previous position includes rigid transform and previous vertex pose"); - check(std::abs(data[4] - std::clamp(expected_x * .5f, -2.f, 2.f)) < 1e-5f && std::abs(data[5] - .25f) < 1e-5f, + check(::abs(data[4] - rx::Clamp(expected_x * .5f, -2.f, 2.f)) < 1e-5f && ::abs(data[5] - .25f) < 1e-5f, "motion is current-to-previous in UV units"); } else { check(data[4] == 2 && data[5] == 2, "missing surface history rejects reprojection"); @@ -137,13 +140,13 @@ int main() { run(RX_SHADER(k_path_motion_nrd_cs_hlsl), 6, 9, false, current_mesh, 1e6f, true, 1e6f); #endif device->WaitIdle(); - rt.reset(); - bindless.reset(); + rt.Reset(); + bindless.Reset(); device->DestroyBuffer(mesh.vertices); device->DestroyBuffer(mesh.indices); device->DestroyBuffer(previous_vertices); device->DestroyBuffer(result); device->DestroyBuffer(readback); - std::printf("path_motion_test: %d failures\n", failures); + ::printf("path_motion_test: %d failures\n", failures); return failures ? 1 : 0; } diff --git a/test/path_sampling_test.cc b/test/path_sampling_test.cc index 1cacce6d..ded6b8f3 100644 --- a/test/path_sampling_test.cc +++ b/test/path_sampling_test.cc @@ -1,9 +1,10 @@ -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" #include "render/rhi/device.h" #include "render/core/bindless.h" #include "render/gi/raytracing.h" @@ -40,19 +41,19 @@ struct Push { int main() { DeviceDesc desc; - const char* backend = std::getenv("RX_RHI"); - desc.backend = backend && std::strcmp(backend, "d3d12") == 0 + const char* backend = ::getenv("RX_RHI"); + desc.backend = backend && ::strcmp(backend, "d3d12") == 0 ? Backend::kD3D12 : Backend::kVulkan; desc.request_raytracing = true; desc.enable_validation = true; auto device = Device::CreateOffscreen(desc); if (!device || device->is_stub()) { - std::printf("path_sampling_test: SKIP, GPU unavailable\n"); + ::printf("path_sampling_test: SKIP, GPU unavailable\n"); return 77; } int failures = 0; auto check = [&](bool ok, const char* message) { - if (!ok) { std::printf("FAIL: %s\n", message); ++failures; } + if (!ok) { ::printf("FAIL: %s\n", message); ++failures; } }; ComputePipelineDesc pd; pd.shader = RX_SHADER(k_recon_restir_di_temporal_cs_hlsl); @@ -68,20 +69,20 @@ int main() { PipelineHandle pipeline = device->CreateComputePipeline(pd); if (!pipeline) return 1; - std::vector owned; + base::Vector owned; auto input = [&](Format format, const void* pixel, u32 pixel_bytes) { - std::vector data(kPixels * pixel_bytes); + base::Vector data(kPixels * pixel_bytes); for (u32 i = 0; i < kPixels; ++i) - std::memcpy(data.data() + i * pixel_bytes, pixel, pixel_bytes); + base::MemCopy(data.data() + i * pixel_bytes, pixel, pixel_bytes); GpuImage image = device->CreateImage2D( format, {kWidth, kHeight}, kTextureUsageSampled | kTextureUsageTransferDst); GpuBuffer staging = device->CreateBufferWithData( - {data.data(), data.size()}, kBufferUsageTransferSrc); - if (!image || !staging) std::exit(1); + ByteSpan(data.data(), data.size()), kBufferUsageTransferSrc); + if (!image || !staging) ::exit(1); device->ImmediateSubmit([&](CommandList& cmd) { cmd.Barrier(Transition(image, ResourceState::kUndefined, ResourceState::kCopyDst)); BufferTextureCopy copy{.extent = {kWidth, kHeight}}; - cmd.CopyBufferToTexture(staging, image, {©, 1}); + cmd.CopyBufferToTexture(staging, image, base::Span(©, 1)); cmd.Barrier(Transition(image, ResourceState::kCopyDst, ResourceState::kShaderReadCompute)); }); device->DestroyBuffer(staging); @@ -113,23 +114,23 @@ int main() { f32 light[16] = {0, 1, 0, 10, 1, 1, 1, 0}; GpuBuffer lights = device->CreateBuffer(sizeof(light), kBufferUsageStorage, true); - std::vector cdf(1 + kSkyH + 2 * kSkyW * kSkyH); - const f32 omega = 2 * kPi / kSkyW * (1 - std::cos(kPi / kSkyH)); + base::Vector cdf(1 + kSkyH + 2 * kSkyW * kSkyH); + const f32 omega = 2 * kPi / kSkyW * (1 - ::cos(kPi / kSkyH)); cdf[0] = omega; for (u32 r = 0; r < kSkyH; ++r) cdf[1 + r] = omega; for (u32 c = 0; c < kSkyW; ++c) cdf[1 + kSkyH + c] = 1; cdf[1 + kSkyH + kSkyW * kSkyH] = 1; GpuBuffer table = device->CreateBufferWithData( - {reinterpret_cast(cdf.data()), cdf.size() * sizeof(f32)}, kBufferUsageStorage); + ByteSpan(reinterpret_cast(cdf.data()), cdf.size() * sizeof(f32)), kBufferUsageStorage); if (!lights.mapped || !table) return 1; SamplerHandle sampler = device->GetSampler({}); - std::vector values[4]; + base::Vector values[4]; for (auto& value : values) value.resize(kPixels * 4); bool first = true; auto run = [&](Push push, f32 point_target, GpuImage prev0 = {}, GpuImage prev1 = {}, GpuImage prev2 = {}, GpuImage prev3 = {}) { light[7] = point_target / (.99f * .99f); - std::memcpy(lights.mapped, light, sizeof(light)); + base::MemCopy(lights.mapped, light, sizeof(light)); device->FlushBuffer(lights, 0, sizeof(light)); device->ImmediateSubmit([&](CommandList& cmd) { if (!first) @@ -163,8 +164,8 @@ int main() { f32 expected = mode == 2 ? 2 : 1; bool correct = true; for (u32 p = 0; p < kPixels; ++p) - correct &= std::abs(values[1][4 * p + 2] - expected) < 1e-4f; - std::printf("candidates=%u mode=%u W=%g expected=%g\n", candidates, mode, + correct &= ::abs(values[1][4 * p + 2] - expected) < 1e-4f; + ::printf("candidates=%u mode=%u W=%g expected=%g\n", candidates, mode, values[1][2], expected); check(correct, "candidate count must not change total light energy"); } @@ -172,17 +173,17 @@ int main() { Push push; push.candidates = 0; run(push, 1); - check(std::abs(values[1][2] - 1) < 1e-5f, "zero point proposals retain sun energy"); + check(::abs(values[1][2] - 1) < 1e-5f, "zero point proposals retain sun energy"); push.candidates = 8; push.light_count = 0; run(push, 0); - check(std::abs(values[1][2] - 1) < 1e-5f, "sun-only scene retains its energy"); + check(::abs(values[1][2] - 1) < 1e-5f, "sun-only scene retains its energy"); push.sun_direction[3] = 0; push.sky_candidates = 1; run(push, 0); double mean = 0; u32 lower_half = 0, samples = 0; - const double cap = 1.0 - std::cos(double(kPi) / kSkyH); + const double cap = 1.0 - ::cos(double(kPi) / kSkyH); for (u32 p = 0; p < kPixels; ++p) { // Direction-to-cell rounding can place boundary samples in a dark neighbor. if (values[2][4 * p + 3] > -1.5f) continue; @@ -193,11 +194,11 @@ int main() { } mean /= samples ? samples : 1; const double fraction = double(lower_half) / (samples ? samples : 1); - std::printf("sky solid-angle mean=%g lower-half fraction=%g retained=%u/%u\n", + ::printf("sky solid-angle mean=%g lower-half fraction=%g retained=%u/%u\n", mean, fraction, samples, kPixels); check(samples >= kPixels - 8, "sky proposals must retain positive target mass"); - check(std::abs(mean - .5) < .015, "sky cells must be uniform in solid angle"); - check(std::abs(fraction - .5) < .02, "sky PDF must match cell samples"); + check(::abs(mean - .5) < .015, "sky cells must be uniform in solid angle"); + check(::abs(fraction - .5) < .02, "sky PDF must match cell samples"); const f32 dead_id[4] = {0, 0, 0, -1}, light_id[4] = {0, 0, 0, 1}; const f32 dead_mass[4] = {0, 2, 0, 0}, live_mass[4] = {4, 2, 2, 0}; @@ -211,12 +212,12 @@ int main() { reuse.reset = 0; run(reuse, 1, dead_sample, dead_history); const f32 dark_estimate = values[1][2]; - check(std::abs(values[1][1] - 4) < 1e-5f, "zero-weight DI history retains sample count"); + check(::abs(values[1][1] - 4) < 1e-5f, "zero-weight DI history retains sample count"); run(reuse, 1, live_sample, live_history); const f32 bright_estimate = values[1][2]; - std::printf("DI reuse: dark=%g bright=%g mean=%g expected=1\n", + ::printf("DI reuse: dark=%g bright=%g mean=%g expected=1\n", dark_estimate, bright_estimate, (dark_estimate + bright_estimate) / 2); - check(std::abs((dark_estimate + bright_estimate) / 2 - 1) < 1e-5f, + check(::abs((dark_estimate + bright_estimate) / 2 - 1) < 1e-5f, "equally likely zero and double-energy histories must preserve expected energy"); reuse.pad[1] = 1; run(reuse, 1, live_sample, live_history); @@ -225,7 +226,7 @@ int main() { reuse.light_count = 0; reuse.sky_candidates = 1; run(reuse, 0, {}, {}, dead_sample, dead_history); - check(std::abs(values[3][1] - 3) < 1e-5f, "zero-weight sky history retains sample count"); + check(::abs(values[3][1] - 3) < 1e-5f, "zero-weight sky history retains sample count"); check(values[3][4 * (kWidth - 1) + 1] == 1, "sky reservoir reprojection includes the jitter delta"); @@ -261,16 +262,16 @@ int main() { }); const u32 center = 4 * ((kHeight / 2) * kWidth + kWidth / 2) + channel; if (mass.format == Format::kRGBA16Float) { - std::vector data(kPixels * 4); + base::Vector data(kPixels * 4); check(device->ReadbackImage(mass, ResourceState::kGeneral, data.data(), data.size() * sizeof(u16)), "read half reservoir count"); - std::printf("%s: M half bits=%04x\n", name, data[center]); + ::printf("%s: M half bits=%04x\n", name, data[center]); check(data[center] > 0x4000 && data[center] < 0x7c00, name); } else { check(device->ReadbackImage(mass, ResourceState::kGeneral, values[0].data(), values[0].size() * sizeof(f32)), "read reservoir count"); - std::printf("%s: M=%g\n", name, values[0][center]); - check(values[0][center] > 2 && std::isfinite(values[0][center]), name); + ::printf("%s: M=%g\n", name, values[0][center]); + check(values[0][center] > 2 && ::isfinite(values[0][center]), name); } device->ImmediateSubmit([&](CommandList& cmd) { cmd.Barrier(Transition(mass, ResourceState::kCopySrc, ResourceState::kGeneral)); @@ -298,7 +299,7 @@ int main() { Bind::Sampled(9, vz), Bind::Sampled(10, vz), Bind::Sampled(11, id), Bind::Sampled(12, id), Bind::Sampled(13, motion), Bind::Sampled(14, empty), Bind::Sampled(15, gi_history), Bind::Sampled(16, empty), Bind::Sampled(17, position)}, half_out, 3, "zero-weight GI temporal history retains sample count"); - std::vector jitter_counts(kPixels * 4); + base::Vector jitter_counts(kPixels * 4); check(device->ReadbackImage(half_out, ResourceState::kGeneral, jitter_counts.data(), jitter_counts.size() * sizeof(u16)), "read GI jitter reprojection"); check(jitter_counts[3] == 0x4200 && jitter_counts[4 * (kWidth - 1) + 3] == 0x3c00, @@ -321,8 +322,8 @@ int main() { "GI temporal reconnection retains sample count"); check(device->ReadbackImage(out[0], ResourceState::kGeneral, values[0].data(), values[0].size() * sizeof(f32)), "read GI reconnection weight"); - std::printf("GI reconnection W=%g expected=%g\n", values[0][3], kPi * .5f); - check(std::abs(values[0][3] - kPi * .5f) < 1e-5f, "temporal GI must convert source solid angle to current solid angle"); + ::printf("GI reconnection W=%g expected=%g\n", values[0][3], kPi * .5f); + check(::abs(values[0][3] - kPi * .5f) < 1e-5f, "temporal GI must convert source solid angle to current solid angle"); device->ImmediateSubmit([&](CommandList& cmd) { cmd.Barrier(Transition(out[0], ResourceState::kCopySrc, ResourceState::kGeneral)); }); @@ -346,7 +347,7 @@ int main() { Bind::Storage(9, out[0]), Bind::Storage(10, out[1]), Bind::Combined(11, sky.view, sampler), Bind::StorageBuffer(12, table), Bind::Sampled(13, dead_sample), Bind::Sampled(14, dead_history), Bind::Storage(15, out[2]), Bind::Storage(16, out[3])}, out[1], 1, - "zero-weight DI spatial neighbors retain sample count", bindless.get()); + "zero-weight DI spatial neighbors retain sample count", bindless.Get_UseOnlyIfYouKnowWhatYouareDoing()); check(device->ReadbackImage(out[3], ResourceState::kGeneral, values[0].data(), values[0].size() * sizeof(f32)), "read spatial sky count"); check(values[0][4 * ((kHeight / 2) * kWidth + kWidth / 2) + 1] > 2, @@ -366,7 +367,7 @@ int main() { Bind::Storage(12, out[1])}, half_out, 3, "zero-weight GI spatial neighbors retain sample count"); } else { - std::printf("path_sampling_test: spatial checks SKIP, ray queries unavailable\n"); + ::printf("path_sampling_test: spatial checks SKIP, ray queries unavailable\n"); } PipelineHandle continuation = device->CreateComputePipeline({ .shader = RX_SHADER(k_path_continue_cs_hlsl), @@ -384,8 +385,8 @@ int main() { for (u32 p = 0; p < kPixels; ++p) for (u32 c = 0; c < 3; ++c) energy[c] += values[0][4 * p + c] / kPixels; for (u32 c = 0; c < 3; ++c) - check(std::abs(energy[c] - .001 * (c + 1)) < .00015, "roulette retains expected energy of dim paths"); - std::printf("dim-path expected energy=(.001,.002,.003), measured=(%g,%g,%g)\n", energy[0], energy[1], energy[2]); + check(::abs(energy[c] - .001 * (c + 1)) < .00015, "roulette retains expected energy of dim paths"); + ::printf("dim-path expected energy=(.001,.002,.003), measured=(%g,%g,%g)\n", energy[0], energy[1], energy[2]); device->DestroyPipeline(continuation); device->DestroyImage(half_out); device->DestroyImage(shaded); @@ -397,6 +398,6 @@ int main() { device->DestroyBuffer(lights); device->DestroyBuffer(table); device->DestroyPipeline(pipeline); - std::printf("path_sampling_test: %d failures\n", failures); + ::printf("path_sampling_test: %d failures\n", failures); return failures ? 1 : (device->caps().ray_query ? 0 : 77); } diff --git a/test/physics_body_test.cc b/test/physics_body_test.cc index ea242616..35056344 100644 --- a/test/physics_body_test.cc +++ b/test/physics_body_test.cc @@ -1,8 +1,9 @@ // Exercises the body/joint adapter surface a physics-first locomotion feedback // controller reads and drives: body velocity/mass/COM, force/torque injection, // gravity, joint motor torque limits, and per-foot contact recording. -#include -#include +#include +#include +#include #include "physics/physics_world.h" @@ -17,23 +18,23 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "physics_body_test: FAIL: %s\n", message); + ::fprintf(stderr, "physics_body_test: FAIL: %s\n", message); ++failures; } void Near(f32 actual, f32 expected, const char* message, f32 epsilon = 1e-2f) { - if (std::abs(actual - expected) <= epsilon) return; - std::fprintf(stderr, "physics_body_test: FAIL: %s (got %.4f, expected %.4f)\n", message, actual, + if (::abs(actual - expected) <= epsilon) return; + ::fprintf(stderr, "physics_body_test: FAIL: %s (got %.4f, expected %.4f)\n", message, actual, expected); ++failures; } // `actual` within `frac` (fraction, e.g. 0.1 = 10%) of `expected`. void NearRel(f32 actual, f32 expected, f32 frac, const char* message) { - Near(actual, expected, message, frac * std::abs(expected)); + Near(actual, expected, message, frac * ::abs(expected)); } -bool Finite(f32 v) { return std::isfinite(v); } +bool Finite(f32 v) { return ::isfinite(v); } bool Finite(const Vec3& v) { return Finite(v.x) && Finite(v.y) && Finite(v.z); } void CheckFinite(const Vec3& v, const char* message) { Check(Finite(v), message); } @@ -152,7 +153,7 @@ void TestApplyTorque() { Check(w.GetBodyVelocity(box, &lin, &ang1), "angular velocity readable"); CheckFinite(ang1, "angular velocity finite (early)"); Check(ang1.y > 0, "torque about +Y spins the body positively about Y"); - Check(std::abs(ang1.y) > std::abs(ang1.x) && std::abs(ang1.y) > std::abs(ang1.z), + Check(::abs(ang1.y) > ::abs(ang1.x) && ::abs(ang1.y) > ::abs(ang1.z), "spin stays about the applied (Y) axis"); spin(20); @@ -219,7 +220,7 @@ void TestContacts() { Check(Finite(c.impulse), "contact impulse finite"); // Normal points INTO the box, i.e. up for a box resting on the floor; // the contact sits at the box's bottom face (y ~ 0). - if (c.normal.y > 0.7f && std::abs(c.position.y) < 0.1f) floor_like = true; + if (c.normal.y > 0.7f && ::abs(c.position.y) < 0.1f) floor_like = true; } } Check(saw_contact, "a box landing on the floor reports at least one contact"); @@ -275,7 +276,7 @@ void TestMotors() { Check(w.GetJointOrientation(joint, q), "joint orientation readable"); Check(Finite(q[0]) && Finite(q[1]) && Finite(q[2]) && Finite(q[3]), "joint orientation finite"); // |dot| of unit quaternions = cos(half-angle); 1 means aligned. - return std::abs(q[0] * target[0] + q[1] * target[1] + q[2] * target[2] + q[3] * target[3]); + return ::abs(q[0] * target[0] + q[1] * target[1] + q[2] * target[2] + q[3] * target[3]); }; step(120); // 2 s @@ -303,7 +304,7 @@ void TestMotors() { step(20); f32 after[4] = {0, 0, 0, 1}; w.GetJointOrientation(joint, after); - const f32 moved = std::abs(before[0] * after[0] + before[1] * after[1] + before[2] * after[2] + + const f32 moved = ::abs(before[0] * after[0] + before[1] * after[1] + before[2] * after[2] + before[3] * after[3]); Check(moved < 0.999f, "disabled motors let the joint swing free"); } @@ -393,9 +394,9 @@ int main() { TestWatchedContactReuse(); if (failures == 0) { - std::printf("physics_body_test: all checks passed\n"); + ::printf("physics_body_test: all checks passed\n"); return 0; } - std::fprintf(stderr, "physics_body_test: %d checks failed\n", failures); + ::fprintf(stderr, "physics_body_test: %d checks failed\n", failures); return 1; } diff --git a/test/placement_gpu_test.cc b/test/placement_gpu_test.cc index 62c25f07..e38897ec 100644 --- a/test/placement_gpu_test.cc +++ b/test/placement_gpu_test.cc @@ -5,11 +5,13 @@ // cleanly (exit 0) when no driver is present; run under vkrun for the real // GPU path. The backend follows RX_RHI (vulkan|d3d12). -#include -#include -#include -#include +#include +#include +#include +#include +#include "base/memory/mem_ops.h" +#include "base/memory/unique_pointer.h" #include "placement/ecotope.h" #include "placement/gpu_placement.h" #include "placement/placement.h" @@ -26,7 +28,7 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "placement_gpu_test: FAIL: %s\n", message); + ::fprintf(stderr, "placement_gpu_test: FAIL: %s\n", message); ++failures; } @@ -55,32 +57,32 @@ void SortInstances(base::Vector& instances) { int main() { render::DeviceDesc desc; - const char* rhi = std::getenv("RX_RHI"); - desc.backend = (rhi && std::strcmp(rhi, "d3d12") == 0) ? render::Backend::kD3D12 + const char* rhi = ::getenv("RX_RHI"); + desc.backend = (rhi && ::strcmp(rhi, "d3d12") == 0) ? render::Backend::kD3D12 : render::Backend::kVulkan; desc.request_raytracing = false; - std::unique_ptr device = render::Device::CreateOffscreen(desc); + base::UniquePointer device = render::Device::CreateOffscreen(desc); if (!device) { - std::fprintf(stderr, "placement_gpu_test: CreateOffscreen returned null\n"); + ::fprintf(stderr, "placement_gpu_test: CreateOffscreen returned null\n"); return 1; } if (device->is_stub()) { - std::printf("placement_gpu_test: no %s driver, skipping (null backend)\n", + ::printf("placement_gpu_test: no %s driver, skipping (null backend)\n", render::BackendName(desc.backend)); return 0; } - std::printf("placement_gpu_test: device '%s'\n", device->caps().adapter_name.c_str()); + ::printf("placement_gpu_test: device '%s'\n", device->caps().adapter_name.c_str()); // A small world with smooth maps (smooth densities keep the CPU/GPU // bilinear paths away from threshold knife edges). WorldData world(0.0f, 0.0f, 512.0f, 128); u32 height = world.AddMap("height"); world.Generate(height, [](f32 x, f32 z) { - return 6.0f * std::sin(x * 0.011f) + 4.0f * std::cos(z * 0.017f); + return 6.0f * ::sin(x * 0.011f) + 4.0f * ::cos(z * 0.017f); }); u32 forest = world.AddMap("forest"); world.Generate(forest, [](f32 x, f32 z) { - return 0.5f + 0.5f * std::sin(x * 0.007f) * std::cos(z * 0.009f); + return 0.5f + 0.5f * ::sin(x * 0.007f) * ::cos(z * 0.009f); }); PlacementConfig config; @@ -116,7 +118,7 @@ int main() { GpuPlacement gpu; if (!gpu.Initialize(*device, system)) { - std::fprintf(stderr, "placement_gpu_test: Initialize failed\n"); + ::fprintf(stderr, "placement_gpu_test: Initialize failed\n"); return 1; } gpu.SyncWorldData(*device, world); @@ -124,9 +126,9 @@ int main() { const TileKey tiles[] = {{0, 0, 0}, {0, 1, 0}, {0, -1, 2}, {1, 3, 5}, {1, -2, -2}}; base::Vector gpu_a; - gpu.GenerateImmediate(*device, system, {tiles, 5}, gpu_a); + gpu.GenerateImmediate(*device, system, base::Span(tiles, 5), gpu_a); base::Vector gpu_b; - gpu.GenerateImmediate(*device, system, {tiles, 5}, gpu_b); + gpu.GenerateImmediate(*device, system, base::Span(tiles, 5), gpu_b); base::Vector cpu; for (const TileKey& tile : tiles) system.EmitTileCpu(tile, cpu); @@ -140,11 +142,11 @@ int main() { bool reruns_identical = gpu_a.size() == gpu_b.size(); for (u32 i = 0; reruns_identical && i < gpu_a.size(); ++i) { - reruns_identical = std::memcmp(&gpu_a[i], &gpu_b[i], sizeof(PlacedInstance)) == 0; + reruns_identical = base::MemCompare(&gpu_a[i], &gpu_b[i], sizeof(PlacedInstance)) == 0; } Check(reruns_identical, "GPU rerun reproduces instances bit for bit"); - std::printf("placement_gpu_test: gpu=%u cpu=%u instances\n", + ::printf("placement_gpu_test: gpu=%u cpu=%u instances\n", static_cast(gpu_a.size()), static_cast(cpu.size())); Check(gpu_a.size() == cpu.size(), "GPU matches CPU reference count"); if (gpu_a.size() == cpu.size()) { @@ -155,7 +157,7 @@ int main() { continue; } for (u32 e = 0; e < 16; ++e) { - if (std::fabs(gpu_a[i].transform.m[e] - cpu[i].transform.m[e]) > 2e-3f) { + if (::fabs(gpu_a[i].transform.m[e] - cpu[i].transform.m[e]) > 2e-3f) { ++mismatches; break; } @@ -163,11 +165,11 @@ int main() { } Check(mismatches == 0, "GPU instances match the CPU reference"); if (mismatches != 0) { - std::fprintf(stderr, "placement_gpu_test: %u mismatching instances\n", mismatches); + ::fprintf(stderr, "placement_gpu_test: %u mismatching instances\n", mismatches); } } gpu.Shutdown(*device); - if (failures == 0) std::printf("placement_gpu_test: all checks passed\n"); + if (failures == 0) ::printf("placement_gpu_test: all checks passed\n"); return failures; } diff --git a/test/placement_test.cc b/test/placement_test.cc index 26d9a17d..f3567bd8 100644 --- a/test/placement_test.cc +++ b/test/placement_test.cc @@ -4,10 +4,12 @@ // (same-footprint layers never collide), exclusion masks, local stability // under WorldData edits, and streaming ring bookkeeping. No GPU. -#include -#include -#include +#include +#include +#include +#include "base/memory/mem_ops.h" +#include "core/scalar.h" #include "placement/density_program.h" #include "placement/ecotope.h" #include "placement/placement.h" @@ -24,23 +26,23 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "placement_test: FAIL: %s\n", message); + ::fprintf(stderr, "placement_test: FAIL: %s\n", message); ++failures; } void Near(f32 actual, f32 expected, const char* message, f32 epsilon = 1e-3f) { - if (std::fabs(actual - expected) <= epsilon) return; - std::fprintf(stderr, "placement_test: FAIL: %s (got %.4f, expected %.4f)\n", message, + if (::fabs(actual - expected) <= epsilon) return; + ::fprintf(stderr, "placement_test: FAIL: %s (got %.4f, expected %.4f)\n", message, actual, expected); ++failures; } f32 ToroidalDistance(f32 ax, f32 ay, f32 bx, f32 by) { - f32 dx = std::fabs(ax - bx); - f32 dy = std::fabs(ay - by); - dx = std::min(dx, 1.0f - dx); - dy = std::min(dy, 1.0f - dy); - return std::sqrt(dx * dx + dy * dy); + f32 dx = ::fabs(ax - bx); + f32 dy = ::fabs(ay - by); + dx = rx::Min(dx, 1.0f - dx); + dy = rx::Min(dy, 1.0f - dy); + return ::sqrt(dx * dx + dy * dy); } // The two pattern-generator rules: even threshold coverage at every prefix, @@ -57,20 +59,20 @@ void TestPattern() { f32 min_dist = 10.0f; for (u32 i = 0; i < prefix; ++i) { for (u32 j = i + 1; j < prefix; ++j) { - min_dist = std::min( + min_dist = rx::Min( min_dist, ToroidalDistance(kPatternXY[i * 2], kPatternXY[i * 2 + 1], kPatternXY[j * 2], kPatternXY[j * 2 + 1])); } } - f32 ideal = std::sqrt(1.0f / static_cast(prefix)); + f32 ideal = ::sqrt(1.0f / static_cast(prefix)); Check(min_dist >= 0.5f * ideal, "pattern prefix keeps half the ideal spacing"); } // Even coverage: every 4x4 cell holds its fair share of the full pattern. u32 cells[16] = {}; for (u32 i = 0; i < kPatternPoints; ++i) { - u32 cx = std::min(static_cast(kPatternXY[i * 2] * 4.0f), 3u); - u32 cy = std::min(static_cast(kPatternXY[i * 2 + 1] * 4.0f), 3u); + u32 cx = rx::Min(static_cast(kPatternXY[i * 2] * 4.0f), 3u); + u32 cy = rx::Min(static_cast(kPatternXY[i * 2 + 1] * 4.0f), 3u); ++cells[cy * 4 + cx]; } for (u32 c = 0; c < 16; ++c) { @@ -168,7 +170,7 @@ void TestDeterminismAndSpacing() { WorldData world(0.0f, 0.0f, 512.0f, 256); u32 height = world.AddMap("height"); world.Generate(height, [](f32 x, f32 z) { - return 4.0f * std::sin(x * 0.02f) + 3.0f * std::cos(z * 0.015f); + return 4.0f * ::sin(x * 0.02f) + 3.0f * ::cos(z * 0.015f); }); u32 forest = world.AddMap("forest", 1.0f); u32 road = world.AddMap("road"); @@ -182,7 +184,7 @@ void TestDeterminismAndSpacing() { Check(a.size() == b.size() && !a.empty(), "regeneration reproduces the count"); bool identical = a.size() == b.size(); for (u32 i = 0; identical && i < a.size(); ++i) { - identical = std::memcmp(&a[i], &b[i], sizeof(PlacedInstance)) == 0; + identical = base::MemCompare(&a[i], &b[i], sizeof(PlacedInstance)) == 0; } Check(identical, "regeneration reproduces instances bit for bit"); @@ -195,7 +197,7 @@ void TestDeterminismAndSpacing() { for (u32 j = i + 1; j < a.size(); ++j) { f32 dx = a[i].transform.m[12] - a[j].transform.m[12]; f32 dz = a[i].transform.m[14] - a[j].transform.m[14]; - min_dist = std::min(min_dist, std::sqrt(dx * dx + dz * dz)); + min_dist = rx::Min(min_dist, ::sqrt(dx * dx + dz * dz)); } } Check(min_dist >= 0.6f * stack.footprint, "instances keep footprint spacing"); @@ -223,7 +225,7 @@ void TestDeterminismAndSpacing() { f32 z = instance.transform.m[14]; Near(instance.transform.m[13], world.Sample(height, x, z), "instance sits on ground", 0.01f); - f32 sx = std::sqrt(instance.transform.m[0] * instance.transform.m[0] + + f32 sx = ::sqrt(instance.transform.m[0] * instance.transform.m[0] + instance.transform.m[1] * instance.transform.m[1] + instance.transform.m[2] * instance.transform.m[2]); Check(sx >= 0.79f && sx <= 1.31f, "instance scale within layer range"); @@ -272,7 +274,7 @@ void TestLocalStability() { Check(near_after.size() < near_before.size(), "clearing removes trees locally"); bool far_identical = far_before.size() == far_after.size(); for (u32 i = 0; far_identical && i < far_before.size(); ++i) { - far_identical = std::memcmp(&far_before[i], &far_after[i], sizeof(PlacedInstance)) == 0; + far_identical = base::MemCompare(&far_before[i], &far_after[i], sizeof(PlacedInstance)) == 0; } Check(far_identical, "distant tile unaffected by the edit"); @@ -332,6 +334,6 @@ int main() { TestExclusionMask(); TestLocalStability(); TestStreamingRing(); - if (failures == 0) std::printf("placement_test: all checks passed\n"); + if (failures == 0) ::printf("placement_test: all checks passed\n"); return failures; } diff --git a/test/post_sampling_test.cc b/test/post_sampling_test.cc index 3ce2778a..55a01e0e 100644 --- a/test/post_sampling_test.cc +++ b/test/post_sampling_test.cc @@ -1,10 +1,10 @@ -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include "base/containers/vector.h" +#include "core/scalar.h" #include "render/post/depth_of_field.h" #include "render/post/motion_blur.h" #include "shaders/motion_tilemax_cs_hlsl.h" @@ -17,7 +17,7 @@ constexpr u32 kW = 256, kH = 128; f32 Half(u16 b) { if ((b & 0x7c00u) == 0x7c00u) return INFINITY; const int exponent = (b >> 10) & 31; - return std::ldexp(float((b & 1023) + (exponent ? 1024 : 0)), + return ::ldexp(float((b & 1023) + (exponent ? 1024 : 0)), exponent ? exponent - 25 : -24) * ((b & 0x8000) ? -1.f : 1.f); } struct TilePush { @@ -32,33 +32,33 @@ struct TilePush { int main() { DeviceDesc desc; - const char* backend = std::getenv("RX_RHI"); - desc.backend = backend && std::strcmp(backend, "d3d12") == 0 ? Backend::kD3D12 : Backend::kVulkan; + const char* backend = ::getenv("RX_RHI"); + desc.backend = backend && ::strcmp(backend, "d3d12") == 0 ? Backend::kD3D12 : Backend::kVulkan; desc.request_raytracing = false; desc.enable_validation = true; auto device = Device::CreateOffscreen(desc); if (!device || device->is_stub()) { - std::printf("post_sampling_test: SKIP, GPU unavailable\n"); + ::printf("post_sampling_test: SKIP, GPU unavailable\n"); return 77; } TransientPool pool(*device); - std::vector owned; - auto input = [&](Format format, Extent2D extent, const std::vector& data) { + base::Vector owned; + auto input = [&](Format format, Extent2D extent, const base::Vector& data) { GpuImage image = device->CreateImage2D(format, extent, kTextureUsageSampled | kTextureUsageTransferDst); GpuBuffer staging = device->CreateBufferWithData( - {reinterpret_cast(data.data()), data.size() * sizeof(f32)}, kBufferUsageTransferSrc); - if (!image || !staging) std::exit(1); + ByteSpan(reinterpret_cast(data.data()), data.size() * sizeof(f32)), kBufferUsageTransferSrc); + if (!image || !staging) ::exit(1); device->ImmediateSubmit([&](CommandList& cmd) { cmd.Barrier(Transition(image, ResourceState::kUndefined, ResourceState::kCopyDst)); BufferTextureCopy copy{.extent = extent}; - cmd.CopyBufferToTexture(staging, image, {©, 1}); + cmd.CopyBufferToTexture(staging, image, base::Span(©, 1)); cmd.Barrier(Transition(image, ResourceState::kCopyDst, ResourceState::kShaderReadCompute)); }); device->DestroyBuffer(staging); owned.push_back(image); return image; }; - std::vector colors(kW*kH*4); + base::Vector colors(kW*kH*4); for (u32 y = 0; y < kH; ++y) for (u32 x = 0; x < kW; ++x) { for (u32 c = 0; c < 3; ++c) colors[(y*kW+x)*4+c] = (x/4) % 2 ? 1 : 0; colors[(y*kW+x)*4+3] = 1; @@ -68,11 +68,11 @@ int main() { for (u32 i = 0; i < 3; ++i) { const u32 divisor = i == 1 ? 2 : 1; depths[i] = input(Format::kR32Float, {kW/divisor,kH/divisor}, - std::vector(kW*kH/(divisor*divisor), i == 2 ? .025f : .05f)); + base::Vector(kW*kH/(divisor*divisor), i == 2 ? .025f : .05f)); } int failures = 0; auto check = [&](bool ok, const char* name, double error) { - std::printf("%s: %s, error=%g\n", name, ok ? "PASS" : "FAIL", error); + ::printf("%s: %s, error=%g\n", name, ok ? "PASS" : "FAIL", error); if (!ok) ++failures; }; auto render = [&](auto& pass, GpuImage guide, const auto& frame) { @@ -85,26 +85,26 @@ int main() { graph.AddPass("readback", [out](RenderGraph::PassBuilder& b) { b.Read(out, ResourceUsage::kResolveSrc); }, [](PassContext&) {}); - if (!graph.Compile(*device, pool)) std::exit(1); + if (!graph.Compile(*device, pool)) ::exit(1); device->ImmediateSubmit([&](CommandList& cmd) { - PassContext ctx{.cmd = &cmd, .device = device.get(), .graph = &graph}; + PassContext ctx{.cmd = &cmd, .device = device.Get_UseOnlyIfYouKnowWhatYouareDoing(), .graph = &graph}; graph.Execute(ctx); }); - std::vector bits(kW*kH*4); + base::Vector bits(kW*kH*4); if (!device->ReadbackImage(graph.image(out), ResourceState::kResolveSrc, - bits.data(), bits.size()*sizeof(u16))) std::exit(1); - std::vector pixels(bits.size()); + bits.data(), bits.size()*sizeof(u16))) ::exit(1); + base::Vector pixels(bits.size()); for (u32 i = 0; i < bits.size(); ++i) pixels[i] = Half(bits[i]); return pixels; }; - auto difference = [](const std::vector& a, const std::vector& b, + auto difference = [](const base::Vector& a, const base::Vector& b, u32 left = 0, u32 right = kW, u32 top = 0, u32 bottom = kH) { double error = 0; for (u32 y = top; y < bottom; ++y) for (u32 x = left; x < right; ++x) for (u32 c = 0; c < 3; ++c) { const u32 i = (y*kW+x)*4+c; - if (!std::isfinite(a[i]) || !std::isfinite(b[i])) return double(INFINITY); - error = std::max(error, double(std::abs(a[i]-b[i]))); + if (!::isfinite(a[i]) || !::isfinite(b[i])) return double(INFINITY); + error = rx::Max(error, double(::abs(a[i]-b[i]))); } return error; }; @@ -151,10 +151,10 @@ int main() { MotionBlurPass::Frame frame; frame.shutter = 1; frame.samples = 32; - std::vector native; + base::Vector native; for (u32 divisor = 1; divisor <= 2; ++divisor) { const u32 w = kW/divisor, h = kH/divisor; - std::vector velocities(w*h*2); + base::Vector velocities(w*h*2); for (u32 y = 32/divisor; y < 96/divisor; ++y) for (u32 x = 192/divisor; x < w; ++x) velocities[(y*w+x)*2] = -16.f/kW; GpuImage motion = input(Format::kRG32Float, {w,h}, velocities); @@ -169,7 +169,7 @@ int main() { } } { - std::vector velocities(kW*kH*2); + base::Vector velocities(kW*kH*2); auto pixels = render(blur, input(Format::kRG32Float, {kW,kH}, velocities), frame); double error = difference(pixels, colors); check(error < .001, "Motion blur static frame", error); @@ -185,7 +185,7 @@ int main() { } blur.Destroy(*device); // The longer screen-space vector must win, even on a non-square image. - std::vector velocities(kW*kH*2); + base::Vector velocities(kW*kH*2); for (u32 p = 0; p < kW*kH; ++p) velocities[p*2+(p%2)] = p%2 ? -8.f/kH : -12.f/kW; GpuImage motion = input(Format::kRG32Float, {kW,kH}, velocities); GpuImage tiles = device->CreateImage2D(Format::kRG16Float, {kW/16,kH/16}, @@ -202,11 +202,11 @@ int main() { cmd.Push(TilePush{}); cmd.Dispatch2D({kW/16,kH/16}); }); - std::vector bits(kW/16*kH/16*2); + base::Vector bits(kW/16*kH/16*2); if (!device->ReadbackImage(tiles, ResourceState::kGeneral, bits.data(), bits.size()*sizeof(u16))) return 1; double error = 0; for (u32 p = 0; p < bits.size()/2; ++p) - error = std::max(error, double(std::abs(Half(bits[p*2])-12.f/kW) + std::abs(Half(bits[p*2+1])))); + error = rx::Max(error, double(::abs(Half(bits[p*2])-12.f/kW) + ::abs(Half(bits[p*2+1])))); check(error < .0001, "Motion blur aspect-correct maximum", error); device->WaitIdle(); device->DestroyPipeline(pipeline); diff --git a/test/procedural_asset_test.cc b/test/procedural_asset_test.cc index 1c72a674..e1a6c7ac 100644 --- a/test/procedural_asset_test.cc +++ b/test/procedural_asset_test.cc @@ -1,8 +1,8 @@ // Procedural asset generation: the pattern texture synthesis and the blockout // primitives a .rxscene may ask for. Pure CPU, no device. -#include -#include -#include +#include +#include +#include #include "asset/primitives.h" #include "asset/procedural_texture.h" @@ -18,7 +18,7 @@ int failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::fprintf(stderr, "procedural_asset_test: FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ::fprintf(stderr, "procedural_asset_test: FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++failures; \ } \ } while (0) @@ -43,9 +43,9 @@ void TestCheckerMask() { CHECK(low < 0.01f); CHECK(high > 0.99f); // Diagonal neighbours share a colour, orthogonal ones do not. - CHECK(std::abs(asset::SamplePattern(desc, 0.375f, 0.375f) - low) < 0.01f); + CHECK(::abs(asset::SamplePattern(desc, 0.375f, 0.375f) - low) < 0.01f); // Tiles: the mask at u and at u + 1 is the same field. - CHECK(std::abs(asset::SamplePattern(desc, 1.125f, 0.125f) - low) < 0.01f); + CHECK(::abs(asset::SamplePattern(desc, 1.125f, 0.125f) - low) < 0.01f); } // The two axes have to be independent, because the thing they describe is: a @@ -77,9 +77,9 @@ void TestPatternAxes() { desc.scale[1] = 12.0f; for (u32 i = 0; i < 8; ++i) { const f32 u = i / 8.0f, v = i / 5.0f; - CHECK(std::abs(asset::SamplePattern(desc, u + 1.0f, v) - + CHECK(::abs(asset::SamplePattern(desc, u + 1.0f, v) - asset::SamplePattern(desc, u, v)) < 1e-4f); - CHECK(std::abs(asset::SamplePattern(desc, u, v + 1.0f) - + CHECK(::abs(asset::SamplePattern(desc, u, v + 1.0f) - asset::SamplePattern(desc, u, v)) < 1e-4f); } } @@ -156,9 +156,9 @@ void TestNormalMap() { for (size_t i = 0; i < relief.data.size(); i += 4) { f32 n[3]; for (int c = 0; c < 3; ++c) n[c] = relief.data[i + c] / 255.0f * 2.0f - 1.0f; - CHECK(std::abs(std::sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]) - 1.0f) < 0.02f); + CHECK(::abs(::sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]) - 1.0f) < 0.02f); CHECK(n[2] > 0.0f); // a heightfield normal never points into the surface - tilted |= std::abs(n[0]) > 0.2f || std::abs(n[1]) > 0.2f; + tilted |= ::abs(n[0]) > 0.2f || ::abs(n[1]) > 0.2f; } CHECK(tilted); } @@ -197,21 +197,21 @@ void CheckMesh(const char* what, const asset::Mesh& mesh) { for (u32 index : lod.indices) CHECK(index < lod.vertices.size()); for (const asset::Vertex& v : lod.vertices) { - f32 len = std::sqrt(v.normal[0] * v.normal[0] + v.normal[1] * v.normal[1] + + f32 len = ::sqrt(v.normal[0] * v.normal[0] + v.normal[1] * v.normal[1] + v.normal[2] * v.normal[2]); - if (std::abs(len - 1.0f) > 1e-3f) { - std::fprintf(stderr, "procedural_asset_test: %s has a non-unit normal (%f)\n", what, len); + if (::abs(len - 1.0f) > 1e-3f) { + ::fprintf(stderr, "procedural_asset_test: %s has a non-unit normal (%f)\n", what, len); ++failures; break; } f32 dot = v.normal[0] * v.tangent[0] + v.normal[1] * v.tangent[1] + v.normal[2] * v.tangent[2]; - if (std::abs(dot) > 1e-3f) { - std::fprintf(stderr, "procedural_asset_test: %s tangent not perpendicular (%f)\n", what, dot); + if (::abs(dot) > 1e-3f) { + ::fprintf(stderr, "procedural_asset_test: %s tangent not perpendicular (%f)\n", what, dot); ++failures; break; } if (v.uv[0] < -1e-4f || v.uv[0] > 1.0001f || v.uv[1] < -1e-4f || v.uv[1] > 1.0001f) { - std::fprintf(stderr, "procedural_asset_test: %s uv off the unit square (%f %f)\n", what, + ::fprintf(stderr, "procedural_asset_test: %s uv off the unit square (%f %f)\n", what, v.uv[0], v.uv[1]); ++failures; break; @@ -231,9 +231,9 @@ void TestPrimitives() { asset::Mesh plane = asset::MakePlane(2.0f, 3.0f, id); for (const asset::Vertex& v : plane.lods[0].vertices) { CHECK(v.normal[1] > 0.999f); - CHECK(std::abs(std::abs(v.position[0]) - 2.0f) < 1e-5f); - CHECK(std::abs(v.position[1]) < 1e-5f); - CHECK(std::abs(std::abs(v.position[2]) - 3.0f) < 1e-5f); + CHECK(::abs(::abs(v.position[0]) - 2.0f) < 1e-5f); + CHECK(::abs(v.position[1]) < 1e-5f); + CHECK(::abs(::abs(v.position[2]) - 3.0f) < 1e-5f); } // A capsule is convex around its axis segment, so every normal points away @@ -243,8 +243,8 @@ void TestPrimitives() { for (const asset::Vertex& v : capsule.lods[0].vertices) { f32 axis_y = v.position[1] > 0.6f ? 0.6f : (v.position[1] < -0.6f ? -0.6f : v.position[1]); f32 d[3] = {v.position[0], v.position[1] - axis_y, v.position[2]}; - f32 len = std::sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); - CHECK(std::abs(len - 0.4f) < 1e-3f); // every point is one radius off the axis + f32 len = ::sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + CHECK(::abs(len - 0.4f) < 1e-3f); // every point is one radius off the axis CHECK((d[0] * v.normal[0] + d[1] * v.normal[1] + d[2] * v.normal[2]) / len > 0.999f); } @@ -252,12 +252,12 @@ void TestPrimitives() { // test above cannot express (the inner half faces the axis). asset::Mesh torus = asset::MakeTorus(1.0f, 0.25f, 20, 32, id); for (const asset::Vertex& v : torus.lods[0].vertices) { - f32 radial = std::sqrt(v.position[0] * v.position[0] + v.position[2] * v.position[2]); + f32 radial = ::sqrt(v.position[0] * v.position[0] + v.position[2] * v.position[2]); f32 scale = radial > 1e-6f ? 1.0f / radial : 0.0f; f32 d[3] = {v.position[0] - v.position[0] * scale, v.position[1], v.position[2] - v.position[2] * scale}; - f32 len = std::sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); - CHECK(std::abs(len - 0.25f) < 1e-3f); + f32 len = ::sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + CHECK(::abs(len - 0.25f) < 1e-3f); CHECK((d[0] * v.normal[0] + d[1] * v.normal[1] + d[2] * v.normal[2]) / len > 0.999f); } @@ -276,7 +276,7 @@ void TestPrimitives() { } f32 n[3] = {e1[1] * e2[2] - e1[2] * e2[1], e1[2] * e2[0] - e1[0] * e2[2], e1[0] * e2[1] - e1[1] * e2[0]}; - CHECK(std::sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]) > 1e-6f); + CHECK(::sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]) > 1e-6f); } } @@ -395,6 +395,6 @@ int main() { TestRoughnessMap(); TestPrimitives(); TestMultiSubmeshLods(); - if (failures == 0) std::printf("procedural_asset_test: PASS\n"); + if (failures == 0) ::printf("procedural_asset_test: PASS\n"); return failures == 0 ? 0 : 1; } diff --git a/test/procedural_grass_test.cc b/test/procedural_grass_test.cc index fb916878..7c9cff53 100644 --- a/test/procedural_grass_test.cc +++ b/test/procedural_grass_test.cc @@ -1,8 +1,8 @@ +#include "base/numeric_limits.h" #include "render/geometry/procedural_grass.h" -#include -#include -#include +#include +#include namespace { @@ -11,12 +11,12 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "procedural_grass_test: FAIL: %s\n", message); + ::fprintf(stderr, "procedural_grass_test: FAIL: %s\n", message); ++failures; } bool Near(rx::f32 a, rx::f32 b) { - return std::fabs(a - b) < 1e-5f; + return ::fabs(a - b) < 1e-5f; } void SetPoint(rx::f32 point[3], rx::f32 x, rx::f32 y, rx::f32 z) { @@ -64,16 +64,16 @@ int main() { Check(safe.max_blades == 1, "blade capacity has a nonzero minimum"); GrassGenerationSettings non_finite; - non_finite.candidate_spacing = std::numeric_limits::quiet_NaN(); - non_finite.stream_radius = std::numeric_limits::infinity(); - non_finite.fade_end = std::numeric_limits::quiet_NaN(); - non_finite.bend_recovery_time = std::numeric_limits::infinity(); + non_finite.candidate_spacing = NAN; + non_finite.stream_radius = INFINITY; + non_finite.fade_end = NAN; + non_finite.bend_recovery_time = INFINITY; const GrassGenerationSettings finite = SanitizeGrassSettings(non_finite); - Check(std::isfinite(finite.candidate_spacing), "non-finite spacing uses a default"); - Check(std::isfinite(finite.stream_radius), "non-finite radius uses a default"); + Check(::isfinite(finite.candidate_spacing), "non-finite spacing uses a default"); + Check(::isfinite(finite.stream_radius), "non-finite radius uses a default"); Check(finite.fade_end <= finite.stream_radius, "fade completes inside the stream radius"); - Check(std::isfinite(finite.bend_recovery_time), + Check(::isfinite(finite.bend_recovery_time), "non-finite bend recovery uses a default"); GrassGenerationSettings distant; @@ -103,14 +103,14 @@ int main() { SetPoint(triangle.p1, 1e9f, 0.0f, 0.0f); SetPoint(triangle.p2, 0.0f, 0.0f, 1e9f); Check( - GrassSurfaceCandidateCount(triangle, 0.08f) == std::numeric_limits::max(), + GrassSurfaceCandidateCount(triangle, 0.08f) == base::MinMax::max(), "oversized candidate counts clamp before integer conversion"); SetPoint(triangle.p1, 2.0f, 0.0f, 0.0f); SetPoint(triangle.p2, 4.0f, 0.0f, 0.0f); Check(GrassSurfaceCandidateCount(triangle, 0.5f) == 0, "degenerate triangles are rejected"); - triangle.p2[0] = std::numeric_limits::infinity(); + triangle.p2[0] = INFINITY; Check(GrassSurfaceCandidateCount(triangle, 0.5f) == 0, "non-finite triangles are rejected"); @@ -125,6 +125,6 @@ int main() { if (failures != 0) return 1; - std::printf("procedural_grass_test: PASS\n"); + ::printf("procedural_grass_test: PASS\n"); return 0; } diff --git a/test/raytracing_test.cc b/test/raytracing_test.cc index b700f2f8..d1fe3969 100644 --- a/test/raytracing_test.cc +++ b/test/raytracing_test.cc @@ -1,9 +1,10 @@ -#include -#include -#include -#include +#include +#include +#include +#include #include "asset/mesh.h" +#include "base/memory/mem_ops.h" #include "render/gi/raytracing.h" #include "render/gi/skinned_rt.h" #include "render/pipeline/material_system.h" @@ -15,19 +16,19 @@ using namespace rx::render; int main() { DeviceDesc desc; - const char* backend = std::getenv("RX_RHI"); - desc.backend = backend && std::strcmp(backend, "d3d12") == 0 + const char* backend = ::getenv("RX_RHI"); + desc.backend = backend && ::strcmp(backend, "d3d12") == 0 ? Backend::kD3D12 : Backend::kVulkan; desc.request_raytracing = true; desc.enable_validation = true; auto device = Device::CreateOffscreen(desc); if (!device || device->is_stub() || !device->caps().ray_query) { - std::printf("raytracing_test: SKIP, ray queries unavailable\n"); + ::printf("raytracing_test: SKIP, ray queries unavailable\n"); return 77; } int failures = 0; auto check = [&](bool ok, const char* message) { - if (!ok) { std::printf("FAIL: %s\n", message); ++failures; } + if (!ok) { ::printf("FAIL: %s\n", message); ++failures; } }; auto rt = RayTracingContext::Create(*device); if (!rt) return 1; @@ -48,7 +49,7 @@ int main() { }); device->InvalidateBuffer(readback, 0, sizeof(f32)); f32 distance; - std::memcpy(&distance, readback.mapped, sizeof(distance)); + base::MemCopy(&distance, readback.mapped, sizeof(distance)); return distance; }; check(trace(0) == -1.0f, "unbuilt slot must miss through fallback"); @@ -59,10 +60,10 @@ int main() { const u32 indices[3] = {0, 1, 2}; GpuMesh mesh; mesh.vertices = device->CreateBufferWithData( - {reinterpret_cast(vertices), sizeof(vertices)}, + ByteSpan(reinterpret_cast(vertices), sizeof(vertices)), kBufferUsageAccelBuildInput | kBufferUsageDeviceAddress | kBufferUsageStorage); mesh.indices = device->CreateBufferWithData( - {reinterpret_cast(indices), sizeof(indices)}, + ByteSpan(reinterpret_cast(indices), sizeof(indices)), kBufferUsageAccelBuildInput | kBufferUsageDeviceAddress | kBufferUsageStorage); mesh.vertex_count = mesh.index_count = 3; mesh.submeshes.push_back({.index_count = 3}); @@ -74,12 +75,12 @@ int main() { device->ImmediateSubmit([&](CommandList& cmd) { rt->BuildTlas(cmd, 0, frame, instances); }); }; build(0); - check(std::abs(trace(0) - 1.0f) < 1e-5f, "compacted triangle must hit at distance 1"); + check(::abs(trace(0) - 1.0f) < 1e-5f, "compacted triangle must hit at distance 1"); check(rt->ReserveTlas(0, 65), "grow built TLAS"); check(!rt->TlasValid(0), "replacement remains invalid until built"); check(trace(0) == -1.0f, "replacement must use fallback before build"); build(1); - check(std::abs(trace(0) - 1.0f) < 1e-5f, "grown TLAS must hit after build"); + check(::abs(trace(0) - 1.0f) < 1e-5f, "grown TLAS must hit after build"); rt->RemoveBlasDeferred(1); check(trace(0) == -1.0f, "retired BLAS must invalidate TLAS"); build(2); @@ -100,7 +101,7 @@ int main() { cmd.MemoryBarrier(BarrierScope::kAccelBuildWrite, BarrierScope::kAccelBuildWrite); rt->BuildTlas(cmd, 0, 4, instances); }); - check(std::abs(trace(0) - 1.0f) < 1e-5f, "built skinned BLAS must hit"); + check(::abs(trace(0) - 1.0f) < 1e-5f, "built skinned BLAS must hit"); check(rt->ReserveSkinnedBlas(3, geometry), "reserve refit target"); instances[0].mesh_key = 3; device->ImmediateSubmit([&](CommandList& cmd) { @@ -108,18 +109,18 @@ int main() { cmd.MemoryBarrier(BarrierScope::kAccelBuildWrite, BarrierScope::kAccelBuildWrite); rt->BuildTlas(cmd, 0, 5, instances); }); - check(std::abs(trace(0) - 1.0f) < 1e-5f, "refitted BLAS must hit"); + check(::abs(trace(0) - 1.0f) < 1e-5f, "refitted BLAS must hit"); rt->RemoveSkinnedBlasDeferred(2); rt->RemoveSkinnedBlasDeferred(3); auto bindless = BindlessRegistry::Create(*device); - auto materials = MaterialSystem::Create(*device, bindless.get()); + auto materials = MaterialSystem::Create(*device, bindless.Get_UseOnlyIfYouKnowWhatYouareDoing()); SkinnedRayTracing skin; if (!bindless || !materials || !skin.Initialize(*device)) return 1; asset::SkinnedVertexExtra weights[3]{}; for (auto& weight : weights) weight.bone_weights[0] = 255; mesh.skinning = device->CreateBufferWithData( - {reinterpret_cast(weights), sizeof(weights)}, kBufferUsageStorage); + ByteSpan(reinterpret_cast(weights), sizeof(weights)), kBufferUsageStorage); mesh.skinned = true; GpuBuffer bones = device->CreateBuffer(sizeof(Mat4), kBufferUsageStorage, true); if (!mesh.skinning || !bones.mapped) return 1; @@ -130,7 +131,7 @@ int main() { requests.push_back({.handle = actor, .mesh_key = 7}); auto pose = [&](f32 z, u32 frame) { Mat4 bone = MakeTranslation(Vec3{0, 0, z}); - std::memcpy(bones.mapped, &bone, sizeof(bone)); + base::MemCopy(bones.mapped, &bone, sizeof(bone)); device->FlushBuffer(bones, 0, sizeof(bone)); check(skin.Prepare(*device, *bindless, *materials, *rt, meshes, requests) == 1, "prepare posed actor"); @@ -140,7 +141,7 @@ int main() { skin.Record(cmd, *rt, bones); rt->BuildTlas(cmd, 0, frame, instances); }); - check(std::abs(trace(0) - (z + 1.0f)) < 1e-5f, "ray must hit the current skinned pose"); + check(::abs(trace(0) - (z + 1.0f)) < 1e-5f, "ray must hit the current skinned pose"); }; pose(1, 6); check(skin.previous_custom_index(actor) == SkinnedRayTracing::kInvalidIndex, @@ -154,31 +155,31 @@ int main() { skin.previous_custom_index(actor) != skin.custom_index(actor), "second skin pose must expose the previous buffer for motion"); base::Vector retired; - skin.InvalidateMesh(*device, rt.get(), 7, retired); + skin.InvalidateMesh(*device, rt.Get_UseOnlyIfYouKnowWhatYouareDoing(), 7, retired); check(!skin.active(actor) && !rt->TlasValid(0), "mesh replacement must retire posed geometry"); device->WaitIdle(); for (u32 index : retired) bindless->ReleaseMesh(index); retired.clear(); pose(3, 8); - skin.Release(*device, rt.get(), actor, retired); - skin.Release(*device, rt.get(), actor, retired); + skin.Release(*device, rt.Get_UseOnlyIfYouKnowWhatYouareDoing(), actor, retired); + skin.Release(*device, rt.Get_UseOnlyIfYouKnowWhatYouareDoing(), actor, retired); const u32 reused = skin.Acquire(); check(reused == actor && skin.Acquire() != reused, "duplicate release must not alias live actors"); device->WaitIdle(); for (u32 index : retired) bindless->ReleaseMesh(index); skin.Destroy(*device); - materials.reset(); - bindless.reset(); + materials.Reset(); + bindless.Reset(); device->DestroyBuffer(mesh.skinning); device->DestroyBuffer(bones); device->WaitIdle(); - rt.reset(); + rt.Reset(); device->DestroyBuffer(mesh.vertices); device->DestroyBuffer(mesh.indices); device->DestroyBuffer(result); device->DestroyBuffer(readback); device->DestroyPipeline(pipeline); - std::printf("raytracing_test: %d failures\n", failures); + ::printf("raytracing_test: %d failures\n", failures); return failures ? 1 : 0; } diff --git a/test/rcgi_history_test.cc b/test/rcgi_history_test.cc index e37532c4..57578eae 100644 --- a/test/rcgi_history_test.cc +++ b/test/rcgi_history_test.cc @@ -1,6 +1,6 @@ #include "render/gi/rcgi_history.h" -#include +#include using namespace rx; using namespace rx::render; @@ -11,7 +11,7 @@ int failures = 0; #define CHECK(condition) \ do { \ if (!(condition)) { \ - std::printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #condition); \ + ::printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #condition); \ ++failures; \ } \ } while (0) @@ -68,6 +68,6 @@ int main() { dormant_b.interior_ambient = {0, 1, 0}; CHECK(!ShouldInvalidateRcgiHistory(dormant_a, dormant_b)); - if (failures == 0) std::printf("rcgi_history_test: all checks passed\n"); + if (failures == 0) ::printf("rcgi_history_test: all checks passed\n"); return failures == 0 ? 0 : 1; } diff --git a/test/rcgi_interior_test.cc b/test/rcgi_interior_test.cc index 8efaf7c6..645b0aac 100644 --- a/test/rcgi_interior_test.cc +++ b/test/rcgi_interior_test.cc @@ -5,7 +5,8 @@ // quantization the shaders rely on. #include "render/gi/rcgi_interior.h" -#include +#include +#include #include "core/math.h" @@ -18,12 +19,12 @@ int g_failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); \ + ::printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); \ ++g_failures; \ } \ } while (0) -bool Near(f32 a, f32 b, f32 eps) { return std::fabs(a - b) <= eps; } +bool Near(f32 a, f32 b, f32 eps) { return ::fabs(a - b) <= eps; } } // namespace @@ -85,6 +86,6 @@ int main() { CHECK(Near(only_y.x, 0.0f, q) && Near(only_y.y, 0.4f, q) && Near(only_y.z, 0.0f, q)); } - if (g_failures == 0) std::printf("rcgi_interior_test: all checks passed\n"); + if (g_failures == 0) ::printf("rcgi_interior_test: all checks passed\n"); return g_failures == 0 ? 0 : 1; } diff --git a/test/recon_atrous_test.cc b/test/recon_atrous_test.cc index 35c215d1..95129b3b 100644 --- a/test/recon_atrous_test.cc +++ b/test/recon_atrous_test.cc @@ -1,9 +1,9 @@ -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include "base/containers/vector.h" #include "render/rhi/device.h" #include "shaders/recon_atrous_cs_hlsl.h" @@ -21,20 +21,20 @@ struct Push { }; f32 Half(u16 b) { const int exponent = (b >> 10) & 31; - return std::ldexp(float((b & 1023) + (exponent ? 1024 : 0)), + return ::ldexp(float((b & 1023) + (exponent ? 1024 : 0)), exponent ? exponent - 25 : -24) * ((b & 0x8000) ? -1.f : 1.f); } } int main() { DeviceDesc desc; - const char* backend = std::getenv("RX_RHI"); - desc.backend = backend && std::strcmp(backend, "d3d12") == 0 ? Backend::kD3D12 : Backend::kVulkan; + const char* backend = ::getenv("RX_RHI"); + desc.backend = backend && ::strcmp(backend, "d3d12") == 0 ? Backend::kD3D12 : Backend::kVulkan; desc.request_raytracing = false; desc.enable_validation = true; auto device = Device::CreateOffscreen(desc); if (!device || device->is_stub()) { - std::printf("recon_atrous_test: SKIP, GPU unavailable\n"); + ::printf("recon_atrous_test: SKIP, GPU unavailable\n"); return 77; } PipelineHandle pipeline = device->CreateComputePipeline({ @@ -44,16 +44,16 @@ int main() { {4, BindingType::kSampledImage}}}}, .push_constant_size = sizeof(Push)}); if (!pipeline) return 1; - std::vector owned; - auto input = [&](Format format, const std::vector& data) { + base::Vector owned; + auto input = [&](Format format, const base::Vector& data) { GpuImage image = device->CreateImage2D(format, {kSize, kSize}, kTextureUsageSampled | kTextureUsageTransferDst); GpuBuffer staging = device->CreateBufferWithData( - {reinterpret_cast(data.data()), data.size() * sizeof(f32)}, kBufferUsageTransferSrc); - if (!image || !staging) std::exit(1); + ByteSpan(reinterpret_cast(data.data()), data.size() * sizeof(f32)), kBufferUsageTransferSrc); + if (!image || !staging) ::exit(1); device->ImmediateSubmit([&](CommandList& cmd) { cmd.Barrier(Transition(image, ResourceState::kUndefined, ResourceState::kCopyDst)); BufferTextureCopy copy{.extent = {kSize, kSize}}; - cmd.CopyBufferToTexture(staging, image, {©, 1}); + cmd.CopyBufferToTexture(staging, image, base::Span(©, 1)); cmd.Barrier(Transition(image, ResourceState::kCopyDst, ResourceState::kShaderReadCompute)); }); device->DestroyBuffer(staging); @@ -63,7 +63,7 @@ int main() { int failures = 0; // Flat noisy reflections, a noiseless sharp reflection, and a depth boundary. for (u32 mode = 0; mode < 3; ++mode) { - std::vector colors(kPixels * 4), normals(kPixels * 4), depths(kPixels, 1); + base::Vector colors(kPixels * 4), normals(kPixels * 4), depths(kPixels, 1); u32 rng = 7; for (u32 p = 0; p < kPixels; ++p) { rng = rng * 1664525u + 1013904223u; @@ -98,7 +98,7 @@ int main() { }); current = last = output; } - std::vector pixels(kPixels * 4); + base::Vector pixels(kPixels * 4); if (!device->ReadbackImage(last, ResourceState::kShaderReadCompute, pixels.data(), pixels.size() * sizeof(u16))) return 1; double mse = 0, mean = 0; bool finite = true; @@ -109,9 +109,9 @@ int main() { mse += (value - expected) * (value - expected) / kPixels; mean += value / kPixels; } - std::printf("specular mode=%u RMS=%g mean=%g\n", mode, std::sqrt(mse), mean); - const bool ok = finite && (mode == 0 ? std::sqrt(mse) < .14 && std::abs(mean - 1) < .03 : std::sqrt(mse) < .005); - if (!ok) { std::printf("FAIL: reflection noise reduction / edge preservation\n"); ++failures; } + ::printf("specular mode=%u RMS=%g mean=%g\n", mode, ::sqrt(mse), mean); + const bool ok = finite && (mode == 0 ? ::sqrt(mse) < .14 && ::abs(mean - 1) < .03 : ::sqrt(mse) < .005); + if (!ok) { ::printf("FAIL: reflection noise reduction / edge preservation\n"); ++failures; } } device->WaitIdle(); for (auto& image : owned) device->DestroyImage(image); diff --git a/test/recon_temporal_test.cc b/test/recon_temporal_test.cc index 99dfcc17..bc8a6f99 100644 --- a/test/recon_temporal_test.cc +++ b/test/recon_temporal_test.cc @@ -1,10 +1,9 @@ -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include "base/containers/vector.h" #include "core/math.h" #include "render/rhi/device.h" #include "shaders/recon_temporal_cs_hlsl.h" @@ -29,19 +28,19 @@ struct Push { int main() { DeviceDesc desc; - const char* backend = std::getenv("RX_RHI"); - desc.backend = backend && std::strcmp(backend, "d3d12") == 0 + const char* backend = ::getenv("RX_RHI"); + desc.backend = backend && ::strcmp(backend, "d3d12") == 0 ? Backend::kD3D12 : Backend::kVulkan; desc.request_raytracing = false; desc.enable_validation = true; auto device = Device::CreateOffscreen(desc); if (!device || device->is_stub()) { - std::printf("recon_temporal_test: SKIP, GPU unavailable\n"); + ::printf("recon_temporal_test: SKIP, GPU unavailable\n"); return 77; } int failures = 0; auto check = [&](bool ok, const char* message) { - if (!ok) { std::printf("FAIL: %s\n", message); ++failures; } + if (!ok) { ::printf("FAIL: %s\n", message); ++failures; } }; ComputePipelineDesc pipeline_desc; pipeline_desc.shader = RX_SHADER(k_recon_temporal_cs_hlsl); @@ -53,17 +52,17 @@ int main() { pipeline_desc.debug_name = "recon_temporal_test"; PipelineHandle pipeline = device->CreateComputePipeline(pipeline_desc); if (!pipeline) return 1; - std::vector owned; + base::Vector owned; auto input = [&](Format format, const void* data, u32 size) { GpuImage image = device->CreateImage2D( format, {3, 1}, kTextureUsageSampled | kTextureUsageTransferDst); GpuBuffer staging = device->CreateBufferWithData( - {static_cast(data), size}, kBufferUsageTransferSrc); - if (!image || !staging) std::exit(1); + ByteSpan(static_cast(data), size), kBufferUsageTransferSrc); + if (!image || !staging) ::exit(1); device->ImmediateSubmit([&](CommandList& cmd) { cmd.Barrier(Transition(image, ResourceState::kUndefined, ResourceState::kCopyDst)); const BufferTextureCopy copy{.extent = {3, 1}}; - cmd.CopyBufferToTexture(staging, image, {©, 1}); + cmd.CopyBufferToTexture(staging, image, base::Span(©, 1)); cmd.Barrier(Transition(image, ResourceState::kCopyDst, ResourceState::kShaderReadCompute)); }); device->DestroyBuffer(staging); @@ -76,7 +75,7 @@ int main() { const u32 material[3] = {}; const f32 motion[6] = {}; f32 history[12] = {}; - for (f32& v : history) v = std::numeric_limits::infinity(); + for (f32& v : history) v = INFINITY; GpuImage curr = input(Format::kRGBA32Float, noisy, sizeof(noisy)); GpuImage nr = input(Format::kRGBA32Float, normals, sizeof(normals)); GpuImage vz = input(Format::kR32Float, depth, sizeof(depth)); @@ -120,8 +119,8 @@ int main() { f32 values[12]{}; check(device->ReadbackImage(moments, ResourceState::kGeneral, values, sizeof(values)), "read back temporal moments"); - for (f32 value : values) check(std::isfinite(value), "HDR moments must remain finite"); - check(std::abs(values[5] - 1000000.0f) < 1.0f, "squared HDR luminance must not overflow"); + for (f32 value : values) check(::isfinite(value), "HDR moments must remain finite"); + check(::abs(values[5] - 1000000.0f) < 1.0f, "squared HDR luminance must not overflow"); check(values[7] == 1.0f, "reset or invalid history must seed one frame"); u16 half[12]{}; check(device->ReadbackImage(accum, ResourceState::kGeneral, half, sizeof(half)), @@ -133,6 +132,6 @@ int main() { device->DestroyImage(accum); device->DestroyImage(moments); device->DestroyPipeline(pipeline); - std::printf("recon_temporal_test: %d failures\n", failures); + ::printf("recon_temporal_test: %d failures\n", failures); return failures ? 1 : 0; } diff --git a/test/render2d_test.cc b/test/render2d_test.cc index 3d28e58a..5dcb04dc 100644 --- a/test/render2d_test.cc +++ b/test/render2d_test.cc @@ -3,9 +3,9 @@ #include "render2d/sprite_renderer.h" #include "render2d/tile_map.h" -#include -#include -#include +#include +#include +#include namespace { @@ -15,13 +15,13 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "render2d_test: FAIL: %s\n", message); + ::fprintf(stderr, "render2d_test: FAIL: %s\n", message); ++failures; } void Near(float actual, float expected, const char* message, float epsilon = 1e-4f) { - if (std::abs(actual - expected) <= epsilon) return; - std::fprintf(stderr, "render2d_test: FAIL: %s (got %.6f, expected %.6f)\n", message, actual, + if (::abs(actual - expected) <= epsilon) return; + ::fprintf(stderr, "render2d_test: FAIL: %s (got %.6f, expected %.6f)\n", message, actual, expected); ++failures; } @@ -47,11 +47,11 @@ void TestCameraRoundTrip() { void TestCameraRejectsDegenerateInputs() { Camera2D camera; camera.SetViewport(0, 0); - camera.SetZoom(std::numeric_limits::infinity()); + camera.SetZoom(INFINITY); const rx::Mat4 view_proj = camera.ViewProj(); Check(camera.viewport().x == 1.0f && camera.viewport().y == 1.0f, "zero viewport is kept invertible"); - Check(std::isfinite(view_proj.m[0]) && std::isfinite(view_proj.m[5]), + Check(::isfinite(view_proj.m[0]) && ::isfinite(view_proj.m[5]), "degenerate camera inputs produce a finite matrix"); } @@ -105,7 +105,7 @@ void TestTileMapQueries() { Check(!map.IsSolidWorld({100.0f, 100.0f}), "invalid tile size is never solid"); Check(map.WorldBounds().w == 0.0f, "invalid tile size has empty bounds"); map.tile_size = 1.0f; - Check(!map.IsSolidWorld({std::numeric_limits::infinity(), 0.0f}), + Check(!map.IsSolidWorld({INFINITY, 0.0f}), "non-finite world position is never solid"); } @@ -125,9 +125,9 @@ int main() { TestRendererStartsInert(); if (failures != 0) { - std::fprintf(stderr, "render2d_test: %d failure(s)\n", failures); + ::fprintf(stderr, "render2d_test: %d failure(s)\n", failures); return 1; } - std::puts("render2d_test: all tests passed"); + ::puts("render2d_test: all tests passed"); return 0; } diff --git a/test/rt_failure_test.cc b/test/rt_failure_test.cc index 46bad8a4..66113805 100644 --- a/test/rt_failure_test.cc +++ b/test/rt_failure_test.cc @@ -1,9 +1,13 @@ -#include -#include -#include -#include -#include +#include +#include +#include +#include "base/containers/span.h" +#include "base/containers/unordered_set.h" +#include "base/functional/function.h" +#include "base/memory/mem_ops.h" +#include "base/memory/unique_pointer.h" +#include "base/strings/xstring.h" #include "render/gi/path_tracer.h" #include "render/gi/raytracing.h" #include "render/gi/recon_path_tracer.h" @@ -20,22 +24,22 @@ namespace { int g_failures = 0; #define CHECK(condition) do { if (!(condition)) { \ - std::printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #condition); ++g_failures; \ + ::printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #condition); ++g_failures; \ } } while (0) class TestCommandList final : public CommandList { public: void BindPipeline(PipelineHandle) override {} void BindSet(u32, BindingSetHandle) override {} - void BindTransient(u32, std::span) override {} - std::string pass; + void BindTransient(u32, base::Span) override {} + base::String pass; bool expect_reset = false; u32 temporal_dispatches = 0; void PushConstants(const void* data, u32 size, u32) override { if (pass != "recon_temporal" && pass != "recon_spec_temporal") return; CHECK(size >= 108); f32 reset; - std::memcpy(&reset, static_cast(data) + 104, sizeof(reset)); + base::MemCopy(&reset, static_cast(data) + 104, sizeof(reset)); CHECK((reset != 0.0f) == expect_reset); ++temporal_dispatches; } @@ -50,12 +54,12 @@ class TestCommandList final : public CommandList { void DrawIndexed(u32, u32, u32, i32, u32) override {} void DrawIndexedIndirect(const GpuBuffer&, rx::u64, u32, u32) override {} void DrawMeshTasks(u32, u32, u32) override {} - void TextureBarriers(std::span barriers) override { + void TextureBarriers(base::Span barriers) override { for (const auto& b : barriers) CHECK(b.texture); } void MemoryBarrier(BarrierScope, BarrierScope) override {} void CopyBufferToTexture(const GpuBuffer&, const GpuImage&, - std::span) override {} + base::Span) override {} void CopyTextureToBuffer(const GpuImage&, const GpuBuffer&, const BufferTextureCopy&) override {} void CopyBuffer(const GpuBuffer&, rx::u64, const GpuBuffer&, rx::u64, rx::u64) override {} @@ -89,31 +93,31 @@ class TestDevice final : public Device { u32 fail_at = 0; u32 attempts = 0; rx::u64 next_id = 1; - std::unordered_set live; + base::UnorderedSet live; rx::u64 Allocate() { if (++attempts == fail_at) return 0; const rx::u64 id = next_id++; live.insert(id); return id; } - void Free(rx::u64 id) { if (id) CHECK(live.erase(id) == 1); } + void Free(rx::u64 id) { if (id) CHECK(live.erase(id)); } void WaitIdle() override {} bool RecreateSurface(Window&) override { return false; } void DestroySurface() override {} - std::unique_ptr CreateSwapchain(u32, u32, bool, bool) override { return nullptr; } + base::UniquePointer CreateSwapchain(u32, u32, bool, bool) override { return nullptr; } MemoryBudget memory_budget() const override { return {}; } GpuBuffer CreateBuffer(rx::u64 size, BufferUsageFlags, bool host) override { const rx::u64 id = Allocate(); if (!id) return {}; - return {.handle = {id}, .size = size, .mapped = host ? std::calloc(1, size) : nullptr, + return {.handle = {id}, .size = size, .mapped = host ? ::calloc(1, size) : nullptr, .address = id * 4096}; } GpuBuffer CreateBufferWithData(ByteSpan, BufferUsageFlags) override { return {}; } void DestroyBuffer(GpuBuffer& buffer) override { Free(buffer.handle.value); - std::free(buffer.mapped); + ::free(buffer.mapped); buffer = {}; } GpuImage CreateImage2D(Format format, Extent2D extent, TextureUsageFlags, u32, u32) override { @@ -135,7 +139,7 @@ class TestDevice final : public Device { void DestroyBindingLayout(BindingLayoutHandle) override {} BindingSetHandle CreateBindingSet(BindingLayoutHandle, u32) override { return {}; } void DestroyBindingSet(BindingSetHandle) override {} - void UpdateBindingSet(BindingSetHandle, std::span) override {} + void UpdateBindingSet(BindingSetHandle, base::Span) override {} AccelSizes GetBlasSizes(const BlasBuildDesc&) override { return {1024, 1024, 512}; } AccelSizes GetTlasSizes(u32) override { return {1024, 1024}; } @@ -147,7 +151,7 @@ class TestDevice final : public Device { void DestroyTimestampPool(TimestampPoolHandle) override {} bool GetTimestamps(TimestampPoolHandle, u32, u32, rx::u64*) override { return false; } - void ImmediateSubmit(const std::function& record) override { + void ImmediateSubmit(const base::Function& record) override { TestCommandList cmd; record(cmd); } @@ -311,7 +315,7 @@ int main() { CHECK(cmd.instances == 1); rt->RemoveSkinnedBlasDeferred(42); CHECK(!rt->TlasValid(0)); - rt.reset(); + rt.Reset(); CHECK(device.live.empty()); } { @@ -376,9 +380,9 @@ int main() { tracer.Resize(device, {19, 11}); run(true); tracer.Destroy(device); - rt.reset(); + rt.Reset(); CHECK(device.live.empty()); } - std::printf("rt_failure_test: %d failures\n", g_failures); + ::printf("rt_failure_test: %d failures\n", g_failures); return g_failures ? 1 : 0; } diff --git a/test/rt_instance_cull_test.cc b/test/rt_instance_cull_test.cc index ef887111..42bd7faa 100644 --- a/test/rt_instance_cull_test.cc +++ b/test/rt_instance_cull_test.cc @@ -3,7 +3,7 @@ // accept-all fallback and generation invalidation. #include "render/gi/rt_instance_cull.h" -#include +#include #include "core/math.h" @@ -16,7 +16,7 @@ int g_failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); \ + ::printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); \ ++g_failures; \ } \ } while (0) @@ -62,7 +62,7 @@ int main() { CHECK(c.DrawVisible(At(Vec3{0, 0, 5000}), Vec3{0, 0, 0}, 0.001f)); base::Vector xf; for (int i = 0; i < 10; ++i) xf.push_back(At(Vec3{0, 0, 5000.0f + i})); - const auto& vis = c.UpdateGroup(0, 1, 0, {xf.data(), xf.size()}, Vec3{0, 0, 0}, 0.01f); + const auto& vis = c.UpdateGroup(0, 1, 0, base::Span(xf.data(), xf.size()), Vec3{0, 0, 0}, 0.01f); CHECK(CountVisible(vis) == xf.size()); } @@ -79,7 +79,7 @@ int main() { c.BeginFrame(eye); // First frame is accept-all (group just appeared): no holes. - const auto& first = c.UpdateGroup(0, 1, 0, {xf.data(), xf.size()}, Vec3{0, 0, 0}, mesh_radius); + const auto& first = c.UpdateGroup(0, 1, 0, base::Span(xf.data(), xf.size()), Vec3{0, 0, 0}, mesh_radius); CHECK(CountVisible(first) == kN); // Per-frame slice is bounded (never a full re-test of a huge group). @@ -91,7 +91,7 @@ int main() { u32 visible = kN; for (u32 f = 0; f < RtInstanceCuller::kSweepFrames + 2; ++f) { c.BeginFrame(eye); - const auto& v = c.UpdateGroup(0, 1, 0, {xf.data(), xf.size()}, Vec3{0, 0, 0}, mesh_radius); + const auto& v = c.UpdateGroup(0, 1, 0, base::Span(xf.data(), xf.size()), Vec3{0, 0, 0}, mesh_radius); visible = CountVisible(v); } CHECK(visible == 0); @@ -109,11 +109,11 @@ int main() { // Converge to fully culled first. for (u32 f = 0; f < RtInstanceCuller::kSweepFrames + 2; ++f) { c.BeginFrame(eye); - c.UpdateGroup(0, 1, 0, {xf.data(), xf.size()}, Vec3{0, 0, 0}, 0.05f); + c.UpdateGroup(0, 1, 0, base::Span(xf.data(), xf.size()), Vec3{0, 0, 0}, 0.05f); } // Now teleport far away in a single frame -> accept-all restored. c.BeginFrame(Vec3{10000, 0, 0}); - const auto& v = c.UpdateGroup(0, 1, 0, {xf.data(), xf.size()}, Vec3{0, 0, 0}, 0.05f); + const auto& v = c.UpdateGroup(0, 1, 0, base::Span(xf.data(), xf.size()), Vec3{0, 0, 0}, 0.05f); CHECK(CountVisible(v) == kN); } @@ -126,13 +126,13 @@ int main() { const Vec3 eye{0, 0, 0}; for (u32 f = 0; f < RtInstanceCuller::kSweepFrames + 2; ++f) { c.BeginFrame(eye); - c.UpdateGroup(3, 1, 0, {a.data(), a.size()}, Vec3{0, 0, 0}, 0.05f); + c.UpdateGroup(3, 1, 0, base::Span(a.data(), a.size()), Vec3{0, 0, 0}, 0.05f); } // Slot 3 reused for a new group (generation bumped): starts accept-all. base::Vector b; for (u32 i = 0; i < 100; ++i) b.push_back(At(Vec3{0, 0, 500.0f + f32(i)})); c.BeginFrame(eye); - const auto& v = c.UpdateGroup(3, 2, 0, {b.data(), b.size()}, Vec3{0, 0, 0}, 0.05f); + const auto& v = c.UpdateGroup(3, 2, 0, base::Span(b.data(), b.size()), Vec3{0, 0, 0}, 0.05f); CHECK(CountVisible(v) == b.size()); } @@ -147,7 +147,7 @@ int main() { const Vec3 eye{0, 0, 0}; for (u32 f = 0; f < RtInstanceCuller::kSweepFrames + 2; ++f) { c.BeginFrame(eye); - c.UpdateGroup(7, 1, 0, {xf.data(), xf.size()}, Vec3{0, 0, 0}, 0.05f); + c.UpdateGroup(7, 1, 0, base::Span(xf.data(), xf.size()), Vec3{0, 0, 0}, 0.05f); } // Move instance 0 right next to the camera. Same group_id, same generation, // same count: only the transforms (and the store's revision) change. @@ -155,7 +155,7 @@ int main() { c.BeginFrame(eye); // Without the revision bump the stale bitmask still hides instance 0 (its // sweep index has not come back around); with it the group re-admits all. - const auto& moved = c.UpdateGroup(7, 1, /*revision=*/1, {xf.data(), xf.size()}, + const auto& moved = c.UpdateGroup(7, 1, /*revision=*/1, base::Span(xf.data(), xf.size()), Vec3{0, 0, 0}, 0.05f); CHECK(moved[0] == 1); CHECK(CountVisible(moved) == kN); // revision bump restarts accept-all @@ -168,7 +168,7 @@ int main() { xf.assign(300, At(Vec3{0, 0, 500})); auto update = [&](f32 radius = 0.05f, Vec3 center = Vec3{}) -> const base::Vector& { c.BeginFrame(Vec3{}); - return c.UpdateGroup(0, 1, 0, {xf.data(), xf.size()}, center, radius); + return c.UpdateGroup(0, 1, 0, base::Span(xf.data(), xf.size()), center, radius); }; for (u32 f = 0; f < 5; ++f) update(); CHECK(CountVisible(update()) == 0); @@ -197,9 +197,9 @@ int main() { } if (g_failures == 0) { - std::printf("rt_instance_cull_test: all checks passed\n"); + ::printf("rt_instance_cull_test: all checks passed\n"); return 0; } - std::printf("rt_instance_cull_test: %d checks FAILED\n", g_failures); + ::printf("rt_instance_cull_test: %d checks FAILED\n", g_failures); return 1; } diff --git a/test/rt_slot_tracker_test.cc b/test/rt_slot_tracker_test.cc index b695284b..07d6cd50 100644 --- a/test/rt_slot_tracker_test.cc +++ b/test/rt_slot_tracker_test.cc @@ -5,7 +5,7 @@ // unbuilt slot, reading a slot whose BLASes were replaced) are guarded off-GPU. #include "render/gi/rt_slot_tracker.h" -#include +#include #include using namespace rx; @@ -17,7 +17,7 @@ int g_failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); \ + ::printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); \ ++g_failures; \ } \ } while (0) @@ -178,9 +178,9 @@ int main() { } if (g_failures == 0) { - std::printf("rt_slot_tracker_test: all checks passed\n"); + ::printf("rt_slot_tracker_test: all checks passed\n"); return 0; } - std::printf("rt_slot_tracker_test: %d checks FAILED\n", g_failures); + ::printf("rt_slot_tracker_test: %d checks FAILED\n", g_failures); return 1; } diff --git a/test/scene_authoring_test.cc b/test/scene_authoring_test.cc index d6f25987..e2be5424 100644 --- a/test/scene_authoring_test.cc +++ b/test/scene_authoring_test.cc @@ -9,22 +9,22 @@ // half of the stretch (the normals) is proved by render instead. Exits non-zero // on the first failure so it slots into ctest. -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include #include "asset/asset_database.h" #include "asset/vfs.h" +#include "base/strings/xstring.h" +#include "core/format.h" #include "core/math.h" +#include "core/scalar.h" #include "ecs/world.h" #include "edit/hierarchy.h" #include "edit/scene_io.h" #include "scene/components.h" #include "scene_authoring.h" +#include "core/file_system.h" using namespace rx; @@ -35,47 +35,45 @@ int failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++failures; \ } \ } while (0) -#define CHECK_NEAR(a, b, eps) CHECK(std::abs((a) - (b)) <= (eps)) +#define CHECK_NEAR(a, b, eps) CHECK(::abs((a) - (b)) <= (eps)) -namespace fs = std::filesystem; +namespace fs = rx::fs; -fs::path WriteScene(const char* name, const std::string& body) { - const fs::path path = fs::temp_directory_path() / name; - std::ofstream out(path, std::ios::binary); - out << "rxscene 1\n" << body; +base::String WriteScene(const char* name, const base::String& body) { + const base::String path = fs::Join(fs::TempDirectory(), name); + fs::WriteTextFile(path, "rxscene 1\n" + body); return path; } -std::string ReadFile(const fs::path& path) { - std::ifstream in(path, std::ios::binary); - std::ostringstream text; - text << in.rdbuf(); - return text.str(); +base::String ReadFile(const base::String& path) { + base::String text; + fs::ReadTextFile(path, &text); + return text; } // The viewer's own pass order (Viewer::LoadRxScene), so what this test measures // is what a render would draw. False when any of them refuses the file. -bool LoadAndBuild(ecs::World& world, asset::AssetDatabase& db, const fs::path& path) { - std::string error; - const std::string file = path.string(); +bool LoadAndBuild(ecs::World& world, asset::AssetDatabase& db, const base::String& path) { + base::String error; + const base::String file = path; if (!edit::LoadScene(world, db, file, &error, /*strict=*/true)) { - std::printf("load error: %s\n", error.c_str()); + ::printf("load error: %s\n", error.c_str()); return false; } if (!BuildSceneGrids(world, file, &error) || !BuildScenePrefabs(world, file, &error)) { - std::printf("layout error: %s\n", error.c_str()); + ::printf("layout error: %s\n", error.c_str()); return false; } BuildSceneRotations(world); if (!BuildSceneShapes(world, db, /*renderer=*/nullptr, file, &error) || !BuildSceneModels(world, db, /*renderer=*/nullptr, file, &error) || !BuildSceneAnchors(world, file, &error)) { - std::printf("build error: %s\n", error.c_str()); + ::printf("build error: %s\n", error.c_str()); return false; } return true; @@ -83,9 +81,9 @@ bool LoadAndBuild(ecs::World& world, asset::AssetDatabase& db, const fs::path& p // The same pass order, kept quiet and handing back the refusal: for the files // that are supposed to fail, where the message is the thing under test. -std::string LoadAndBuildError(ecs::World& world, asset::AssetDatabase& db, const fs::path& path) { - std::string error; - const std::string file = path.string(); +base::String LoadAndBuildError(ecs::World& world, asset::AssetDatabase& db, const base::String& path) { + base::String error; + const base::String file = path; if (!edit::LoadScene(world, db, file, &error, /*strict=*/true)) return error; if (!BuildSceneGrids(world, file, &error) || !BuildScenePrefabs(world, file, &error)) { return error; @@ -99,7 +97,7 @@ std::string LoadAndBuildError(ecs::World& world, asset::AssetDatabase& db, const return {}; } -ecs::Entity FindByName(ecs::World& world, const std::string& name) { +ecs::Entity FindByName(ecs::World& world, const base::String& name) { ecs::Entity found = ecs::kInvalidEntity; world.Each([&](ecs::Entity entity, scene::Name& value) { if (value.value == name) found = entity; @@ -116,7 +114,7 @@ Quat WorldRotation(ecs::World& world, ecs::Entity entity) { // tell it from every other one: which way a positive angle turns, and which // order the three axes compose in. void TestEulerConvention() { - const fs::path path = WriteScene("rx_rotation_convention.rxscene", R"( + const base::String path = WriteScene("rx_rotation_convention.rxscene", R"( entity Name.value = "Yawed" Shape.kind = "box" @@ -153,10 +151,10 @@ Rotation.euler = 90 90 0 // --validate's non_unit_rotation warns past 5%, and MakeFromQuat does not // normalize, so a euler that did not resolve to a unit quaternion would scale // every mesh it is written to. - const f32 length = std::sqrt(yaw.x * yaw.x + yaw.y * yaw.y + yaw.z * yaw.z + yaw.w * yaw.w); + const f32 length = ::sqrt(yaw.x * yaw.x + yaw.y * yaw.y + yaw.z * yaw.z + yaw.w * yaw.w); CHECK_NEAR(length, 1.0f, 1e-5f); - fs::remove(path); + fs::Remove(path); } // Write it, load it, save it, load THAT and save again: the two saved files have @@ -164,7 +162,7 @@ Rotation.euler = 90 90 0 // author did not touch. The first save is what settles the random guids, so the // comparison starts from it rather than from the source text. void TestRoundTrip() { - const fs::path path = WriteScene("rx_rotation_roundtrip.rxscene", R"( + const base::String path = WriteScene("rx_rotation_roundtrip.rxscene", R"( entity Name.value = "Plinth" Transform.position = 0 0.5 0 @@ -184,20 +182,20 @@ Shape.size = 0.3 0.3 0.3 ecs::World first; CHECK(LoadAndBuild(first, db, path)); - const fs::path saved_a = fs::temp_directory_path() / "rx_rotation_roundtrip_a.rxscene"; - const fs::path saved_b = fs::temp_directory_path() / "rx_rotation_roundtrip_b.rxscene"; - std::string error; - CHECK(edit::SaveScene(first, saved_a.string(), &error)); + const base::String saved_a = fs::Join(fs::TempDirectory(), "rx_rotation_roundtrip_a.rxscene"); + const base::String saved_b = fs::Join(fs::TempDirectory(), "rx_rotation_roundtrip_b.rxscene"); + base::String error; + CHECK(edit::SaveScene(first, saved_a, &error)); ecs::World second; CHECK(LoadAndBuild(second, db, saved_a)); - CHECK(edit::SaveScene(second, saved_b.string(), &error)); + CHECK(edit::SaveScene(second, saved_b, &error)); CHECK(ReadFile(saved_a) == ReadFile(saved_b)); // The source declaration survives alongside the value it resolved into: a // save that dropped Rotation would leave a bare quaternion no one can edit, // and one that re-applied it on load would turn the entity twice. - CHECK(ReadFile(saved_a).find("Rotation.euler") != std::string::npos); + CHECK(ReadFile(saved_a).find("Rotation.euler") != base::String::npos); const Quat before = WorldRotation(first, FindByName(first, "Tilted")); const Quat after = WorldRotation(second, FindByName(second, "Tilted")); CHECK_NEAR(before.x, after.x, 1e-6f); @@ -205,16 +203,16 @@ Shape.size = 0.3 0.3 0.3 CHECK_NEAR(before.z, after.z, 1e-6f); CHECK_NEAR(before.w, after.w, 1e-6f); - fs::remove(path); - fs::remove(saved_a); - fs::remove(saved_b); + fs::Remove(path); + fs::Remove(saved_a); + fs::Remove(saved_b); } // An anchor stands one box on another by measuring both, and a turned box has a // taller footprint than the one it was authored with. Standing it on the // unrotated extent would bury a corner in the plinth. void TestAnchorOnRotated() { - const fs::path path = WriteScene("rx_rotation_anchor.rxscene", R"( + const base::String path = WriteScene("rx_rotation_anchor.rxscene", R"( entity Name.value = "Plinth" Transform.position = 0 0.5 0 @@ -246,12 +244,12 @@ Shape.size = 0.5 0.5 0.5 const scene::Transform* upright = world.Get(FindByName(world, "Upright")); CHECK_NEAR(upright->position[1], 1.5f, 1e-4f); const scene::Transform* cornered = world.Get(FindByName(world, "Cornered")); - CHECK_NEAR(cornered->position[1], 1.0f + 0.5f * std::sqrt(2.0f), 1e-4f); + CHECK_NEAR(cornered->position[1], 1.0f + 0.5f * ::sqrt(2.0f), 1e-4f); // The two axes the mode does not stack along still centre on the target. CHECK_NEAR(cornered->position[0], 0.0f, 1e-4f); CHECK_NEAR(cornered->position[2], 0.0f, 1e-4f); - fs::remove(path); + fs::Remove(path); } // Anchor.offset is what makes a ground plane usable as a target: centring is @@ -261,7 +259,7 @@ Shape.size = 0.5 0.5 0.5 // naming any of their heights, and go on landing on it when a Stretch changes // one of them. void TestAnchorOffset() { - const fs::path path = WriteScene("rx_anchor_offset.rxscene", R"( + const base::String path = WriteScene("rx_anchor_offset.rxscene", R"( entity Name.value = "Ground" Transform.position = 0 0 0 @@ -308,10 +306,10 @@ Stretch.scale = 1 4 1 // Transform.position instead, every round trip would walk the object another // offset along - which is the bug the anchor replaces rather than offsets to // avoid in the first place. - const fs::path saved = fs::temp_directory_path() / "rx_anchor_offset_saved.rxscene"; - std::string error; - CHECK(edit::SaveScene(world, saved.string(), &error)); - CHECK(ReadFile(saved).find("Anchor.offset") != std::string::npos); + const base::String saved = fs::Join(fs::TempDirectory(), "rx_anchor_offset_saved.rxscene"); + base::String error; + CHECK(edit::SaveScene(world, saved, &error)); + CHECK(ReadFile(saved).find("Anchor.offset") != base::String::npos); ecs::World reloaded; CHECK(LoadAndBuild(reloaded, db, saved)); @@ -320,15 +318,15 @@ Stretch.scale = 1 4 1 CHECK_NEAR(again->position[1], 6.0f, 1e-4f); CHECK_NEAR(again->position[2], -3.0f, 1e-4f); - fs::remove(path); - fs::remove(saved); + fs::Remove(path); + fs::Remove(saved); } // A grid member is a child of its container, so a rotation on either composes // the way a parent chain does: the container's turns the whole layout about its // own origin, the member's turns only that cell. void TestRotatedGrid() { - const fs::path path = WriteScene("rx_rotation_grid.rxscene", R"( + const base::String path = WriteScene("rx_rotation_grid.rxscene", R"( entity Name.value = "Row" Transform.position = 0 0 0 @@ -362,27 +360,27 @@ Shape.size = 0.5 0.5 0.5 // ... and the member's own 45 adds to the container's 90, so its +z face ends // up 135 degrees round. const Vec3 facing = Rotate(WorldRotation(world, FindByName(world, "Second")), {0, 0, 1}); - CHECK_NEAR(facing.x, std::sin(135.0f * 3.14159265f / 180.0f), 1e-4f); - CHECK_NEAR(facing.z, std::cos(135.0f * 3.14159265f / 180.0f), 1e-4f); + CHECK_NEAR(facing.x, ::sin(135.0f * 3.14159265f / 180.0f), 1e-4f); + CHECK_NEAR(facing.z, ::cos(135.0f * 3.14159265f / 180.0f), 1e-4f); const Vec3 unrotated = Rotate(WorldRotation(world, FindByName(world, "First")), {0, 0, 1}); CHECK_NEAR(unrotated.x, 1.0f, 1e-4f); - fs::remove(path); + fs::Remove(path); } // The prefab override rule is per component, so an instance that says anything // about Rotation owns its orientation and one that says nothing takes the // prefab's, exactly like Shape and Surface. void TestPrefabRotation() { - const fs::path prefab = WriteScene("rx_rotation_prefab_cell.rxscene", R"( + const base::String prefab = WriteScene("rx_rotation_prefab_cell.rxscene", R"( entity Name.value = "Cell" Rotation.euler = 0 90 0 Shape.kind = "box" Shape.size = 0.5 0.5 0.5 )"); - const fs::path path = WriteScene("rx_rotation_prefab.rxscene", + const base::String path = WriteScene("rx_rotation_prefab.rxscene", R"( entity Name.value = "Inherits" @@ -405,8 +403,8 @@ Prefab.path = "rx_rotation_prefab_cell.rxscene" const Vec3 overridden = Rotate(WorldRotation(world, FindByName(world, "Overrides")), {0, 0, 1}); CHECK_NEAR(overridden.z, -1.0f, 1e-4f); - fs::remove(prefab); - fs::remove(path); + fs::Remove(prefab); + fs::Remove(path); } // Stretch.scale multiplies the built geometry per axis, so a box may reach the @@ -414,7 +412,7 @@ Prefab.path = "rx_rotation_prefab_cell.rxscene" // the key has to keep them apart, or the second entity draws the first's // geometry. void TestStretchComposesWithSize() { - const fs::path path = WriteScene("rx_stretch_compose.rxscene", R"( + const base::String path = WriteScene("rx_stretch_compose.rxscene", R"( entity Name.value = "Sized" Shape.kind = "box" @@ -464,7 +462,7 @@ Shape.size = 0.5 0.5 0.5 CHECK(by_stretch.hash == twin.hash); CHECK(by_stretch.hash != plain.hash); - fs::remove(path); + fs::Remove(path); } // A prefab of more than one piece has to stretch WHOLE, or the only shape that @@ -472,7 +470,7 @@ Shape.size = 0.5 0.5 0.5 // authored building a rectangle. Each part's offset scales, so the crown stays // on the shaft, and each part's geometry scales, so it widens with it. void TestPrefabStretchReachesParts() { - const fs::path prefab = WriteScene("rx_stretch_parts_cell.rxscene", R"( + const base::String prefab = WriteScene("rx_stretch_parts_cell.rxscene", R"( entity Name.value = "Podium" Shape.kind = "box" @@ -491,7 +489,7 @@ Shape.kind = "box" Shape.size = 1.5 1 1.5 Stretch.scale = 1 0.5 1 )"); - const fs::path path = WriteScene("rx_stretch_parts.rxscene", R"( + const base::String path = WriteScene("rx_stretch_parts.rxscene", R"( entity Name.value = "Plain" Transform.position = 0 0 0 @@ -524,8 +522,8 @@ Prefab.path = "rx_stretch_parts_cell.rxscene" CHECK(count == 2); // One crown stayed at 10, the other doubled to 20 with the y stretch. Without // the offset scaling it would have stayed at 10 and sat inside the shaft. - const f32 low = std::min(heights[0], heights[1]); - const f32 high = std::max(heights[0], heights[1]); + const f32 low = rx::Min(heights[0], heights[1]); + const f32 high = rx::Max(heights[0], heights[1]); CHECK_NEAR(low, 10.0f, 1e-4f); CHECK_NEAR(high, 20.0f, 1e-4f); @@ -544,14 +542,14 @@ Prefab.path = "rx_stretch_parts_cell.rxscene" }); CHECK(checked_crown); - fs::remove(prefab); - fs::remove(path); + fs::Remove(prefab); + fs::Remove(path); } // A turned part cannot take a per-axis stretch: that is a shear, and nothing in // the transform path carries one. Refused by name rather than rendered wrong. void TestPrefabStretchRefusesShear() { - const fs::path prefab = WriteScene("rx_stretch_shear_cell.rxscene", R"( + const base::String prefab = WriteScene("rx_stretch_shear_cell.rxscene", R"( entity Name.value = "Base" Shape.kind = "box" @@ -564,7 +562,7 @@ Rotation.euler = 0 30 0 Shape.kind = "box" Shape.size = 1 0.2 0.4 )"); - const fs::path path = WriteScene("rx_stretch_shear.rxscene", R"( + const base::String path = WriteScene("rx_stretch_shear.rxscene", R"( entity Name.value = "Sheared" Transform.position = 0 0 0 @@ -574,12 +572,12 @@ Prefab.path = "rx_stretch_shear_cell.rxscene" asset::Vfs vfs; asset::AssetDatabase db(vfs); ecs::World world; - const std::string error = LoadAndBuildError(world, db, path); - CHECK(error.find("Fin") != std::string::npos); - CHECK(error.find("shear") != std::string::npos); + const base::String error = LoadAndBuildError(world, db, path); + CHECK(error.find("Fin") != base::String::npos); + CHECK(error.find("shear") != base::String::npos); // A UNIFORM stretch of the same prefab is a similarity, so it is allowed. - const fs::path uniform = WriteScene("rx_stretch_shear_uniform.rxscene", R"( + const base::String uniform = WriteScene("rx_stretch_shear_uniform.rxscene", R"( entity Name.value = "Scaled" Transform.position = 0 0 0 @@ -589,9 +587,9 @@ Prefab.path = "rx_stretch_shear_cell.rxscene" ecs::World fine; CHECK(LoadAndBuild(fine, db, uniform)); - fs::remove(prefab); - fs::remove(path); - fs::remove(uniform); + fs::Remove(prefab); + fs::Remove(path); + fs::Remove(uniform); } // The reason Stretch is a component of its own: prefab merge is per component, @@ -600,7 +598,7 @@ Prefab.path = "rx_stretch_shear_cell.rxscene" // all - authoring any part of Shape replaces the prefab's whole Shape, which // silently loses the kind and size and draws a default box. void TestPrefabStretch() { - const fs::path prefab = WriteScene("rx_stretch_prefab_cell.rxscene", R"( + const base::String prefab = WriteScene("rx_stretch_prefab_cell.rxscene", R"( entity Name.value = "Tower" Shape.kind = "box" @@ -608,7 +606,7 @@ Shape.size = 1 4 1 Surface.base_color = 0.2 0.4 0.6 Surface.roughness = 0.35 )"); - const fs::path path = WriteScene("rx_stretch_prefab.rxscene", R"( + const base::String path = WriteScene("rx_stretch_prefab.rxscene", R"( entity Name.value = "Plain" Transform.position = 0 0 0 @@ -647,7 +645,7 @@ Prefab.path = "rx_stretch_prefab_cell.rxscene" // ... and the facade rides along, which is the other half of "the instance // owns only what it authored". const SceneSurface* surface = world.Get(entity); - CHECK(surface && std::abs(surface->base_color[2] - 0.6f) < 1e-6f); + CHECK(surface && ::abs(surface->base_color[2] - 0.6f) < 1e-6f); } // Three proportions of one prefab are three meshes, never a shared one. @@ -660,10 +658,10 @@ Prefab.path = "rx_stretch_prefab_cell.rxscene" // A save of the expanded scene reloads onto the same proportions: the // instance's Stretch survives beside the Shape the expansion left on it. - const fs::path saved = fs::temp_directory_path() / "rx_stretch_prefab_saved.rxscene"; - std::string error; - CHECK(edit::SaveScene(world, saved.string(), &error)); - CHECK(ReadFile(saved).find("Stretch.scale = 2 0.5 1") != std::string::npos); + const base::String saved = fs::Join(fs::TempDirectory(), "rx_stretch_prefab_saved.rxscene"); + base::String error; + CHECK(edit::SaveScene(world, saved, &error)); + CHECK(ReadFile(saved).find("Stretch.scale = 2 0.5 1") != base::String::npos); ecs::World reloaded; CHECK(LoadAndBuild(reloaded, db, saved)); const SceneBounds* before = world.Get(FindByName(world, "Wide")); @@ -671,9 +669,9 @@ Prefab.path = "rx_stretch_prefab_cell.rxscene" CHECK(before && after); for (int axis = 0; axis < 3; ++axis) CHECK_NEAR(before->max[axis], after->max[axis], 1e-6f); - fs::remove(saved); - fs::remove(prefab); - fs::remove(path); + fs::Remove(saved); + fs::Remove(prefab); + fs::Remove(path); } // An anchor measures built geometry, and after this change the built geometry is @@ -681,7 +679,7 @@ Prefab.path = "rx_stretch_prefab_cell.rxscene" // this is the check that the bake really did reach SceneBounds rather than // leaving the placement to be worked out from the authored Shape.size. void TestAnchorOnStretched() { - const fs::path path = WriteScene("rx_stretch_anchor.rxscene", R"( + const base::String path = WriteScene("rx_stretch_anchor.rxscene", R"( entity Name.value = "Plinth" Transform.position = 0 1 0 @@ -720,14 +718,14 @@ Stretch.scale = 4 1 1 CHECK_NEAR(beside->position[0], 1.0f + 2.0f, 1e-3f); CHECK_NEAR(beside->position[1], 1.0f, 1e-3f); - fs::remove(path); + fs::Remove(path); } // A grid steps by Grid.step whatever its members are made of, so a stretched // cell lands on the same coordinate an unstretched one would. The point of the // check is that the bake did not move the geometry off its own origin. void TestStretchedGrid() { - const fs::path path = WriteScene("rx_stretch_grid.rxscene", R"( + const base::String path = WriteScene("rx_stretch_grid.rxscene", R"( entity Name.value = "Row" Transform.position = 0 0 0 @@ -765,13 +763,13 @@ Stretch.scale = 1 1 2 CHECK_NEAR(bounds->min[0], -0.5f, 1e-4f); CHECK_NEAR(bounds->min[2], -1.0f, 1e-4f); - fs::remove(path); + fs::Remove(path); } // Zero would make the normal bake divide by it and hand the whole mesh nans, so // the load says no and names the line rather than uploading the wreckage. void TestDegenerateStretchFailsTheLoad() { - const fs::path path = WriteScene("rx_stretch_degenerate.rxscene", R"( + const base::String path = WriteScene("rx_stretch_degenerate.rxscene", R"( entity Name.value = "Flattened" Shape.kind = "sphere" @@ -781,18 +779,18 @@ Stretch.scale = 1 0 1 asset::Vfs vfs; asset::AssetDatabase db(vfs); ecs::World world; - const std::string error = LoadAndBuildError(world, db, path); - CHECK(error.find("rx_stretch_degenerate.rxscene:7:") != std::string::npos); - CHECK(error.find("Stretch.scale") != std::string::npos); + const base::String error = LoadAndBuildError(world, db, path); + CHECK(error.find("rx_stretch_degenerate.rxscene:7:") != base::String::npos); + CHECK(error.find("Stretch.scale") != base::String::npos); - fs::remove(path); + fs::Remove(path); } // Same round trip the rotation test makes: a saved scene reloads and re-saves // byte-identically, so a live edit of a stretched scene does not rewrite the // proportions the author set. void TestStretchRoundTrip() { - const fs::path path = WriteScene("rx_stretch_roundtrip.rxscene", R"( + const base::String path = WriteScene("rx_stretch_roundtrip.rxscene", R"( entity Name.value = "Tower" Transform.position = 0 6 0 @@ -805,44 +803,45 @@ Stretch.scale = 2.5 6 1.75 ecs::World first; CHECK(LoadAndBuild(first, db, path)); - const fs::path saved_a = fs::temp_directory_path() / "rx_stretch_roundtrip_a.rxscene"; - const fs::path saved_b = fs::temp_directory_path() / "rx_stretch_roundtrip_b.rxscene"; - std::string error; - CHECK(edit::SaveScene(first, saved_a.string(), &error)); + const base::String saved_a = fs::Join(fs::TempDirectory(), "rx_stretch_roundtrip_a.rxscene"); + const base::String saved_b = fs::Join(fs::TempDirectory(), "rx_stretch_roundtrip_b.rxscene"); + base::String error; + CHECK(edit::SaveScene(first, saved_a, &error)); ecs::World second; CHECK(LoadAndBuild(second, db, saved_a)); - CHECK(edit::SaveScene(second, saved_b.string(), &error)); + CHECK(edit::SaveScene(second, saved_b, &error)); CHECK(ReadFile(saved_a) == ReadFile(saved_b)); - CHECK(ReadFile(saved_a).find("Stretch.scale = 2.5 6 1.75") != std::string::npos); + CHECK(ReadFile(saved_a).find("Stretch.scale = 2.5 6 1.75") != base::String::npos); const SceneBounds* before = first.Get(FindByName(first, "Tower")); const SceneBounds* after = second.Get(FindByName(second, "Tower")); CHECK(before && after); for (int axis = 0; axis < 3; ++axis) CHECK_NEAR(before->max[axis], after->max[axis], 1e-6f); - fs::remove(path); - fs::remove(saved_a); - fs::remove(saved_b); + fs::Remove(path); + fs::Remove(saved_a); + fs::Remove(saved_b); } // A tiny uncompressed 24-bit TGA of one colour. Written rather than checked in // because what these tests need from an image is only that it decodes: an // 18-byte header and three bytes a pixel is the whole format, and a binary // fixture in the tree would have to be explained to everyone who greps for it. -fs::path WriteImage(const char* name, u8 r, u8 g, u8 b) { - const fs::path path = fs::temp_directory_path() / name; +base::String WriteImage(const char* name, u8 r, u8 g, u8 b) { + const base::String path = fs::Join(fs::TempDirectory(), name); const int size = 4; u8 header[18] = {}; header[2] = 2; // uncompressed true-colour header[12] = static_cast(size); header[14] = static_cast(size); header[16] = 24; - std::ofstream out(path, std::ios::binary); - out.write(reinterpret_cast(header), sizeof(header)); + base::Vector bytes; + bytes.insert(bytes.end(), header, header + sizeof(header)); for (int pixel = 0; pixel < size * size; ++pixel) { const u8 bgr[3] = {b, g, r}; // TGA stores blue first - out.write(reinterpret_cast(bgr), sizeof(bgr)); + bytes.insert(bytes.end(), bgr, bgr + sizeof(bgr)); } + fs::WriteFile(path, bytes); return path; } @@ -852,9 +851,9 @@ fs::path WriteImage(const char* name, u8 r, u8 g, u8 b) { // one material - the second entity would silently draw the first one's texture, // which looks like a scene that was authored that way. void TestSurfaceMapsKeyApart() { - const fs::path red = WriteImage("rx_map_red.tga", 220, 30, 30); - const fs::path blue = WriteImage("rx_map_blue.tga", 30, 30, 220); - const fs::path path = WriteScene("rx_surface_maps.rxscene", std::format(R"( + const base::String red = WriteImage("rx_map_red.tga", 220, 30, 30); + const base::String blue = WriteImage("rx_map_blue.tga", 30, 30, 220); + const base::String path = WriteScene("rx_surface_maps.rxscene", rx::StrFormat(R"( entity Name.value = "Red" Shape.kind = "box" @@ -872,7 +871,7 @@ Name.value = "RedAgain" Shape.kind = "box" Shape.size = 1 1 1 Surface.base_color_map = "{}" -)", red.string(), blue.string(), red.string())); +)", red, blue, red)); asset::Vfs vfs; asset::AssetDatabase db(vfs); ecs::World world; @@ -887,16 +886,16 @@ Surface.base_color_map = "{}" // every textured entity in a scene would pay for its own copy of the set. CHECK(a->mesh == c->mesh); - fs::remove(path); - fs::remove(red); - fs::remove(blue); + fs::Remove(path); + fs::Remove(red); + fs::Remove(blue); } // A map that names no image fails the load on the line that wrote it, rather // than binding the material system's 1x1 default and rendering a flat colour // the author would have to reverse-engineer. void TestSurfaceMapMissingFailsTheLoad() { - const fs::path path = WriteScene("rx_surface_map_missing.rxscene", R"( + const base::String path = WriteScene("rx_surface_map_missing.rxscene", R"( entity Name.value = "Wall" Shape.kind = "box" @@ -906,20 +905,20 @@ Surface.normal_map = "no/such/concrete_normal.png" asset::Vfs vfs; asset::AssetDatabase db(vfs); ecs::World world; - const std::string error = LoadAndBuildError(world, db, path); - CHECK(error.find("rx_surface_map_missing.rxscene:7:") != std::string::npos); - CHECK(error.find("Surface.normal_map") != std::string::npos); - CHECK(error.find("working directory") != std::string::npos); + const base::String error = LoadAndBuildError(world, db, path); + CHECK(error.find("rx_surface_map_missing.rxscene:7:") != base::String::npos); + CHECK(error.find("Surface.normal_map") != base::String::npos); + CHECK(error.find("working directory") != base::String::npos); - fs::remove(path); + fs::Remove(path); } // A Pattern and a texture map both write base colour, normal and roughness on // the one material the entity gets, so the pair is refused by name instead of // given a silent precedence. void TestSurfaceMapRefusesPattern() { - const fs::path image = WriteImage("rx_map_pattern.tga", 200, 200, 200); - const fs::path path = WriteScene("rx_surface_map_pattern.rxscene", std::format(R"( + const base::String image = WriteImage("rx_map_pattern.tga", 200, 200, 200); + const base::String path = WriteScene("rx_surface_map_pattern.rxscene", rx::StrFormat(R"( entity Name.value = "Facade" Shape.kind = "box" @@ -927,17 +926,17 @@ Shape.size = 1 1 1 Surface.base_color_map = "{}" Pattern.kind = "brick" Pattern.scale = 6 8 -)", image.string())); +)", image)); asset::Vfs vfs; asset::AssetDatabase db(vfs); ecs::World world; - const std::string error = LoadAndBuildError(world, db, path); - CHECK(error.find("rx_surface_map_pattern.rxscene:7:") != std::string::npos); - CHECK(error.find("Surface.base_color_map") != std::string::npos); - CHECK(error.find("Pattern") != std::string::npos); + const base::String error = LoadAndBuildError(world, db, path); + CHECK(error.find("rx_surface_map_pattern.rxscene:7:") != base::String::npos); + CHECK(error.find("Surface.base_color_map") != base::String::npos); + CHECK(error.find("Pattern") != base::String::npos); - fs::remove(path); - fs::remove(image); + fs::Remove(path); + fs::Remove(image); } } // namespace @@ -961,6 +960,6 @@ int main() { TestSurfaceMapsKeyApart(); TestSurfaceMapMissingFailsTheLoad(); TestSurfaceMapRefusesPattern(); - if (failures == 0) std::printf("scene_authoring_test: all checks passed\n"); + if (failures == 0) ::printf("scene_authoring_test: all checks passed\n"); return failures == 0 ? 0 : 1; } diff --git a/test/script_test.cc b/test/script_test.cc index d6daebcb..a41cf4df 100644 --- a/test/script_test.cc +++ b/test/script_test.cc @@ -4,13 +4,17 @@ // scene components directly (no gateway); the test exercises them purely from a // ScriptValue stack. Pure CPU logic; plain ctest runs it. -#include -#include -#include -#include -#include -#include - +#include +#include +#include +#if !defined(_WIN32) +#include +#include +#endif + +#include "base/memory/move.h" +#include "base/numeric_limits.h" +#include "base/strings/xstring.h" #include "core/math.h" #include "ecs/entity.h" #include "ecs/world.h" @@ -25,12 +29,32 @@ namespace { +// True when `fn` ends the process instead of returning. POSIX only; elsewhere +// the check is skipped and counts as passing. +template +bool DiesInChild(Fn fn) { +#if defined(_WIN32) + (void)fn; + return true; +#else + ::fflush(nullptr); + const pid_t child = ::fork(); + if (child == 0) { + fn(); + ::_exit(0); // returned: the request was not refused + } + int status = 0; + if (child < 0 || ::waitpid(child, &status, 0) < 0) return false; + return WIFSIGNALED(status); +#endif +} + int g_failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++g_failures; \ } \ } while (0) @@ -41,20 +65,20 @@ namespace ecs = rx::ecs; using rx::f32; using rx::Vec3; -std::string g_log; +base::String g_log; void LogSink(void* user, script::ScriptStringView msg) { - static_cast(user)->assign(msg.view()); + static_cast(user)->assign(msg.view()); } void TestScriptString() { script::ScriptString s("hello"); CHECK(s.size() == 5); CHECK(s == script::ScriptStringView("hello", 5)); - CHECK(std::string(s.c_str()) == "hello"); + CHECK(base::String(s.c_str()) == "hello"); script::ScriptString copy = s; // copy ctor -> deep copy CHECK(copy == s); - script::ScriptString moved = std::move(copy); // move ctor + script::ScriptString moved = base::move(copy); // move ctor CHECK(moved == s); CHECK(moved.view().view() == "hello"); @@ -65,42 +89,26 @@ void TestScriptString() { script::ScriptString empty; CHECK(empty.empty()); - CHECK(std::string(empty.c_str()).empty()); + CHECK(base::String(empty.c_str()).empty()); - bool rejected = false; - try { + // Invalid requests end the process (there are no exceptions to catch), so + // each is run in a child that must die rather than return. + CHECK(DiesInChild([] { script::ScriptString too_large( - script::ScriptStringView(nullptr, std::numeric_limits::max())); - } catch (const std::bad_array_new_length&) { - rejected = true; - } - CHECK(rejected); - - script::ScriptArena arena(16); - rejected = false; - try { - script::ArenaCopy( - arena, script::ScriptStringView(nullptr, std::numeric_limits::max())); - } catch (const std::bad_array_new_length&) { - rejected = true; - } - CHECK(rejected); - - rejected = false; - try { - arena.Alloc(std::numeric_limits::max(), alignof(std::max_align_t)); - } catch (const std::bad_array_new_length&) { - rejected = true; - } - CHECK(rejected); - - rejected = false; - try { + script::ScriptStringView(nullptr, base::MinMax::max())); + })); + CHECK(DiesInChild([] { + script::ScriptArena arena(16); + script::ArenaCopy(arena, script::ScriptStringView(nullptr, base::MinMax::max())); + })); + CHECK(DiesInChild([] { + script::ScriptArena arena(16); + arena.Alloc(SIZE_MAX, alignof(max_align_t)); + })); + CHECK(DiesInChild([] { + script::ScriptArena arena(16); arena.Alloc(1, 3); - } catch (const std::bad_array_new_length&) { - rejected = true; - } - CHECK(rejected); + })); } void TestInterner() { @@ -169,7 +177,7 @@ void TestDispatch() { rig.Call("World.SetScale", {V::EntityRef(e), V::Float(3.0)}); ret = rig.Call("World.GetScale", {V::EntityRef(e)}); - CHECK(std::fabs(ret.as_float() - 3.0) < 1e-5); + CHECK(::fabs(ret.as_float() - 3.0) < 1e-5); ret = rig.Call("World.IsValid", {V::EntityRef(e)}); CHECK(ret.as_bool() == true); @@ -213,21 +221,21 @@ void TestWorldSpaceDispatch() { rig.world.Add(child, scene::Parent{parent}); V ret = rig.Call("World.GetPosition", {V::EntityRef(child)}); - CHECK(std::fabs(ret.as_vec3().x - 10.0f) < 1e-4f); - CHECK(std::fabs(ret.as_vec3().y - 2.0f) < 1e-4f); + CHECK(::fabs(ret.as_vec3().x - 10.0f) < 1e-4f); + CHECK(::fabs(ret.as_vec3().y - 2.0f) < 1e-4f); rig.Call("World.Teleport", {V::EntityRef(child), V::Vec(Vec3{8, 0, 0})}); scene::Transform* local = rig.world.Get(child); - CHECK(std::fabs(local->position[0] - 0.0f) < 1e-4f); - CHECK(std::fabs(local->position[1] - 1.0f) < 1e-4f); + CHECK(::fabs(local->position[0] - 0.0f) < 1e-4f); + CHECK(::fabs(local->position[1] - 1.0f) < 1e-4f); ret = rig.Call("World.GetPosition", {V::EntityRef(child)}); - CHECK(std::fabs(ret.as_vec3().x - 8.0f) < 1e-4f); - CHECK(std::fabs(ret.as_vec3().y - 0.0f) < 1e-4f); + CHECK(::fabs(ret.as_vec3().x - 8.0f) < 1e-4f); + CHECK(::fabs(ret.as_vec3().y - 0.0f) < 1e-4f); ecs::Entity other = rig.world.Create(); rig.world.Add(other, scene::Transform{{8, 0, 0}}); ret = rig.Call("World.DistanceBetween", {V::EntityRef(child), V::EntityRef(other)}); - CHECK(std::fabs(ret.as_float()) < 1e-4); + CHECK(::fabs(ret.as_float()) < 1e-4); // Any nonzero parent scale is invertible, even below the editor's normal // range. Teleport must not silently reject it as singular. @@ -238,7 +246,7 @@ void TestWorldSpaceDispatch() { rig.world.Add(tiny_child, scene::Parent{tiny_parent}); rig.Call("World.Teleport", {V::EntityRef(tiny_child), V::Vec(Vec3{1, 0, 0})}); ret = rig.Call("World.GetPosition", {V::EntityRef(tiny_child)}); - CHECK(std::fabs(ret.as_vec3().x - 1.0f) < 1e-4f); + CHECK(::fabs(ret.as_vec3().x - 1.0f) < 1e-4f); // Over-depth hierarchies are rejected consistently: reads report no position // and writes leave the local transform untouched. @@ -306,9 +314,9 @@ int main() { TestWorldSpaceDispatch(); TestSymbolsAndArena(); if (g_failures == 0) { - std::printf("script_test: all checks passed\n"); + ::printf("script_test: all checks passed\n"); return 0; } - std::printf("script_test: %d failure(s)\n", g_failures); + ::printf("script_test: %d failure(s)\n", g_failures); return 1; } diff --git a/test/settings_ini_test.cc b/test/settings_ini_test.cc index f405f648..02f6dfe2 100644 --- a/test/settings_ini_test.cc +++ b/test/settings_ini_test.cc @@ -1,9 +1,9 @@ +#include "base/strings/xstring.h" #include "render/core/settings_ini.h" -#include -#include -#include -#include +#include +#include +#include namespace { @@ -12,15 +12,10 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "settings_ini_test: FAIL: %s\n", message); + ::fprintf(stderr, "settings_ini_test: FAIL: %s\n", message); ++failures; } -class CommaDecimal final : public std::numpunct { -protected: - char do_decimal_point() const override { return ','; } -}; - } // namespace int main() { @@ -42,28 +37,40 @@ int main() { settings.cloud_coverage = 0.5f; Check(rx::render::ApplyIni("cloud_coverage = nan", settings) == 0, "non-finite floats are rejected"); - Check(std::isfinite(settings.cloud_coverage) && + Check(::isfinite(settings.cloud_coverage) && settings.cloud_coverage == 0.5f, "a rejected float leaves the setting unchanged"); - std::locale original = std::locale(); - std::locale::global(std::locale(std::locale::classic(), new CommaDecimal)); - settings.cloud_coverage = 0.25f; - std::string serialized = rx::render::SettingsToIni(settings); - rx::render::RenderSettings restored; - int round_trip_count = rx::render::ApplyIni(serialized, restored); - std::locale::global(original); - Check(round_trip_count > 0 && - restored.cloud_coverage == settings.cloud_coverage, - "serialized settings round-trip under a localized global locale"); + // A host that localizes the process to a comma decimal point must not change + // what the ini writes or reads. Skipped when no such locale is installed. + const char* const kCommaLocales[] = {"de_DE.UTF-8", "de_DE.utf8", "fr_FR.UTF-8", "fr_FR.utf8"}; + const char* localized = nullptr; + for (const char* name : kCommaLocales) { + if (::setlocale(LC_NUMERIC, name) && ::localeconv()->decimal_point[0] == ',') { + localized = name; + break; + } + } + if (localized) { + settings.cloud_coverage = 0.25f; + base::String serialized = rx::render::SettingsToIni(settings); + rx::render::RenderSettings restored; + int round_trip_count = rx::render::ApplyIni(serialized, restored); + ::setlocale(LC_NUMERIC, "C"); + Check(round_trip_count > 0 && + restored.cloud_coverage == settings.cloud_coverage, + "serialized settings round-trip under a comma-decimal C locale"); + } else { + ::fprintf(stderr, "settings_ini_test: no comma-decimal locale installed, locale check skipped\n"); + } rx::render::RenderSettings source; source.procedural_grass = false; source.tonemap = rx::render::TonemapOperator::kAgx; - const std::string ini = rx::render::SettingsToIni(source); - Check(ini.find("procedural_grass = false") != std::string::npos, + const base::String ini = rx::render::SettingsToIni(source); + Check(ini.find("procedural_grass = false") != base::String::npos, "serialization emits the disabled grass toggle"); - Check(ini.find("tonemap = agx") != std::string::npos, + Check(ini.find("tonemap = agx") != base::String::npos, "serialization emits the AgX tonemap operator"); rx::render::RenderSettings round_trip; @@ -96,6 +103,6 @@ int main() { if (failures != 0) return 1; - std::printf("settings_ini_test: PASS\n"); + ::printf("settings_ini_test: PASS\n"); return 0; } diff --git a/test/terrain_test.cc b/test/terrain_test.cc index 97aa3e9d..550e2a84 100644 --- a/test/terrain_test.cc +++ b/test/terrain_test.cc @@ -1,21 +1,24 @@ +#include "base/containers/array.h" +#include "base/numeric_limits.h" +#include "base/optional.h" +#include "base/strings/xstring.h" +#include "core/format.h" #include "terrain/terrain.h" +#include "base/random/random.h" +#include "core/file_system.h" +#include "core/scalar.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include namespace { using namespace rx::terrain; namespace asset = rx::asset; namespace scene = rx::scene; +namespace fs = rx::fs; using rx::f32; using rx::u32; using rx::u64; @@ -27,14 +30,14 @@ int failures = 0; void Check(bool condition, const char *message) { if (condition) return; - std::fprintf(stderr, "terrain_test: FAIL: %s\n", message); + ::fprintf(stderr, "terrain_test: FAIL: %s\n", message); ++failures; } void Near(f32 actual, f32 expected, const char *message, f32 epsilon = 1e-4f) { - if (std::abs(actual - expected) <= epsilon) + if (::abs(actual - expected) <= epsilon) return; - std::fprintf(stderr, "terrain_test: FAIL: %s (got %.6f, expected %.6f)\n", + ::fprintf(stderr, "terrain_test: FAIL: %s (got %.6f, expected %.6f)\n", message, actual, expected); ++failures; } @@ -55,8 +58,8 @@ u32 WeightSum(TerrainWeights weights) { void TestSeamsAndBrushes() { Terrain terrain(BasicDesc()); - const std::array flat = {}; - const std::array right = {3, 3, 3, 4, 4, 4, 5, 5, 5}; + const base::Array flat = {}; + const base::Array right = {3, 3, 3, 4, 4, 4, 5, 5, 5}; Check(terrain.AddOrReplaceTile({0, 0}, flat), "left tile is added"); Check(terrain.AddOrReplaceTile({1, 0}, right), "right tile is added"); const TerrainTile *left = terrain.FindTile({0, 0}); @@ -153,11 +156,11 @@ void TestSeamsAndBrushes() { Check(terrain.RevertChange(smooth_change), "smooth snapshot reverts"); Terrain extreme(BasicDesc()); - Check(extreme.AddOrReplaceTile({std::numeric_limits::max(), 0}, flat), + Check(extreme.AddOrReplaceTile({base::MinMax::max(), 0}, flat), "extreme positive tile key is accepted"); TerrainBrush edge_brush = raise; edge_brush.center_x = static_cast( - static_cast(std::numeric_limits::max()) * 2.0 + 1.0); + static_cast(base::MinMax::max()) * 2.0 + 1.0); edge_brush.center_z = 1; edge_brush.radius = 4; Check(!extreme.ApplyBrush(edge_brush).empty(), @@ -166,14 +169,14 @@ void TestSeamsAndBrushes() { void TestNeighborInvalidation() { Terrain terrain(BasicDesc()); - const std::array left = {}; - const std::array right = {0, 1, 2, 0, 1, 2, 0, 1, 2}; + const base::Array left = {}; + const base::Array right = {0, 1, 2, 0, 1, 2, 0, 1, 2}; Check(terrain.AddOrReplaceTile({0, 0}, left), "normal-invalidation left tile is added"); Check(terrain.AddOrReplaceTile({1, 0}, right), "normal-invalidation right tile is added"); const u64 revision = terrain.FindTile({0, 0})->revision; - const std::array changed_interior = + const base::Array changed_interior = {0, 8, 9, 0, 8, 9, 0, 8, 9}; Check(terrain.AddOrReplaceTile({1, 0}, changed_interior), "normal-invalidation neighbor is replaced"); @@ -185,8 +188,8 @@ void TestDiagonalAndNegativeInvalidation() { // Negative-key cardinal border: adding {0,0} after {-1,0} synchronizes the // shared column through the FloorDiv/PositiveMod signed-coordinate paths. Terrain terrain(BasicDesc()); - const std::array west = {1, 2, 3, 1, 2, 3, 1, 2, 3}; - const std::array flat = {}; + const base::Array west = {1, 2, 3, 1, 2, 3, 1, 2, 3}; + const base::Array flat = {}; Check(terrain.AddOrReplaceTile({-1, 0}, west), "negative west tile is added"); Check(terrain.AddOrReplaceTile({0, 0}, flat), "origin tile is added"); const TerrainTile *west_tile = terrain.FindTile({-1, 0}); @@ -204,7 +207,7 @@ void TestDiagonalAndNegativeInvalidation() { // normal reads the new tile's samples (1,0)/(0,1) through GridHeight. Check(terrain.AddOrReplaceTile({-1, -1}, flat), "diagonal tile is added"); const u64 diagonal_revision = terrain.FindTile({-1, -1})->revision; - const std::array edge_changed = {0, 5, 0, 5, 0, 0, 0, 0, 0}; + const base::Array edge_changed = {0, 5, 0, 5, 0, 0, 0, 0, 0}; Check(terrain.AddOrReplaceTile({0, 0}, edge_changed), "origin tile is replaced with the corner sample unchanged"); Check(terrain.FindTile({-1, -1})->revision > diagonal_revision, @@ -237,9 +240,9 @@ void TestDiagonalAndNegativeInvalidation() { void TestMeshAndRaycast() { TerrainDesc desc = BasicDesc(); Terrain terrain(desc); - const std::array left = {0, 1, 4, 0, 1, 4, 0, 1, 4}; - const std::array right = {4, 9, 16, 4, 9, 16, 4, 9, 16}; - std::array blend; + const base::Array left = {0, 1, 4, 0, 1, 4, 0, 1, 4}; + const base::Array right = {4, 9, 16, 4, 9, 16, 4, 9, 16}; + base::Array blend; blend.fill(TerrainWeights{{128, 127, 0, 0}}); Check(terrain.AddOrReplaceTile({0, 0}, left, blend), "mesh source tile is added"); @@ -247,7 +250,7 @@ void TestMeshAndRaycast() { "normal neighbor tile is added"); const asset::AssetId material{77}; - const std::optional mesh = + const base::Optional mesh = terrain.BuildTileMesh({0, 0}, material); Check(mesh.has_value(), "tile mesh builds"); if (mesh) { @@ -277,21 +280,21 @@ void TestMeshAndRaycast() { "layer debug colors are blended into vertex colors"); Near(mesh->bounds_center[0], 1, "mesh bounds center x is tile-local"); Near(mesh->bounds_center[1], 2, "mesh bounds center includes height range"); - Near(mesh->bounds_radius, std::sqrt(6.0f), + Near(mesh->bounds_radius, ::sqrt(6.0f), "mesh sphere contains xz and height extents"); } Terrain ray_terrain(BasicDesc()); - const std::array plane = {0, 1, 2, 2, 3, 4, 4, 5, 6}; + const base::Array plane = {0, 1, 2, 2, 3, 4, 4, 5, 6}; Check(ray_terrain.AddOrReplaceTile({0, 0}, plane), "raycast plane is added"); - const std::optional sampled = ray_terrain.SampleHeight(0.25f, 0.25f); + const base::Optional sampled = ray_terrain.SampleHeight(0.25f, 0.25f); Check(sampled.has_value(), "world height samples inside a sparse tile"); if (sampled) Near(*sampled, 0.75f, "height sampling follows the mesh triangles"); Check(!ray_terrain.SampleHeight(10, 10).has_value(), "height sampling reports sparse holes"); - const std::optional hit = + const base::Optional hit = ray_terrain.Raycast({0.25f, 10, 0.25f}, {0, -2, 0}, 20); Check(hit.has_value(), "vertical terrain ray hits"); if (hit) { @@ -308,7 +311,7 @@ void TestMeshAndRaycast() { "tile AABB broad phase rejects a ray over a sparse hole"); Check(!ray_terrain .Raycast({0.25f, 10, 0.25f}, - {std::numeric_limits::denorm_min(), 0, 0}, 20) + {FLT_TRUE_MIN, 0, 0}, 20) .has_value(), "subnormal ray directions cannot produce a non-finite hit"); @@ -326,8 +329,8 @@ void TestStreaming() { desc.origin = {100, 10, -50}; desc.sample_spacing = 2; Terrain terrain(desc); - const std::array near_heights = {-2, 0, 1, 0, 1, 2, 1, 2, 3}; - const std::array far_heights = {}; + const base::Array near_heights = {-2, 0, 1, 0, 1, 2, 1, 2, 3}; + const base::Array far_heights = {}; Check(terrain.AddOrReplaceTile({2, 0}, far_heights), "far stream tile is added"); Check(terrain.AddOrReplaceTile({-1, 0}, near_heights), @@ -335,7 +338,7 @@ void TestStreaming() { Check(terrain.tiles()[0].key == TerrainTileKey{-1, 0}, "terrain keeps signed tile keys sorted"); - const std::optional region = + const base::Optional region = terrain.TileRegion({-1, 0}, 1, 7); Check(region.has_value(), "terrain tile produces a stream region"); if (region) { @@ -378,24 +381,17 @@ void TestStreaming() { "swept stream query conservatively gathers crossed tiles"); } -std::vector ReadBytes(const std::string &path) { - std::ifstream input(path, std::ios::binary | std::ios::ate); - if (!input) - return {}; - const std::streamsize size = input.tellg(); - input.seekg(0); - std::vector bytes(static_cast(size)); - input.read(reinterpret_cast(bytes.data()), size); +base::Vector ReadBytes(const base::String &path) { + base::Vector bytes; + fs::ReadFile(path, &bytes); return bytes; } -void WriteBytes(const std::string &path, const std::vector &bytes) { - std::ofstream output(path, std::ios::binary | std::ios::trunc); - output.write(reinterpret_cast(bytes.data()), - static_cast(bytes.size())); +void WriteBytes(const base::String &path, const base::Vector &bytes) { + fs::WriteFile(path, bytes); } -u64 Checksum(const std::vector &bytes, size_t size) { +u64 Checksum(const base::Vector &bytes, size_t size) { u64 hash = 0xcbf29ce484222325ull; for (size_t i = 0; i < size; ++i) { hash ^= bytes[i]; @@ -404,32 +400,31 @@ u64 Checksum(const std::vector &bytes, size_t size) { return hash; } -void StoreU64(std::vector *bytes, size_t offset, u64 value) { +void StoreU64(base::Vector *bytes, size_t offset, u64 value) { for (u32 shift = 0; shift < 64; shift += 8) { (*bytes)[offset + shift / 8] = static_cast(value >> shift); } } void TestSerialization() { - const std::filesystem::path root = - std::filesystem::temp_directory_path() / - ("rx-terrain-test-" + std::to_string(std::random_device{}())); - std::filesystem::create_directories(root); - const std::string first_path = (root / "a.rxterrain").string(); - const std::string second_path = (root / "b.rxterrain").string(); - const std::string loaded_path = (root / "loaded.rxterrain").string(); - const std::string loaded_again_path = - (root / "loaded-again.rxterrain").string(); - const std::string corrupt_path = (root / "corrupt.rxterrain").string(); - const std::string truncated_path = (root / "truncated.rxterrain").string(); - const std::string nonfinite_path = (root / "nonfinite.rxterrain").string(); - const std::string zero_id_path = (root / "zero-id.rxterrain").string(); - const std::array paths = { + const base::String root = + fs::Join(fs::TempDirectory(), "rx-terrain-test-" + rx::ToString(base::RandomUint())); + fs::CreateDirectories(root); + const base::String first_path = (fs::Join(root, "a.rxterrain")); + const base::String second_path = (fs::Join(root, "b.rxterrain")); + const base::String loaded_path = (fs::Join(root, "loaded.rxterrain")); + const base::String loaded_again_path = + (fs::Join(root, "loaded-again.rxterrain")); + const base::String corrupt_path = (fs::Join(root, "corrupt.rxterrain")); + const base::String truncated_path = (fs::Join(root, "truncated.rxterrain")); + const base::String nonfinite_path = (fs::Join(root, "nonfinite.rxterrain")); + const base::String zero_id_path = (fs::Join(root, "zero-id.rxterrain")); + const base::Array paths = { first_path, second_path, loaded_path, loaded_again_path, corrupt_path, truncated_path, nonfinite_path, zero_id_path}; - for (const std::string &path : paths) { - std::filesystem::remove(path); - std::filesystem::remove(path + ".tmp"); + for (const base::String &path : paths) { + fs::Remove(path); + fs::Remove(path + ".tmp"); } TerrainDesc desc = BasicDesc(); @@ -440,10 +435,10 @@ void TestSerialization() { desc.layers[1].albedo = asset::AssetId{21}; desc.layers[1].normal = asset::AssetId{22}; Terrain terrain(desc); - const std::array a = {-10.25f, -2, 0.5f, 1.25f, 3, + const base::Array a = {-10.25f, -2, 0.5f, 1.25f, 3, 8.75f, 9, 12, 20.5f}; - const std::array b = {7, 6, 5, 4, 3, 2, 1, 0, -1}; - std::array weights; + const base::Array b = {7, 6, 5, 4, 3, 2, 1, 0, -1}; + base::Array weights; for (u32 i = 0; i < weights.size(); ++i) { weights[i] = TerrainWeights{{static_cast(255 - i), static_cast(i), 0, 0}}; @@ -453,17 +448,17 @@ void TestSerialization() { Check(terrain.AddOrReplaceTile({-2, 3}, a, weights), "serialization tile a is added"); - std::string error; + base::String error; Check(SaveTerrain(terrain, first_path, &error), "terrain saves to rxterrain"); Check(SaveTerrain(terrain, first_path, &error), "terrain atomically replaces an existing destination"); Check(SaveTerrain(terrain, second_path, &error), "terrain saves a second time"); - const std::vector first_bytes = ReadBytes(first_path); - const std::vector second_bytes = ReadBytes(second_path); + const base::Vector first_bytes = ReadBytes(first_path); + const base::Vector second_bytes = ReadBytes(second_path); Check(!first_bytes.empty() && first_bytes == second_bytes, "saving unchanged terrain produces deterministic bytes"); - Check(!std::filesystem::exists(first_path + ".tmp"), + Check(!fs::Exists(first_path + ".tmp"), "successful save leaves no temporary file"); Terrain loaded; @@ -493,7 +488,7 @@ void TestSerialization() { ReadBytes(loaded_path) == ReadBytes(loaded_again_path), "a loaded terrain also saves deterministically"); - std::vector corrupt = first_bytes; + base::Vector corrupt = first_bytes; if (!corrupt.empty()) corrupt[0] ^= 0xff; WriteBytes(corrupt_path, corrupt); @@ -501,16 +496,16 @@ void TestSerialization() { Check(!LoadTerrain(corrupt_path, &rejected, &error), "corrupt terrain is rejected"); - std::vector truncated = first_bytes; + base::Vector truncated = first_bytes; if (truncated.size() > 3) truncated.resize(truncated.size() - 3); WriteBytes(truncated_path, truncated); Check(!LoadTerrain(truncated_path, &rejected, &error), "truncated terrain is rejected"); - std::vector nonfinite = first_bytes; + base::Vector nonfinite = first_bytes; if (nonfinite.size() >= 44) { - const u32 infinity = std::bit_cast(INFINITY); + const u32 infinity = rx::BitCast(INFINITY); for (u32 shift = 0; shift < 32; shift += 8) { nonfinite[36 + shift / 8] = static_cast(infinity >> shift); } @@ -521,7 +516,7 @@ void TestSerialization() { Check(!LoadTerrain(nonfinite_path, &rejected, &error), "non-finite terrain metadata is rejected even with a valid checksum"); - std::vector zero_id = first_bytes; + base::Vector zero_id = first_bytes; if (zero_id.size() >= 20) { StoreU64(&zero_id, 12, 0); StoreU64(&zero_id, zero_id.size() - 8, @@ -531,11 +526,11 @@ void TestSerialization() { Check(!LoadTerrain(zero_id_path, &rejected, &error), "a zero terrain identity is rejected even with a valid checksum"); - for (const std::string &path : paths) { - std::filesystem::remove(path); - std::filesystem::remove(path + ".tmp"); + for (const base::String &path : paths) { + fs::Remove(path); + fs::Remove(path + ".tmp"); } - std::filesystem::remove(root); + fs::Remove(root); } } // namespace @@ -548,9 +543,9 @@ int main() { TestStreaming(); TestSerialization(); if (failures != 0) { - std::fprintf(stderr, "terrain_test: %d failure(s)\n", failures); + ::fprintf(stderr, "terrain_test: %d failure(s)\n", failures); return 1; } - std::printf("terrain_test: PASS\n"); + ::printf("terrain_test: PASS\n"); return 0; } diff --git a/test/thunder_synth_test.cc b/test/thunder_synth_test.cc index 120bff21..0d837e7c 100644 --- a/test/thunder_synth_test.cc +++ b/test/thunder_synth_test.cc @@ -1,9 +1,10 @@ #include "audio/thunder_synth.h" +#include "base/containers/vector.h" +#include "base/numeric_limits.h" -#include -#include -#include -#include +#include +#include +#include namespace { @@ -13,13 +14,13 @@ int failures = 0; void Check(bool condition, const char *message) { if (condition) return; - std::fprintf(stderr, "thunder_synth_test: FAIL: %s\n", message); + ::fprintf(stderr, "thunder_synth_test: FAIL: %s\n", message); ++failures; } -std::vector Decode(audio::Decoder &decoder, u32 block_size) { - std::vector samples; - std::vector block(block_size); +base::Vector Decode(audio::Decoder &decoder, u32 block_size) { + base::Vector samples; + base::Vector block(block_size); for (;;) { u32 read = decoder.Read(block.data(), block_size); if (read == 0) break; @@ -30,12 +31,12 @@ std::vector Decode(audio::Decoder &decoder, u32 block_size) { void TestInvalidInputs() { Check(!audio::MakeThunder(0, 1, 1.0f, 100.0f), "zero sample rate is rejected"); - Check(!audio::MakeThunder(std::numeric_limits::max(), 1, 1.0f, 100.0f), + Check(!audio::MakeThunder(base::MinMax::max(), 1, 1.0f, 100.0f), "unreasonable sample rates are rejected"); - Check(!audio::MakeThunder(48000, 1, std::numeric_limits::quiet_NaN(), 100.0f), + Check(!audio::MakeThunder(48000, 1, NAN, 100.0f), "non-finite energy is rejected"); Check(!audio::MakeThunder(48000, 1, 1.0f, - std::numeric_limits::infinity()), + INFINITY), "non-finite distance is rejected"); } @@ -44,19 +45,19 @@ void TestFiniteDeterministicStream() { auto b = audio::MakeThunder(12000, 0x1234u, 0.8f, 750.0f); Check(a && b, "valid thunder decoders are created"); if (!a || !b) return; - std::vector lhs = Decode(*a, 127); - std::vector rhs = Decode(*b, 509); + base::Vector lhs = Decode(*a, 127); + base::Vector rhs = Decode(*b, 509); Check(lhs == rhs, "stream output is deterministic across mixer block sizes"); Check(lhs.size() == a->frame_count(), "decoder emits its advertised frame count"); bool finite = true; bool non_silent = false; for (float sample : lhs) { - finite &= std::isfinite(sample) && std::abs(sample) <= 0.951f; - non_silent |= std::abs(sample) > 1e-4f; + finite &= ::isfinite(sample) && ::abs(sample) <= 0.951f; + non_silent |= ::abs(sample) > 1e-4f; } Check(finite, "all samples remain finite and bounded"); Check(non_silent, "positive energy produces an audible signal"); - Check(!lhs.empty() && std::abs(lhs.back()) < 1e-4f, + Check(!lhs.empty() && ::abs(lhs.back()) < 1e-4f, "the end-of-stream fade reaches silence"); } @@ -64,7 +65,7 @@ void TestZeroEnergyIsSilent() { auto decoder = audio::MakeThunder(8000, 4u, 0.0f, 0.0f); Check(static_cast(decoder), "zero energy still yields a finite decoder"); if (!decoder) return; - std::vector samples = Decode(*decoder, 256); + base::Vector samples = Decode(*decoder, 256); bool silent = true; for (float sample : samples) silent &= sample == 0.0f; @@ -78,9 +79,9 @@ int main() { TestFiniteDeterministicStream(); TestZeroEnergyIsSilent(); if (failures == 0) { - std::printf("thunder_synth_test: OK\n"); + ::printf("thunder_synth_test: OK\n"); return 0; } - std::fprintf(stderr, "thunder_synth_test: %d failure(s)\n", failures); + ::fprintf(stderr, "thunder_synth_test: %d failure(s)\n", failures); return failures; } diff --git a/test/upload_batch_test.cc b/test/upload_batch_test.cc index 6a12ab0c..42fa83e6 100644 --- a/test/upload_batch_test.cc +++ b/test/upload_batch_test.cc @@ -8,14 +8,17 @@ // ParkBatchStaging with an image, as MaterialSystem uploads textures. // Skips cleanly (exit 0) when no Vulkan driver is present (null backend). -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "base/memory/unique_pointer.h" +#include "base/threading/condition_variable.h" +#include "base/threading/lock_guard.h" +#include "base/threading/mutex.h" +#include "base/threading/thread.h" #include "render/pipeline/material_system.h" #include "render/rhi/command_list.h" #include "render/rhi/device.h" @@ -26,13 +29,36 @@ namespace asset = rx::asset; namespace { int Fail(const char* msg) { - std::fprintf(stderr, "upload_batch_test: FAIL: %s\n", msg); + ::fprintf(stderr, "upload_batch_test: FAIL: %s\n", msg); return 1; } +// std::barrier(2)'s reusable phase: each ArriveAndWait blocks until both +// parties of the current phase arrived. +class TwoPartyBarrier { + public: + void ArriveAndWait() { + base::UniqueLock lock(mutex_); + const u64 phase = phase_; + if (++arrived_ == 2) { + arrived_ = 0; + ++phase_; + cv_.NotifyAll(); + return; + } + cv_.Wait(lock, [&] { return phase_ != phase; }); + } + + private: + base::Mutex mutex_; + base::ConditionVariable cv_; + int arrived_ = 0; + u64 phase_ = 0; +}; + // Uploads `bytes` of a deterministic per-buffer pattern and returns the device // buffer (created batched when a batch is open). -GpuBuffer UploadPattern(Device& device, u64 bytes, u8 seed, std::vector& expect) { +GpuBuffer UploadPattern(Device& device, u64 bytes, u8 seed, base::Vector& expect) { expect.resize(bytes); for (u64 i = 0; i < bytes; ++i) expect[i] = static_cast(seed + i * 31); return device.CreateBufferWithData(rx::ByteSpan(expect.data(), expect.size()), @@ -40,7 +66,7 @@ GpuBuffer UploadPattern(Device& device, u64 bytes, u8 seed, std::vector& exp } // Copies `buffer` back to the host and compares against `expect`. -bool VerifyContents(Device& device, const GpuBuffer& buffer, const std::vector& expect) { +bool VerifyContents(Device& device, const GpuBuffer& buffer, const base::Vector& expect) { GpuBuffer readback = device.CreateBuffer(expect.size(), kBufferUsageTransferDst, /*host_visible=*/true); if (!readback || !readback.mapped) return false; @@ -49,7 +75,7 @@ bool VerifyContents(Device& device, const GpuBuffer& buffer, const std::vector device = Device::CreateOffscreen(desc); + desc.enable_validation = ::getenv("RX_VALIDATION") != nullptr; + base::UniquePointer device = Device::CreateOffscreen(desc); if (!device) return Fail("CreateOffscreen returned null"); if (device->is_stub()) { - std::printf("upload_batch_test: no vulkan driver, skipping (null backend)\n"); + ::printf("upload_batch_test: no vulkan driver, skipping (null backend)\n"); return 0; } - std::printf("upload_batch_test: device '%s'\n", device->caps().adapter_name.c_str()); + ::printf("upload_batch_test: device '%s'\n", device->caps().adapter_name.c_str()); // batched uploads, nested scopes, verified after the outermost flush device->BeginUploadBatch(); - std::vector expect_a, expect_b; + base::Vector expect_a, expect_b; GpuBuffer a = UploadPattern(*device, 64 * 1024, 1, expect_a); device->BeginUploadBatch(); // nested: must not submit at the inner flush GpuBuffer b = UploadPattern(*device, 3 * 1024 + 7, 2, expect_b); @@ -85,7 +111,7 @@ int main() { // implicit flush: ImmediateSubmit must see a still-batched buffer device->BeginUploadBatch(); - std::vector expect_c; + base::Vector expect_c; GpuBuffer c = UploadPattern(*device, 16 * 1024, 3, expect_c); if (!c) return Fail("batched buffer creation failed (implicit-flush case)"); // VerifyContents runs an ImmediateSubmit while the batch is still open; the @@ -97,7 +123,7 @@ int main() { // every buffer must still verify (early chunks and the final flush alike) --- constexpr u64 kChunk = 24ull << 20; // 3 x 24 MiB crosses the 64 MiB budget device->BeginUploadBatch(); - std::vector expect_big[3]; + base::Vector expect_big[3]; GpuBuffer big[3]; for (int i = 0; i < 3; ++i) { big[i] = UploadPattern(*device, kChunk, static_cast(10 + i), expect_big[i]); @@ -114,7 +140,7 @@ int main() { // transfer->all barrier are the only things ordering the copy against the // frame, which is what streaming mid-frame relies on. --- device->BeginUploadBatch(); - std::vector expect_d; + base::Vector expect_d; GpuBuffer d = UploadPattern(*device, 128 * 1024, 4, expect_d); if (!d) return Fail("batched buffer creation failed (frame case)"); GpuBuffer frame_readback = @@ -127,7 +153,7 @@ int main() { device->SubmitFrame(frame); device->WaitIdle(); device->InvalidateBuffer(frame_readback, 0, expect_d.size()); - if (std::memcmp(frame_readback.mapped, expect_d.data(), expect_d.size()) != 0) + if (base::MemCompare(frame_readback.mapped, expect_d.data(), expect_d.size()) != 0) return Fail("frame read a batched buffer before its copy landed"); device->FlushUploadBatch(); @@ -135,7 +161,7 @@ int main() { // does it. The staging must survive to the flush and the image must come out // in kShaderReadAll with the right pixels. --- constexpr u32 kDim = 64; - std::vector expect_tex(kDim * kDim * 4); + base::Vector expect_tex(kDim * kDim * 4); for (size_t i = 0; i < expect_tex.size(); ++i) expect_tex[i] = static_cast(i * 7 + 3); device->BeginUploadBatch(); GpuImage image = device->CreateImage2D( @@ -145,22 +171,22 @@ int main() { GpuBuffer tex_staging = device->CreateBuffer(expect_tex.size(), kBufferUsageTransferSrc, /*host_visible=*/true); if (!tex_staging.mapped) return Fail("texture staging creation failed"); - std::memcpy(tex_staging.mapped, expect_tex.data(), expect_tex.size()); + base::MemCopy(tex_staging.mapped, expect_tex.data(), expect_tex.size()); device->FlushBuffer(tex_staging, 0, expect_tex.size()); if (!device->UploadBatchActive()) return Fail("batch not active for the texture case"); device->RecordUpload([&](CommandList& cmd) { cmd.Barrier(Transition(image, ResourceState::kUndefined, ResourceState::kCopyDst)); BufferTextureCopy region{.mip = 0, .extent = {kDim, kDim}}; - cmd.CopyBufferToTexture(tex_staging, image, {®ion, 1}); + cmd.CopyBufferToTexture(tex_staging, image, base::Span(®ion, 1)); cmd.Barrier(Transition(image, ResourceState::kCopyDst, ResourceState::kShaderReadAll)); }); device->ParkBatchStaging(tex_staging); if (tex_staging) return Fail("ParkBatchStaging left the caller's handle live"); device->FlushUploadBatch(); - std::vector got_tex(expect_tex.size()); + base::Vector got_tex(expect_tex.size()); if (!device->ReadbackImage(image, ResourceState::kShaderReadAll, got_tex.data(), got_tex.size())) return Fail("ReadbackImage failed"); - if (std::memcmp(got_tex.data(), expect_tex.data(), expect_tex.size()) != 0) + if (base::MemCompare(got_tex.data(), expect_tex.data(), expect_tex.size()) != 0) return Fail("batched texture contents wrong after flush"); device->DestroyImage(image); @@ -189,23 +215,23 @@ int main() { buffer = device->CreateBuffer(16, kBufferUsageStorage, true); if (!buffer) return Fail("CreateBuffer returned null (off-thread destroy case)"); } - std::barrier start(2); - std::jthread retire([&] { + TwoPartyBarrier start; + base::Thread retire("retire", [&] { for (GpuBuffer& buffer : race_buffers) { - start.arrive_and_wait(); + start.ArriveAndWait(); device->DestroyBufferDeferred(buffer); } - }); + }, /*start_now=*/true); bool race_frames_ok = true; for (int i = 0; i < kRaceIterations; ++i) { - start.arrive_and_wait(); // every iteration must arrive, or the worker hangs + start.ArriveAndWait(); // every iteration must arrive, or the worker hangs if (CommandList* frame_cmd = device->BeginFrame(i % Device::kMaxFramesInFlight)) { device->SubmitFrame(frame_cmd); } else { race_frames_ok = false; } } - retire.join(); + retire.Join(); if (!race_frames_ok) return Fail("BeginFrame returned null (off-thread destroy case)"); // deferred destroy of a resource a submitted-but-unfinished batch is @@ -220,12 +246,12 @@ int main() { GpuBuffer doomed_staging = device->CreateBuffer(expect_tex.size(), kBufferUsageTransferSrc, /*host_visible=*/true); if (!doomed_staging.mapped) return Fail("staging creation failed (deferred-destroy case)"); - std::memcpy(doomed_staging.mapped, expect_tex.data(), expect_tex.size()); + base::MemCopy(doomed_staging.mapped, expect_tex.data(), expect_tex.size()); device->FlushBuffer(doomed_staging, 0, expect_tex.size()); device->RecordUpload([&](CommandList& cmd) { cmd.Barrier(Transition(doomed, ResourceState::kUndefined, ResourceState::kCopyDst)); BufferTextureCopy region{.mip = 0, .extent = {kDim, kDim}}; - cmd.CopyBufferToTexture(doomed_staging, doomed, {®ion, 1}); + cmd.CopyBufferToTexture(doomed_staging, doomed, base::Span(®ion, 1)); cmd.Barrier(Transition(doomed, ResourceState::kCopyDst, ResourceState::kShaderReadAll)); }); device->ParkBatchStaging(doomed_staging); @@ -243,6 +269,6 @@ int main() { for (GpuBuffer* buffer : {&a, &b, &c, &d, &frame_readback, &big[0], &big[1], &big[2]}) device->DestroyBuffer(*buffer); device->WaitIdle(); - std::printf("upload_batch_test: PASS\n"); + ::printf("upload_batch_test: PASS\n"); return 0; } diff --git a/test/upscaler_motion_test.cc b/test/upscaler_motion_test.cc index e27f7ded..80130ff2 100644 --- a/test/upscaler_motion_test.cc +++ b/test/upscaler_motion_test.cc @@ -1,13 +1,13 @@ -#include -#include -#include -#include -#include +#include +#include +#include +#include #include "render/post/antialiasing.h" #include "render/post/upscaler.h" #include "render/rhi/device.h" #if defined(RX_HAS_DLSS) && !defined(__aarch64__) +#include "base/containers/vector.h" #include "render/gi/denoiser_rr.h" #endif @@ -17,12 +17,12 @@ using namespace rx::render; namespace { constexpr u32 kW = 96, kH = 64, kOutW = 192, kOutH = 128; float Pattern(float x, float y, u32 channel) { - if (channel == 0) return .5f + .4f * std::sin(x * 1.1f); - if (channel == 1) return .5f + .4f * std::sin(y * .9f); - return .5f + .3f * std::sin(x * .6f + y * .7f); + if (channel == 0) return .5f + .4f * ::sin(x * 1.1f); + if (channel == 1) return .5f + .4f * ::sin(y * .9f); + return .5f + .3f * ::sin(x * .6f + y * .7f); } float Half(u16 bits) { - return std::ldexp(float((bits & 1023) | 1024), int((bits >> 10) & 31) - 25) * + return ::ldexp(float((bits & 1023) | 1024), int((bits >> 10) & 31) - 25) * ((bits & 0x8000) ? -1.f : 1.f); } } // namespace @@ -33,14 +33,14 @@ int main() { desc.enable_validation = true; auto device = Device::CreateOffscreen(desc); if (!device || device->is_stub()) { - std::printf("upscaler_motion_test: SKIP, GPU unavailable\n"); + ::printf("upscaler_motion_test: SKIP, GPU unavailable\n"); return 77; } - const char* name = std::getenv("RX_TEST_UPSCALER"); - const UpscalerKind kind = name && std::strcmp(name, "dlss") == 0 ? UpscalerKind::kDlss : UpscalerKind::kFsr3; + const char* name = ::getenv("RX_TEST_UPSCALER"); + const UpscalerKind kind = name && ::strcmp(name, "dlss") == 0 ? UpscalerKind::kDlss : UpscalerKind::kFsr3; auto upscaler = CreateUpscaler({.kind = kind, .render_width = kW, .render_height = kH, .output_width = kOutW, .output_height = kOutH}, *device); - if (!upscaler) { std::printf("upscaler_motion_test: SKIP, backend unavailable\n"); return 77; } + if (!upscaler) { ::printf("upscaler_motion_test: SKIP, backend unavailable\n"); return 77; } #if defined(RX_HAS_DLSS) && !defined(__aarch64__) if (kind == UpscalerKind::kDlss) { RrDenoiser rr; @@ -60,14 +60,14 @@ int main() { if (!color || !depth || !motion) return 1; ResourceState color_state = ResourceState::kUndefined, depth_state = ResourceState::kUndefined, motion_state = ResourceState::kUndefined; - std::vector colors(kW * kH * 4), depths(kW * kH, .1f), motions(kW * kH * 2, 0); - auto upload = [&](GpuImage image, ResourceState& state, const std::vector& data) { + base::Vector colors(kW * kH * 4), depths(kW * kH, .1f), motions(kW * kH * 2, 0); + auto upload = [&](GpuImage image, ResourceState& state, const base::Vector& data) { GpuBuffer staging = device->CreateBufferWithData( - {reinterpret_cast(data.data()), data.size() * sizeof(f32)}, kBufferUsageTransferSrc); + ByteSpan(reinterpret_cast(data.data()), data.size() * sizeof(f32)), kBufferUsageTransferSrc); device->ImmediateSubmit([&](CommandList& cmd) { cmd.Barrier(Transition(image, state, ResourceState::kCopyDst)); BufferTextureCopy copy{.extent = {kW, kH}}; - cmd.CopyBufferToTexture(staging, image, {©, 1}); + cmd.CopyBufferToTexture(staging, image, base::Span(©, 1)); }); state = ResourceState::kCopyDst; device->DestroyBuffer(staging); @@ -104,21 +104,21 @@ int main() { graph.AddPass("readback_state", [output](RenderGraph::PassBuilder& b) { b.Read(output, ResourceUsage::kResolveSrc); }, [](PassContext&) {}); - if (!graph.Compile(*device, pool)) std::exit(1); + if (!graph.Compile(*device, pool)) ::exit(1); device->ImmediateSubmit([&](CommandList& cmd) { - PassContext ctx{.cmd = &cmd, .device = device.get(), .graph = &graph}; + PassContext ctx{.cmd = &cmd, .device = device.Get_UseOnlyIfYouKnowWhatYouareDoing(), .graph = &graph}; graph.Execute(ctx); }); if (frame >= 24) { - std::vector pixels(kOutW * kOutH * 4); + base::Vector pixels(kOutW * kOutH * 4); if (!device->ReadbackImage(graph.image(output), ResourceState::kResolveSrc, - pixels.data(), pixels.size() * sizeof(u16))) std::exit(1); + pixels.data(), pixels.size() * sizeof(u16))) ::exit(1); for (u32 y = 16; y < kOutH - 16; ++y) for (u32 x = 16; x < kOutW - 16; ++x) for (u32 c = 0; c < 3; ++c) { const u16 bits = pixels[4 * (y * kOutW + x) + c]; if ((bits & 0x7c00u) == 0x7c00u) { - std::printf("FAIL: upscaler produced non-finite color\n"); - std::exit(1); + ::printf("FAIL: upscaler produced non-finite color\n"); + ::exit(1); } double delta = Half(bits) - Pattern((x + .5f) * .5f - velocity * frame, (y + .5f) * .5f, c); @@ -132,16 +132,16 @@ int main() { const double negative = measure(-1, 0, 0); const double moving_correct = measure(1, .75f, -1); const double moving_wrong = measure(1, .75f, 1); - std::printf("upscaler_motion_test %s: jitter positive MSE=%g negative MSE=%g; motion correct=%g reversed=%g\n", + ::printf("upscaler_motion_test %s: jitter positive MSE=%g negative MSE=%g; motion correct=%g reversed=%g\n", UpscalerName(kind), positive, negative, moving_correct, moving_wrong); const double resumed = measure(1, 0, 0, 1); const double reset = measure(1, 0, 0, 2); - std::printf("skipped-frame reset MSE=%g explicit-reset MSE=%g\n", resumed, reset); + ::printf("skipped-frame reset MSE=%g explicit-reset MSE=%g\n", resumed, reset); device->WaitIdle(); - upscaler.reset(); + upscaler.Reset(); pool.Clear(); device->DestroyImage(color); device->DestroyImage(depth); device->DestroyImage(motion); - return positive < negative && moving_correct < moving_wrong && std::abs(resumed - reset) < 1e-6 ? 0 : 1; + return positive < negative && moving_correct < moving_wrong && ::abs(resumed - reset) < 1e-6 ? 0 : 1; } diff --git a/test/usd_scene_test.cc b/test/usd_scene_test.cc index faf64846..7a5bffec 100644 --- a/test/usd_scene_test.cc +++ b/test/usd_scene_test.cc @@ -1,8 +1,9 @@ -#include -#include -#include +#include +#include #include "asset/usd_loader.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" namespace { @@ -174,20 +175,20 @@ int failures = 0; void Check(bool condition, const char *message) { if (condition) return; - std::fprintf(stderr, "usd_scene_test: FAIL: %s\n", message); + ::fprintf(stderr, "usd_scene_test: FAIL: %s\n", message); ++failures; } -bool Write(const std::filesystem::path &path, const char *text, size_t size) { - std::FILE *file = std::fopen(path.string().c_str(), "wb"); +bool Write(const base::String &path, const char *text, size_t size) { + FILE *file = ::fopen(path.c_str(), "wb"); if (!file) return false; - const bool ok = std::fwrite(text, 1, size, file) == size; - std::fclose(file); + const bool ok = ::fwrite(text, 1, size, file) == size; + ::fclose(file); return ok; } -bool Near(f32 value, f32 expected) { return std::fabs(value - expected) < 1e-4f; } +bool Near(f32 value, f32 expected) { return ::fabs(value - expected) < 1e-4f; } } // namespace @@ -199,22 +200,22 @@ int main() { !asset::IsUsdPath("no_extension"), "non-usd paths are not claimed"); - const std::filesystem::path dir = std::filesystem::temp_directory_path(); - const std::filesystem::path prop = dir / "rx_usd_prop.usda"; - const std::filesystem::path stage = dir / "rx_usd_stage.usda"; + const base::String dir = rx::fs::TempDirectory(); + const base::String prop = rx::fs::Join(dir, "rx_usd_prop.usda"); + const base::String stage = rx::fs::Join(dir, "rx_usd_stage.usda"); if (!Write(prop, kPropLayer, sizeof(kPropLayer) - 1) || !Write(stage, kStage, sizeof(kStage) - 1)) { - std::fprintf(stderr, "usd_scene_test: cannot create fixture\n"); + ::fprintf(stderr, "usd_scene_test: cannot create fixture\n"); return 1; } asset::ImportedScene scene; - const bool loaded = asset::LoadUsdScene(stage.string(), &scene); - std::filesystem::remove(prop); - std::filesystem::remove(stage); + const bool loaded = asset::LoadUsdScene(stage, &scene); + rx::fs::Remove(prop); + rx::fs::Remove(stage); Check(loaded, "referenced usda stage loads"); if (!loaded) { - std::fprintf(stderr, "usd_scene_test: FAIL\n"); + ::fprintf(stderr, "usd_scene_test: FAIL\n"); return 1; } @@ -259,15 +260,15 @@ int main() { // OpenPBR: tydra parses it into a separate slot on RenderMaterial, so the // importer has to reach for it instead of falling back to engine defaults. - const std::filesystem::path openpbr_stage = dir / "rx_usd_openpbr.usda"; + const base::String openpbr_stage = rx::fs::Join(dir, "rx_usd_openpbr.usda"); if (!Write(openpbr_stage, kOpenPbrStage, sizeof(kOpenPbrStage) - 1)) { - std::fprintf(stderr, "usd_scene_test: cannot create openpbr fixture\n"); + ::fprintf(stderr, "usd_scene_test: cannot create openpbr fixture\n"); return 1; } asset::ImportedScene openpbr_scene; const bool openpbr_loaded = - asset::LoadUsdScene(openpbr_stage.string(), &openpbr_scene); - std::filesystem::remove(openpbr_stage); + asset::LoadUsdScene(openpbr_stage, &openpbr_scene); + rx::fs::Remove(openpbr_stage); Check(openpbr_loaded, "an OpenPBR stage loads"); if (openpbr_loaded && openpbr_scene.materials.size() == 1) { @@ -301,18 +302,18 @@ int main() { Check(false, "the OpenPBR stage yields exactly one material"); } - const std::filesystem::path textured_stage = dir / "rx_usd_textured.usda"; - const std::filesystem::path grey = dir / "rx_usd_grey.png"; + const base::String textured_stage = rx::fs::Join(dir, "rx_usd_textured.usda"); + const base::String grey = rx::fs::Join(dir, "rx_usd_grey.png"); if (!Write(textured_stage, kTexturedStage, sizeof(kTexturedStage) - 1) || !Write(grey, kGreyPng, sizeof(kGreyPng) - 1)) { - std::fprintf(stderr, "usd_scene_test: cannot create textured fixture\n"); + ::fprintf(stderr, "usd_scene_test: cannot create textured fixture\n"); return 1; } asset::ImportedScene textured_scene; const bool textured_loaded = - asset::LoadUsdScene(textured_stage.string(), &textured_scene); - std::filesystem::remove(textured_stage); - std::filesystem::remove(grey); + asset::LoadUsdScene(textured_stage, &textured_scene); + rx::fs::Remove(textured_stage); + rx::fs::Remove(grey); Check(textured_loaded, "a textured UsdPreviewSurface stage loads"); if (textured_loaded && textured_scene.materials.size() == 1) { const asset::Material &m = textured_scene.materials[0]; @@ -327,7 +328,7 @@ int main() { } if (failures == 0) { - std::puts("usd_scene_test: PASS"); + ::puts("usd_scene_test: PASS"); return 0; } return 1; diff --git a/test/vehicle_audio_test.cc b/test/vehicle_audio_test.cc index cf93337d..82caa675 100644 --- a/test/vehicle_audio_test.cc +++ b/test/vehicle_audio_test.cc @@ -4,18 +4,20 @@ // the dominant frequency, that load changes the sound, that the skid layer gates // on slip, and that a mid-render parameter jump does not click. -#include -#include -#include -#include -#include +#include +#include #include "audio/aux_synth.h" #include "audio/engine_synth.h" #include "audio/mixer.h" #include "audio/synth_voice.h" #include "audio/vehicle_audio.h" +#include "base/atomic.h" +#include "base/containers/vector.h" +#include "base/memory/unique_pointer.h" +#include "base/threading/thread.h" #include "core/math.h" +#include "core/scalar.h" #include "core/types.h" using namespace rx; @@ -28,7 +30,7 @@ int failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::fprintf(stderr, "vehicle_audio_test: FAIL %s:%d: %s\n", __FILE__, __LINE__, \ + ::fprintf(stderr, "vehicle_audio_test: FAIL %s:%d: %s\n", __FILE__, __LINE__, \ #cond); \ ++failures; \ } \ @@ -36,34 +38,34 @@ int failures = 0; constexpr u32 kRate = 48000; -bool AllFinite(const std::vector& b) { +bool AllFinite(const base::Vector& b) { for (f32 v : b) - if (!std::isfinite(v)) return false; + if (!::isfinite(v)) return false; return true; } -f32 MaxAbs(const std::vector& b) { +f32 MaxAbs(const base::Vector& b) { f32 m = 0.0f; - for (f32 v : b) m = std::max(m, std::fabs(v)); + for (f32 v : b) m = rx::Max(m, ::fabs(v)); return m; } -f32 Rms(const std::vector& b) { +f32 Rms(const base::Vector& b) { f64 acc = 0.0; for (f32 v : b) acc += static_cast(v) * v; - return b.empty() ? 0.0f : static_cast(std::sqrt(acc / b.size())); + return b.empty() ? 0.0f : static_cast(::sqrt(acc / b.size())); } -f32 MaxDelta(const std::vector& b) { +f32 MaxDelta(const base::Vector& b) { f32 m = 0.0f; - for (size_t i = 1; i < b.size(); ++i) m = std::max(m, std::fabs(b[i] - b[i - 1])); + for (size_t i = 1; i < b.size(); ++i) m = rx::Max(m, ::fabs(b[i] - b[i - 1])); return m; } // One-pole low-pass in place, to isolate the firing fundamental from the noise // bed before counting zero crossings. -void LowPass(std::vector& b, f32 cutoff) { - const f32 a = 1.0f - std::exp(-6.28318531f * cutoff / kRate); +void LowPass(base::Vector& b, f32 cutoff) { + const f32 a = 1.0f - ::exp(-6.28318531f * cutoff / kRate); f32 s = 0.0f; for (f32& v : b) { s += a * (v - s); @@ -73,8 +75,8 @@ void LowPass(std::vector& b, f32 cutoff) { // Zero crossings per second of a low-passed copy: a coarse dominant-frequency // estimate (~2x the fundamental) that rises monotonically with engine speed. -f32 DominantHz(const std::vector& src) { - std::vector b = src; +f32 DominantHz(const base::Vector& src) { + base::Vector b = src; LowPass(b, 400.0f); int crossings = 0; for (size_t i = 1; i < b.size(); ++i) @@ -83,9 +85,9 @@ f32 DominantHz(const std::vector& src) { return crossings / (2.0f * seconds); } -std::vector RenderEngine(const EnginePreset& preset, const SynthParams& p, u32 frames) { +base::Vector RenderEngine(const EnginePreset& preset, const SynthParams& p, u32 frames) { EngineSynth synth(preset, kRate); - std::vector buf(frames, 0.0f); + base::Vector buf(frames, 0.0f); synth.Render(buf.data(), frames, p); return buf; } @@ -93,23 +95,23 @@ std::vector RenderEngine(const EnginePreset& preset, const SynthParams& p, // RMS of the first difference: a cheap high-frequency-energy proxy. Muffling the // output (a low-pass) drops this far more than it drops the total RMS, so their // ratio is a coarse spectral-centroid stand-in with no FFT. -f32 HighFreqRms(const std::vector& b) { +f32 HighFreqRms(const base::Vector& b) { f64 acc = 0.0; for (size_t i = 1; i < b.size(); ++i) { const f32 d = b[i] - b[i - 1]; acc += static_cast(d) * d; } - return b.size() < 2 ? 0.0f : static_cast(std::sqrt(acc / (b.size() - 1))); + return b.size() < 2 ? 0.0f : static_cast(::sqrt(acc / (b.size() - 1))); } -f32 HfFraction(const std::vector& b) { +f32 HfFraction(const base::Vector& b) { const f32 r = Rms(b); return r > 1e-9f ? HighFreqRms(b) / r : 0.0f; } // Zero crossings per second of the raw (un-low-passed) signal: rises with the // brightest tone present, so it tracks the jet whine's pitch (the roar is low). -f32 RawZeroCrossHz(const std::vector& b) { +f32 RawZeroCrossHz(const base::Vector& b) { int crossings = 0; for (size_t i = 1; i < b.size(); ++i) if ((b[i - 1] <= 0.0f) != (b[i] <= 0.0f)) ++crossings; @@ -118,14 +120,14 @@ f32 RawZeroCrossHz(const std::vector& b) { } // RMS of one channel of an interleaved stereo buffer (0 = left, 1 = right). -f32 ChannelRms(const std::vector& interleaved, int ch) { +f32 ChannelRms(const base::Vector& interleaved, int ch) { f64 acc = 0.0; size_t n = 0; for (size_t i = ch; i < interleaved.size(); i += 2) { acc += static_cast(interleaved[i]) * interleaved[i]; ++n; } - return n == 0 ? 0.0f : static_cast(std::sqrt(acc / n)); + return n == 0 ? 0.0f : static_cast(::sqrt(acc / n)); } // (a) The engine renders bounded, finite, non-silent audio. @@ -134,7 +136,7 @@ void TestEngineRendersSound() { p.rpm = 3500.0f; p.load = 0.6f; p.throttle = 0.7f; - std::vector buf = RenderEngine(V8Preset(), p, 24000); + base::Vector buf = RenderEngine(V8Preset(), p, 24000); CHECK(AllFinite(buf)); CHECK(MaxAbs(buf) > 0.02f); // audibly present CHECK(MaxAbs(buf) <= 1.001f); // bounded (soft-limited) @@ -150,7 +152,7 @@ void TestEngineRendersSound() { q.load = 0.8f; q.throttle = 0.9f; q.speed_mps = 30.0f; - std::vector b = RenderEngine(preset, q, 12000); + base::Vector b = RenderEngine(preset, q, 12000); CHECK(AllFinite(b)); CHECK(MaxAbs(b) <= 1.001f); CHECK(MaxAbs(b) > 0.01f); @@ -168,7 +170,7 @@ void TestRpmRaisesPitch() { hi.rpm = 5000.0f; const f32 f_lo = DominantHz(RenderEngine(preset, lo, 24000)); const f32 f_hi = DominantHz(RenderEngine(preset, hi, 24000)); - std::fprintf(stderr, "vehicle_audio_test: dominant Hz lo=%.1f hi=%.1f\n", f_lo, f_hi); + ::fprintf(stderr, "vehicle_audio_test: dominant Hz lo=%.1f hi=%.1f\n", f_lo, f_hi); CHECK(f_hi > f_lo * 1.5f); // clearly higher, not marginal noise } @@ -184,7 +186,7 @@ void TestLoadChangesTimbre() { heavy.throttle = 0.95f; const f32 rms_light = Rms(RenderEngine(preset, light, 24000)); const f32 rms_heavy = Rms(RenderEngine(preset, heavy, 24000)); - std::fprintf(stderr, "vehicle_audio_test: rms light=%.4f heavy=%.4f\n", rms_light, rms_heavy); + ::fprintf(stderr, "vehicle_audio_test: rms light=%.4f heavy=%.4f\n", rms_light, rms_heavy); CHECK(rms_heavy > rms_light * 1.2f); // richer + louder under load } @@ -194,7 +196,7 @@ void TestSkidGatesOnSlip() { SynthParams rolling; rolling.slip = 0.0f; rolling.speed_mps = 20.0f; - std::vector quiet(12000, 1.0f); // pre-fill non-zero to prove it clears + base::Vector quiet(12000, 1.0f); // pre-fill non-zero to prove it clears silent.Render(quiet.data(), 12000, rolling); CHECK(MaxAbs(quiet) == 0.0f); @@ -202,7 +204,7 @@ void TestSkidGatesOnSlip() { SynthParams slide; slide.slip = 1.0f; slide.speed_mps = 20.0f; - std::vector loud(12000, 0.0f); + base::Vector loud(12000, 0.0f); sliding.Render(loud.data(), 12000, slide); CHECK(AllFinite(loud)); CHECK(Rms(loud) > 0.01f); @@ -212,13 +214,13 @@ void TestSkidGatesOnSlip() { WindSynth wind(kRate); SynthParams crawl; crawl.speed_mps = 1.0f; - std::vector gentle(12000, 0.0f); + base::Vector gentle(12000, 0.0f); wind.Render(gentle.data(), 12000, crawl); SynthParams fast; fast.speed_mps = 50.0f; - std::vector gale(12000, 0.0f); + base::Vector gale(12000, 0.0f); wind.Render(gale.data(), 12000, fast); - std::fprintf(stderr, "vehicle_audio_test: wind rms crawl=%.4f fast=%.4f\n", Rms(gentle), + ::fprintf(stderr, "vehicle_audio_test: wind rms crawl=%.4f fast=%.4f\n", Rms(gentle), Rms(gale)); CHECK(Rms(gale) > Rms(gentle) * 3.0f + 0.005f); } @@ -227,7 +229,7 @@ void TestSkidGatesOnSlip() { // click threshold. The boat preset's heavy exhaust low-pass keeps the noise // floor's own sample-to-sample delta small, so a click would stand out. void TestNoClickOnParamJump() { - SynthVoice voice(kRate, std::unique_ptr(new EngineSynth(InboardBoatPreset(), kRate))); + SynthVoice voice(kRate, base::UniquePointer(new EngineSynth(InboardBoatPreset(), kRate))); SynthParams start; start.rpm = 800.0f; start.load = 0.1f; @@ -235,7 +237,7 @@ void TestNoClickOnParamJump() { voice.SetParams(start); const u32 block = 512; - std::vector buf(block, 0.0f); + base::Vector buf(block, 0.0f); f32 worst = 0.0f; // Render a few seconds, slamming the parameters to a hard jump partway. for (int blk = 0; blk < 200; ++blk) { @@ -248,9 +250,9 @@ void TestNoClickOnParamJump() { } voice.Read(buf.data(), block); CHECK(AllFinite(buf)); - worst = std::max(worst, MaxDelta(buf)); + worst = rx::Max(worst, MaxDelta(buf)); } - std::fprintf(stderr, "vehicle_audio_test: worst sample delta=%.4f\n", worst); + ::fprintf(stderr, "vehicle_audio_test: worst sample delta=%.4f\n", worst); CHECK(worst < 0.15f); // no zipper/click across the jump } @@ -269,13 +271,13 @@ void TestMixerIntegration() { st.position = Vec3{3.0f, 0.0f, -2.0f}; va.Update(st); - std::vector out(1024 * 2, 0.0f); // interleaved stereo + base::Vector out(1024 * 2, 0.0f); // interleaved stereo f32 peak = 0.0f; for (int i = 0; i < 40; ++i) { mixer.MixInto(out.data(), 1024); for (f32 v : out) { - CHECK(std::isfinite(v)); - peak = std::max(peak, std::fabs(v)); + CHECK(::isfinite(v)); + peak = rx::Max(peak, ::fabs(v)); } } CHECK(peak > 0.01f); // the vehicle is audible in the mix @@ -297,11 +299,11 @@ void TestMuffleDarkensAndDucks() { dry.throttle = 0.6f; SynthParams wet = dry; wet.muffle = 1.0f; - const std::vector a = RenderEngine(preset, dry, 24000); - const std::vector b = RenderEngine(preset, wet, 24000); + const base::Vector a = RenderEngine(preset, dry, 24000); + const base::Vector b = RenderEngine(preset, wet, 24000); const f32 rms_dry = Rms(a), rms_wet = Rms(b); const f32 hf_dry = HfFraction(a), hf_wet = HfFraction(b); - std::fprintf(stderr, "vehicle_audio_test: muffle rms dry=%.4f wet=%.4f | hf-frac dry=%.4f wet=%.4f\n", + ::fprintf(stderr, "vehicle_audio_test: muffle rms dry=%.4f wet=%.4f | hf-frac dry=%.4f wet=%.4f\n", rms_dry, rms_wet, hf_dry, hf_wet); CHECK(AllFinite(b)); CHECK(MaxAbs(b) <= 1.001f); @@ -309,13 +311,13 @@ void TestMuffleDarkensAndDucks() { CHECK(hf_wet < hf_dry * 0.8f); // spectrum darkened (relative HF gone) // A live submerge toggle through the SynthVoice must not click. - SynthVoice voice(kRate, std::unique_ptr(new EngineSynth(InboardBoatPreset(), kRate))); + SynthVoice voice(kRate, base::UniquePointer(new EngineSynth(InboardBoatPreset(), kRate))); SynthParams p; p.rpm = 1500.0f; p.load = 0.4f; p.throttle = 0.4f; voice.SetParams(p); - std::vector buf(512, 0.0f); + base::Vector buf(512, 0.0f); f32 worst = 0.0f; for (int blk = 0; blk < 200; ++blk) { if (blk == 60) { @@ -328,9 +330,9 @@ void TestMuffleDarkensAndDucks() { } voice.Read(buf.data(), 512); CHECK(AllFinite(buf)); - worst = std::max(worst, MaxDelta(buf)); + worst = rx::Max(worst, MaxDelta(buf)); } - std::fprintf(stderr, "vehicle_audio_test: muffle toggle worst delta=%.4f\n", worst); + ::fprintf(stderr, "vehicle_audio_test: muffle toggle worst delta=%.4f\n", worst); CHECK(worst < 0.15f); } @@ -347,20 +349,20 @@ void TestSignedThrottleEquivalence() { st.throttle = throttle; st.speed_mps = 8.0f; va.Update(st); - std::vector out(1024 * 2, 0.0f); - std::vector acc; + base::Vector out(1024 * 2, 0.0f); + base::Vector acc; for (int i = 0; i < 20; ++i) { mixer.MixInto(out.data(), 1024); acc.insert(acc.end(), out.begin(), out.end()); } return acc; }; - const std::vector ahead = run(0.6f); - const std::vector astern = run(-0.6f); + const base::Vector ahead = run(0.6f); + const base::Vector astern = run(-0.6f); CHECK(ahead.size() == astern.size()); f32 diff = 0.0f; - for (size_t i = 0; i < ahead.size(); ++i) diff = std::max(diff, std::fabs(ahead[i] - astern[i])); - std::fprintf(stderr, "vehicle_audio_test: signed-throttle max diff=%.6g\n", diff); + for (size_t i = 0; i < ahead.size(); ++i) diff = rx::Max(diff, ::fabs(ahead[i] - astern[i])); + ::fprintf(stderr, "vehicle_audio_test: signed-throttle max diff=%.6g\n", diff); CHECK(Rms(ahead) > 0.005f); // audible (so the equivalence is not trivial silence) CHECK(diff < 1e-6f); // |−0.6| == |+0.6|: bit-for-bit identical } @@ -389,8 +391,8 @@ void TestPerWheelSlipPansSkid() { for (int i = 0; i < 4; ++i) st.wheel_slip[i] = slips[i]; st.position = Vec3{0.0f, 0.0f, 4.0f}; va.Update(st); - std::vector out(1024 * 2, 0.0f); - std::vector acc; + base::Vector out(1024 * 2, 0.0f); + base::Vector acc; for (int i = 0; i < 40; ++i) { // let the pan/gain envelopes settle mixer.MixInto(out.data(), 1024); if (i >= 10) acc.insert(acc.end(), out.begin(), out.end()); @@ -401,28 +403,28 @@ void TestPerWheelSlipPansSkid() { const f32 left_heavy[4] = {1.0f, 0.1f, 1.0f, 0.1f}; // FL, RL washing const f32 balanced[4] = {0.7f, 0.7f, 0.7f, 0.7f}; - const std::vector r = render_side(right_heavy, 4, 0.0f); - const std::vector l = render_side(left_heavy, 4, 0.0f); + const base::Vector r = render_side(right_heavy, 4, 0.0f); + const base::Vector l = render_side(left_heavy, 4, 0.0f); const f32 rL = ChannelRms(r, 0), rR = ChannelRms(r, 1); const f32 lL = ChannelRms(l, 0), lR = ChannelRms(l, 1); - std::fprintf(stderr, "vehicle_audio_test: skid pan right L=%.4f R=%.4f | left L=%.4f R=%.4f\n", + ::fprintf(stderr, "vehicle_audio_test: skid pan right L=%.4f R=%.4f | left L=%.4f R=%.4f\n", rL, rR, lL, lR); CHECK(rR > rL * 1.05f); // right-wheel slip -> right ear louder CHECK(lL > lR * 1.05f); // left-wheel slip -> left ear louder // Fallback: aggregate slip (count 0) and four equal wheels of the same value // produce identical audio (same intensity, no pan bias). - const std::vector agg = render_side(balanced, 0, 0.7f); - const std::vector quad = render_side(balanced, 4, 0.0f); + const base::Vector agg = render_side(balanced, 0, 0.7f); + const base::Vector quad = render_side(balanced, 4, 0.0f); f32 diff = 0.0f; - for (size_t i = 0; i < std::min(agg.size(), quad.size()); ++i) - diff = std::max(diff, std::fabs(agg[i] - quad[i])); + for (size_t i = 0; i < rx::Min(agg.size(), quad.size()); ++i) + diff = rx::Max(diff, ::fabs(agg[i] - quad[i])); const f32 aL = ChannelRms(agg, 0), aR = ChannelRms(agg, 1); - std::fprintf(stderr, "vehicle_audio_test: skid fallback diff=%.6g balanced L=%.4f R=%.4f\n", diff, + ::fprintf(stderr, "vehicle_audio_test: skid fallback diff=%.6g balanced L=%.4f R=%.4f\n", diff, aL, aR); CHECK(Rms(agg) > 0.005f); // the skid is actually audible CHECK(diff < 1e-6f); // count=0 aggregate == 4 equal wheels - CHECK(std::fabs(aL - aR) < 0.1f * (aL + aR)); // balanced slip stays centred + CHECK(::fabs(aL - aR) < 0.1f * (aL + aR)); // balanced slip stays centred } // (4) A gear shift produces a bounded, click-free level excursion, and nothing at @@ -434,16 +436,16 @@ void TestShiftFlare() { // fundamental's phase). High throttle keeps the overrun burble small so the // reference window is steady. auto flare_window = [](f32 request) { - SynthVoice voice(kRate, std::unique_ptr(new EngineSynth(InlineFourCarPreset(), kRate))); + SynthVoice voice(kRate, base::UniquePointer(new EngineSynth(InlineFourCarPreset(), kRate))); SynthParams p; p.rpm = 3400.0f; p.load = 0.7f; p.throttle = 0.9f; voice.SetParams(p); const u32 win = 9000; // ~187 ms, a touch longer than the flare envelope - std::vector ref(win, 0.0f), flr(win, 0.0f); + base::Vector ref(win, 0.0f), flr(win, 0.0f); // Warm up, then a steady reference window. - std::vector warm(win, 0.0f); + base::Vector warm(win, 0.0f); voice.Read(warm.data(), win); voice.Read(ref.data(), win); // Trigger the flare (rising edge) and render the flare window contiguously. @@ -451,7 +453,7 @@ void TestShiftFlare() { voice.SetParams(p); voice.Read(flr.data(), win); // Worst sample step across the whole reference+flare span (click check). - std::vector both = ref; + base::Vector both = ref; both.insert(both.end(), flr.begin(), flr.end()); struct R { f32 ref, flr, worst; }; return R{Rms(ref), Rms(flr), MaxDelta(both)}; @@ -460,7 +462,7 @@ void TestShiftFlare() { const auto up = flare_window(-1.0f); // upshift cut const auto down = flare_window(1.0f); // downshift blip const auto none = flare_window(0.0f); // no request - std::fprintf(stderr, + ::fprintf(stderr, "vehicle_audio_test: shift up(ref=%.4f flr=%.4f worst=%.4f) " "down(ref=%.4f flr=%.4f worst=%.4f) none(ref=%.4f flr=%.4f)\n", up.ref, up.flr, up.worst, down.ref, down.flr, down.worst, none.ref, none.flr); @@ -468,7 +470,7 @@ void TestShiftFlare() { CHECK(up.worst < 0.15f); // ... without a click CHECK(down.flr > down.ref * 1.05f); // downshift audibly blips it up CHECK(down.worst < 0.15f); - CHECK(std::fabs(none.flr - none.ref) < none.ref * 0.06f); // flat when idle + CHECK(::fabs(none.flr - none.ref) < none.ref * 0.06f); // flat when idle // Mapping level: a gear left INT_MIN must yield no flare even as is_shifting // pulses, so the mix is bit-identical to a run that never touches either field. @@ -480,8 +482,8 @@ void TestShiftFlare() { st.rpm = 3000.0f; st.load = 0.5f; st.throttle = 0.5f; - std::vector out(512 * 2, 0.0f); - std::vector acc; + base::Vector out(512 * 2, 0.0f); + base::Vector acc; for (int i = 0; i < 30; ++i) { st.is_shifting = pulse_shift && (i == 10); // pulse, but gear stays INT_MIN va.Update(st); @@ -490,11 +492,11 @@ void TestShiftFlare() { } return acc; }; - const std::vector plain = run_gate(false); - const std::vector pulsed = run_gate(true); + const base::Vector plain = run_gate(false); + const base::Vector pulsed = run_gate(true); f32 gate_diff = 0.0f; - for (size_t i = 0; i < plain.size(); ++i) gate_diff = std::max(gate_diff, std::fabs(plain[i] - pulsed[i])); - std::fprintf(stderr, "vehicle_audio_test: shift gate (unknown gear) diff=%.6g\n", gate_diff); + for (size_t i = 0; i < plain.size(); ++i) gate_diff = rx::Max(gate_diff, ::fabs(plain[i] - pulsed[i])); + ::fprintf(stderr, "vehicle_audio_test: shift gate (unknown gear) diff=%.6g\n", gate_diff); CHECK(gate_diff < 1e-6f); // unknown gear -> no flare, nothing changes } @@ -512,28 +514,28 @@ void TestJetSpoolSplit() { // Whine pitch tracks N1 even with thrust pinned low. const f32 whine_lo = RawZeroCrossHz(render(40.0f, 0.1f)); const f32 whine_hi = RawZeroCrossHz(render(85.0f, 0.1f)); - std::fprintf(stderr, "vehicle_audio_test: jet whine Hz n1=40 -> %.1f n1=85 -> %.1f\n", whine_lo, + ::fprintf(stderr, "vehicle_audio_test: jet whine Hz n1=40 -> %.1f n1=85 -> %.1f\n", whine_lo, whine_hi); CHECK(whine_hi > whine_lo * 1.2f); // N1 climbs the whine pitch // Roar level tracks thrust at fixed N1. The roar is low-frequency broadband // noise; a steep (two-pole) low-pass well under the whine isolates it, so the // 4 kHz whine tone does not leak in and mask the difference. - auto roar_rms = [](std::vector b) { + auto roar_rms = [](base::Vector b) { LowPass(b, 180.0f); LowPass(b, 180.0f); return Rms(b); }; const f32 roar_low = roar_rms(render(85.0f, 0.1f)); const f32 roar_high = roar_rms(render(85.0f, 0.9f)); - std::fprintf(stderr, "vehicle_audio_test: jet roar rms thrust=0.1 -> %.5f thrust=0.9 -> %.5f\n", + ::fprintf(stderr, "vehicle_audio_test: jet roar rms thrust=0.1 -> %.5f thrust=0.9 -> %.5f\n", roar_low, roar_high); CHECK(roar_high > roar_low * 1.3f); // thrust drives the roar, spool does not // Spool-up character: at high N1, holding thrust low keeps the roar well below // the old derive-from-rpm behaviour, while the whine (above) already tracks N1. const f32 roar_derive = roar_rms(render(85.0f, -1.0f)); - std::fprintf(stderr, "vehicle_audio_test: jet roar rms derive(n1=85) -> %.5f\n", roar_derive); + ::fprintf(stderr, "vehicle_audio_test: jet roar rms derive(n1=85) -> %.5f\n", roar_derive); CHECK(roar_low < roar_derive * 0.7f); // whine climbs before the roar catches up } @@ -556,7 +558,7 @@ void TestUpdateAfterStopAllIsSafe() { st.slip = 0.4f; va.Update(st); - std::vector out(1024 * 2, 0.0f); + base::Vector out(1024 * 2, 0.0f); // Get the voices live in the mixer. for (int i = 0; i < 4; ++i) mixer.MixInto(out.data(), 1024); @@ -572,7 +574,7 @@ void TestUpdateAfterStopAllIsSafe() { st.slip = 0.5f; va.Update(st); mixer.MixInto(out.data(), 1024); - for (f32 v : out) CHECK(std::isfinite(v)); + for (f32 v : out) CHECK(::isfinite(v)); } va.Stop(); // idempotent, also safe after the voices are gone } @@ -584,20 +586,20 @@ void TestUpdateAfterStopAllIsSafe() { // time. A torn read of the shared struct would surface as a NaN, an out-of-range // value blowing past the soft limit, or a click. void TestParamMailboxThreadedHammer() { - SynthVoice voice(kRate, std::unique_ptr(new EngineSynth(InlineFourCarPreset(), kRate))); + SynthVoice voice(kRate, base::UniquePointer(new EngineSynth(InlineFourCarPreset(), kRate))); SynthParams warm; warm.rpm = 1200.0f; warm.load = 0.3f; warm.throttle = 0.3f; voice.SetParams(warm); - std::atomic done{false}; - std::atomic writes{0}; + base::Atomic done{false}; + base::Atomic writes{0}; // Writer: slam the parameters across the full authored range as fast as it can, // so a torn snapshot would mix low-rpm fields with high-rpm fields. - std::thread writer([&] { + base::Thread writer("audio_writer", [&] { u32 n = 0; - while (!done.load(std::memory_order_relaxed)) { + while (!done.load(base::memory_order_relaxed)) { SynthParams p; const f32 phase = static_cast((n++ % 100)) / 100.0f; p.rpm = 800.0f + phase * 6200.0f; @@ -608,25 +610,25 @@ void TestParamMailboxThreadedHammer() { p.muffle = (n & 1) ? 1.0f : 0.0f; p.gear_shift = (n % 7 == 0) ? 1.0f : 0.0f; voice.SetParams(p); - writes.fetch_add(1, std::memory_order_relaxed); + writes.fetch_add(1, base::memory_order_relaxed); } - }); + }, /*start_now=*/true); const u32 block = 256; - std::vector buf(block, 0.0f); + base::Vector buf(block, 0.0f); f32 worst = 0.0f, peak = 0.0f; bool finite = true; // Bounded, finite render loop on the consumer (device) side. for (int blk = 0; blk < 4000; ++blk) { voice.Read(buf.data(), block); if (!AllFinite(buf)) finite = false; - worst = std::max(worst, MaxDelta(buf)); - peak = std::max(peak, MaxAbs(buf)); + worst = rx::Max(worst, MaxDelta(buf)); + peak = rx::Max(peak, MaxAbs(buf)); } - done.store(true, std::memory_order_relaxed); - writer.join(); + done.store(true, base::memory_order_relaxed); + writer.Join(); - std::fprintf(stderr, + ::fprintf(stderr, "vehicle_audio_test: mailbox hammer writes=%llu peak=%.4f worst-delta=%.4f\n", static_cast(writes.load()), peak, worst); CHECK(finite); // no torn read ever produced a NaN @@ -651,6 +653,6 @@ int main() { TestJetSpoolSplit(); TestUpdateAfterStopAllIsSafe(); TestParamMailboxThreadedHammer(); - if (failures == 0) std::printf("vehicle_audio_test: all checks passed\n"); + if (failures == 0) ::printf("vehicle_audio_test: all checks passed\n"); return failures == 0 ? 0 : 1; } diff --git a/test/vehicle_integration_test.cc b/test/vehicle_integration_test.cc index 820a73c0..281a7942 100644 --- a/test/vehicle_integration_test.cc +++ b/test/vehicle_integration_test.cc @@ -13,14 +13,14 @@ // The vehicle Update-before-PhysicsWorld::Update contract is honoured: every // simulator stages its forces for the frame, then the world integrates once. -#include -#include -#include -#include +#include +#include #include "audio/engine_synth.h" #include "audio/synth_voice.h" +#include "base/containers/vector.h" #include "core/math.h" +#include "core/scalar.h" #include "physics/aircraft.h" #include "physics/boat.h" #include "physics/physics_world.h" @@ -56,32 +56,32 @@ constexpr f32 kLakeX = 1500.0f; f32 g_wave_t = 0.0f; int Fail(const char* what) { - std::fprintf(stderr, "vehicle_integration_test FAIL: %s\n", what); + ::fprintf(stderr, "vehicle_integration_test FAIL: %s\n", what); return 1; } bool IsFinite(const Vec3& v) { - return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z); + return ::isfinite(v.x) && ::isfinite(v.y) && ::isfinite(v.z); } // audio buffer helpers (mirror vehicle_audio_test) -bool AllFinite(const std::vector& b) { +bool AllFinite(const base::Vector& b) { for (f32 v : b) - if (!std::isfinite(v)) return false; + if (!::isfinite(v)) return false; return true; } -f32 MaxAbs(const std::vector& b) { +f32 MaxAbs(const base::Vector& b) { f32 m = 0.0f; - for (f32 v : b) m = std::max(m, std::fabs(v)); + for (f32 v : b) m = rx::Max(m, ::fabs(v)); return m; } // RMS over the half-open sample window [lo, hi). -f32 RmsWindow(const std::vector& b, size_t lo, size_t hi) { - hi = std::min(hi, b.size()); +f32 RmsWindow(const base::Vector& b, size_t lo, size_t hi) { + hi = rx::Min(hi, b.size()); if (lo >= hi) return 0.0f; f64 acc = 0.0; for (size_t i = lo; i < hi; ++i) acc += static_cast(b[i]) * b[i]; - return static_cast(std::sqrt(acc / static_cast(hi - lo))); + return static_cast(::sqrt(acc / static_cast(hi - lo))); } // World up projected onto the boat's up axis: 1 = upright, 0 = on its side. @@ -92,7 +92,7 @@ f32 Uprightness(const Quat& q) { return Rotate(q, Vec3{0, 1, 0}).y; } int main() { PhysicsWorld probe; if (!probe.Initialize()) { - std::fprintf(stderr, "vehicle_integration_test: physics unavailable, skipping\n"); + ::fprintf(stderr, "vehicle_integration_test: physics unavailable, skipping\n"); return 0; } @@ -104,7 +104,7 @@ int main() { { constexpr u32 kSamples = 64; constexpr f32 kSize = 2000.0f; - std::vector heights(static_cast(kSamples) * kSamples, 0.0f); + base::Vector heights(static_cast(kSamples) * kSamples, 0.0f); world.AddHeightField(Vec3{-kSize * 0.5f, 0.0f, -kSize * 0.5f}, heights.data(), kSamples, kSize, SurfaceType::kAsphalt); } @@ -151,9 +151,9 @@ int main() { EngineSynth car_synth(audio::InlineFourCarPreset(), kRate); EngineSynth boat_synth(audio::InboardBoatPreset(), kRate); EngineSynth plane_synth(audio::SinglePropPlanePreset(), kRate); - std::vector car_audio(static_cast(kSteps) * kBlock, 0.0f); - std::vector boat_audio(static_cast(kSteps) * kBlock, 0.0f); - std::vector plane_audio(static_cast(kSteps) * kBlock, 0.0f); + base::Vector car_audio(static_cast(kSteps) * kBlock, 0.0f); + base::Vector boat_audio(static_cast(kSteps) * kBlock, 0.0f); + base::Vector plane_audio(static_cast(kSteps) * kBlock, 0.0f); // Car audio load-response windows: an early idle stretch vs a later // full-throttle stretch (throttle schedule below drives the change). @@ -181,7 +181,7 @@ int main() { // (a) Car: idle briefly, then full throttle with a mild weave. const f32 car_throttle = frame < kIdleEnd ? 0.0f : 1.0f; - const f32 car_steer = frame < kIdleEnd ? 0.0f : 0.25f * std::sin(frame * 0.02f); + const f32 car_steer = frame < kIdleEnd ? 0.0f : 0.25f * ::sin(frame * 0.02f); world.DriveVehicle(car, car_throttle, car_steer, 0.0f, 0.0f); // (b) Boat: straight-ahead throttle across the chop. @@ -206,28 +206,28 @@ int main() { if (!world.GetVehicleState(car, &cst)) return Fail("(a) car telemetry read failed"); if (cst.rpm < 0.0f || cst.rpm > car_redline + 50.0f) return Fail("(a) car rpm out of range"); if (cst.engine_load < 0.0f || cst.engine_load > 1.0f) return Fail("(a) car load out of [0,1]"); - car_max_gear = std::max(car_max_gear, cst.gear); + car_max_gear = rx::Max(car_max_gear, cst.gear); const f32 car_speed = world.VehicleForwardSpeed(car); - car_max_speed = std::max(car_max_speed, car_speed); + car_max_speed = rx::Max(car_max_speed, car_speed); f32 car_slip = 0.0f; for (u32 w = 0; w < cst.wheel_count; ++w) - car_slip = std::max(car_slip, cst.wheels[w].longitudinal_slip); + car_slip = rx::Max(car_slip, cst.wheels[w].longitudinal_slip); const physics::BoatState& bst = boat.state(); - boat_max_fwd = std::max(boat_max_fwd, bst.forward_speed); - boat_max_wetted = std::max(boat_max_wetted, bst.wetted); + boat_max_fwd = rx::Max(boat_max_fwd, bst.forward_speed); + boat_max_wetted = rx::Max(boat_max_wetted, bst.wetted); const physics::AircraftState& ast = plane.state(); - plane_max_alt = std::max(plane_max_alt, ast.position.y - plane_ground_y); - if (!ast.on_ground) plane_peak_climb = std::max(plane_peak_climb, ast.vertical_speed_mps); + plane_max_alt = rx::Max(plane_max_alt, ast.position.y - plane_ground_y); + if (!ast.on_ground) plane_peak_climb = rx::Max(plane_peak_climb, ast.vertical_speed_mps); // Per-frame NaN gate: three simulators must never poison the shared world. - if (!std::isfinite(car_speed) || !std::isfinite(cst.rpm)) return Fail("(a) car NaN in telemetry"); - if (!IsFinite(bst.position) || !std::isfinite(bst.rpm) || !std::isfinite(bst.forward_speed) || - !std::isfinite(bst.rotation.w)) + if (!::isfinite(car_speed) || !::isfinite(cst.rpm)) return Fail("(a) car NaN in telemetry"); + if (!IsFinite(bst.position) || !::isfinite(bst.rpm) || !::isfinite(bst.forward_speed) || + !::isfinite(bst.rotation.w)) return Fail("(b) boat NaN in telemetry"); - if (!IsFinite(ast.position) || !std::isfinite(ast.airspeed_mps) || - !std::isfinite(ast.vertical_speed_mps) || !std::isfinite(ast.rotation.w)) + if (!IsFinite(ast.position) || !::isfinite(ast.airspeed_mps) || + !::isfinite(ast.vertical_speed_mps) || !::isfinite(ast.rotation.w)) return Fail("(c) aircraft NaN in telemetry"); // (d) Telemetry -> SynthParams -> one rendered block per vehicle. @@ -235,14 +235,14 @@ int main() { cp.rpm = cst.rpm; cp.load = cst.engine_load; cp.throttle = car_throttle; - cp.speed_mps = std::fabs(car_speed); + cp.speed_mps = ::fabs(car_speed); cp.slip = car_slip; car_synth.Render(&car_audio[static_cast(frame) * kBlock], kBlock, cp); SynthParams bp; bp.rpm = bst.rpm; bp.load = bst.engine_load; - bp.throttle = std::max(0.0f, bst.throttle); + bp.throttle = rx::Max(0.0f, bst.throttle); bp.speed_mps = bst.speed_mps; boat_synth.Render(&boat_audio[static_cast(frame) * kBlock], kBlock, bp); @@ -260,8 +260,8 @@ int main() { f32 rot[4]; world.GetVehicleTransform(car, &pos, rot); const f32 dx = pos.x - car_start.x, dz = pos.z - car_start.z; - const f32 travelled = std::sqrt(dx * dx + dz * dz); - std::fprintf(stderr, "(a) car: travelled=%.1f m max_speed=%.1f m/s max_gear=%d\n", travelled, + const f32 travelled = ::sqrt(dx * dx + dz * dz); + ::fprintf(stderr, "(a) car: travelled=%.1f m max_speed=%.1f m/s max_gear=%d\n", travelled, car_max_speed, car_max_gear); if (travelled < 20.0f) return Fail("(a) car did not drive"); if (car_max_speed < 10.0f) return Fail("(a) car did not reach speed"); @@ -270,7 +270,7 @@ int main() { // (b) The boat throttled forward and is still afloat. const physics::BoatState& bfin = boat.state(); - std::fprintf(stderr, + ::fprintf(stderr, "(b) boat: max_fwd=%.1f m/s pos=(%.0f,%.2f,%.1f) wetted=%.2f max_wetted=%.2f " "up=%.2f\n", boat_max_fwd, bfin.position.x, bfin.position.y, bfin.position.z, bfin.wetted, @@ -282,7 +282,7 @@ int main() { // (c) The aircraft climbed away from the runway. const physics::AircraftState& afin = plane.state(); - std::fprintf(stderr, + ::fprintf(stderr, "(c) aircraft: max_alt_gain=%.1f m peak_climb=%.2f m/s airspeed=%.1f m/s " "on_ground=%d\n", plane_max_alt, plane_peak_climb, afin.airspeed_mps, afin.on_ground); @@ -293,7 +293,7 @@ int main() { plane_peak = MaxAbs(plane_audio); const f32 car_idle_rms = RmsWindow(car_audio, idle_lo, idle_hi); const f32 car_load_rms = RmsWindow(car_audio, load_lo, load_hi); - std::fprintf(stderr, + ::fprintf(stderr, "(d) audio peaks car=%.3f boat=%.3f plane=%.3f | car rms idle=%.4f load=%.4f\n", car_peak, boat_peak, plane_peak, car_idle_rms, car_load_rms); if (!AllFinite(car_audio) || !AllFinite(boat_audio) || !AllFinite(plane_audio)) @@ -316,7 +316,7 @@ int main() { for (u32 w = 0; w < cst.wheel_count; ++w) car_grounded |= cst.wheels[w].contact; if (!IsFinite(cpos)) return Fail("(e) car position NaN"); if (!car_grounded) return Fail("(e) car left the ground"); - if (std::fabs(cpos.y) > 5.0f) return Fail("(e) car position drifted off the surface"); + if (::fabs(cpos.y) > 5.0f) return Fail("(e) car position drifted off the surface"); if (!IsFinite(bfin.position)) return Fail("(e) boat position NaN"); if (boat_max_wetted <= 0.0f) return Fail("(e) boat never touched the water"); @@ -325,6 +325,6 @@ int main() { if (!IsFinite(afin.position)) return Fail("(e) aircraft position NaN"); } - std::fprintf(stderr, "vehicle_integration_test: all checks passed\n"); + ::fprintf(stderr, "vehicle_integration_test: all checks passed\n"); return 0; } diff --git a/test/vehicle_test.cc b/test/vehicle_test.cc index 48eb5c22..9643373f 100644 --- a/test/vehicle_test.cc +++ b/test/vehicle_test.cc @@ -4,11 +4,12 @@ // aquaplaning. Each scenario builds its own flat height-field world so the // tagged ground surface is fixed for the run. -#include -#include -#include +#include +#include +#include "base/containers/vector.h" #include "core/math.h" +#include "core/scalar.h" #include "physics/physics_world.h" using namespace rx; @@ -21,7 +22,7 @@ namespace { constexpr f32 kDt = 1.0f / 60.0f; int Fail(const char* what) { - std::fprintf(stderr, "vehicle_test FAIL: %s\n", what); + ::fprintf(stderr, "vehicle_test FAIL: %s\n", what); return 1; } @@ -29,7 +30,7 @@ int Fail(const char* what) { physics::BodyId AddFlatGround(PhysicsWorld& world, SurfaceType surface) { constexpr u32 kSamples = 64; constexpr f32 kSize = 1024.0f; - std::vector heights(static_cast(kSamples) * kSamples, 0.0f); + base::Vector heights(static_cast(kSamples) * kSamples, 0.0f); return world.AddHeightField(Vec3{-kSize * 0.5f, 0.0f, -kSize * 0.5f}, heights.data(), kSamples, kSize, surface); } @@ -60,7 +61,7 @@ VehicleId SpawnSettledCar(PhysicsWorld& world, const PhysicsWorld::VehicleDesc& f32 HorizontalDistance(const Vec3& a, const Vec3& b) { const f32 dx = a.x - b.x; const f32 dz = a.z - b.z; - return std::sqrt(dx * dx + dz * dz); + return ::sqrt(dx * dx + dz * dz); } // Full-throttle until the car reaches `target` m/s or `max_steps` elapse. @@ -85,7 +86,7 @@ f32 BrakeToStop(PhysicsWorld& world, VehicleId id, int max_steps) { world.DriveVehicle(id, 0.0f, 0.0f, 1.0f, 0.0f); world.Update(kDt); world.GetVehicleTransform(id, &last, rot); - if (std::fabs(world.VehicleForwardSpeed(id)) < 0.5f) break; + if (::fabs(world.VehicleForwardSpeed(id)) < 0.5f) break; } return HorizontalDistance(start, last); } @@ -108,7 +109,7 @@ f32 StoppingDistance(SurfaceType surface, f32 wetness) { int main() { PhysicsWorld probe; if (!probe.Initialize()) { - std::fprintf(stderr, "vehicle_test: physics unavailable, skipping\n"); + ::fprintf(stderr, "vehicle_test: physics unavailable, skipping\n"); return 0; } @@ -125,10 +126,10 @@ int main() { world.Update(kDt); PhysicsWorld::VehicleState st; world.GetVehicleState(car, &st); - max_gear = std::max(max_gear, st.gear); - max_speed = std::max(max_speed, world.VehicleForwardSpeed(car)); + max_gear = rx::Max(max_gear, st.gear); + max_speed = rx::Max(max_speed, world.VehicleForwardSpeed(car)); } - std::fprintf(stderr, "(a) max_speed=%.2f m/s max_gear=%d\n", max_speed, max_gear); + ::fprintf(stderr, "(a) max_speed=%.2f m/s max_gear=%d\n", max_speed, max_gear); if (max_speed < 10.0f) return Fail("(a) car did not accelerate"); if (max_gear < 2) return Fail("(a) automatic box did not shift past 1st"); } @@ -137,7 +138,7 @@ int main() { { const f32 asphalt = StoppingDistance(SurfaceType::kAsphalt, 0.0f); const f32 ice = StoppingDistance(SurfaceType::kIce, 0.0f); - std::fprintf(stderr, "(b) asphalt stop=%.2f m, ice stop=%.2f m\n", asphalt, ice); + ::fprintf(stderr, "(b) asphalt stop=%.2f m, ice stop=%.2f m\n", asphalt, ice); if (asphalt <= 0 || ice <= 0) return Fail("(b) stopping distance measurement failed"); if (ice < asphalt * 2.0f) return Fail("(b) ice braking not much longer than asphalt"); } @@ -146,7 +147,7 @@ int main() { { const f32 dry = StoppingDistance(SurfaceType::kAsphalt, 0.0f); const f32 wet = StoppingDistance(SurfaceType::kAsphalt, 1.0f); - std::fprintf(stderr, "(c) dry stop=%.2f m, wet stop=%.2f m (x%.2f)\n", dry, wet, wet / dry); + ::fprintf(stderr, "(c) dry stop=%.2f m, wet stop=%.2f m (x%.2f)\n", dry, wet, wet / dry); if (dry <= 0 || wet <= 0) return Fail("(c) stopping distance measurement failed"); // Grip-limited (see DefaultCar): soaked asphalt is ~0.7 of dry grip, so the // stop is ~1.4x longer. Assert a ratio with slack that still proves the loss. @@ -171,10 +172,10 @@ int main() { PhysicsWorld::VehicleState st; world.GetVehicleState(car, &st); gear_before_shift = st.gear; - peak_rpm = std::max(peak_rpm, st.rpm); + peak_rpm = rx::Max(peak_rpm, st.rpm); if (st.gear != 1) break; } - std::fprintf(stderr, "(d) held gear=%d at peak_rpm=%.0f\n", gear_before_shift, peak_rpm); + ::fprintf(stderr, "(d) held gear=%d at peak_rpm=%.0f\n", gear_before_shift, peak_rpm); if (gear_before_shift != 1) return Fail("(d) manual mode did not hold 1st gear"); // A single shift_up edge advances one gear. PhysicsWorld::VehicleInput up = in; @@ -183,7 +184,7 @@ int main() { world.Update(kDt); PhysicsWorld::VehicleState st; world.GetVehicleState(car, &st); - std::fprintf(stderr, "(d) gear after shift_up=%d\n", st.gear); + ::fprintf(stderr, "(d) gear after shift_up=%d\n", st.gear); if (st.gear != 2) return Fail("(d) shift_up did not advance a gear"); } @@ -212,7 +213,7 @@ int main() { } } } - std::fprintf(stderr, "(e) telemetry ranges OK\n"); + ::fprintf(stderr, "(e) telemetry ranges OK\n"); } // (f) Aquaplaning: a fast car over a water plane loses lateral grip vs dry. @@ -242,13 +243,13 @@ int main() { world.Update(kDt); Vec3 pos; world.GetVehicleTransform(car, &pos, rot); - peak = std::max(peak, std::fabs(pos.x - start.x)); + peak = rx::Max(peak, ::fabs(pos.x - start.x)); } return peak; }; const f32 dry = lateral_offset(false); const f32 wet = lateral_offset(true); - std::fprintf(stderr, "(f) dry lateral offset=%.2f m, aquaplane offset=%.2f m\n", dry, wet); + ::fprintf(stderr, "(f) dry lateral offset=%.2f m, aquaplane offset=%.2f m\n", dry, wet); if (dry <= wet * 1.3f) return Fail("(f) aquaplaning did not reduce cornering grip"); } @@ -273,13 +274,13 @@ int main() { for (int i = 0; i < 60 * 25; ++i) { // 25 s of full throttle down a straight world.DriveVehicle(car, 1.0f, 0.0f, 0.0f, 0.0f); world.Update(kDt); - peak = std::max(peak, world.VehicleForwardSpeed(car)); + peak = rx::Max(peak, world.VehicleForwardSpeed(car)); } return peak; }; const f32 dry = top_speed(false); const f32 wet = top_speed(true); - std::fprintf(stderr, "(g) top speed: dry=%.2f m/s standing-water=%.2f m/s (x%.2f)\n", dry, wet, + ::fprintf(stderr, "(g) top speed: dry=%.2f m/s standing-water=%.2f m/s (x%.2f)\n", dry, wet, wet / dry); if (dry <= 0) return Fail("(g) dry top-speed measurement failed"); if (wet >= dry * 0.9f) return Fail("(g) standing water did not drag top speed down"); @@ -303,9 +304,9 @@ int main() { world.Update(kDt); } const f32 self_driven = world.VehicleForwardSpeed(trailer); - std::fprintf(stderr, "(h) free-rolling self-driven speed after 3 s throttle=%.3f m/s\n", + ::fprintf(stderr, "(h) free-rolling self-driven speed after 3 s throttle=%.3f m/s\n", self_driven); - if (std::fabs(self_driven) > 1.0f) return Fail("(h) free-rolling vehicle drove itself"); + if (::fabs(self_driven) > 1.0f) return Fail("(h) free-rolling vehicle drove itself"); // (3) Tow it: a steady forward (+Z) force on the chassis body makes it roll. const physics::BodyId body = world.GetVehicleBody(trailer); @@ -319,14 +320,14 @@ int main() { world.Update(kDt); } const f32 towed = world.VehicleForwardSpeed(trailer); - std::fprintf(stderr, "(h) free-rolling towed speed after 4 s=%.3f m/s\n", towed); + ::fprintf(stderr, "(h) free-rolling towed speed after 4 s=%.3f m/s\n", towed); if (towed < 2.0f) return Fail("(h) free-rolling vehicle did not roll forward when towed"); // (4) Steering still works: hold a tow force and full lock, heading changes. Vec3 p0{}; f32 r0[4]; world.GetVehicleTransform(trailer, &p0, r0); - const f32 start_heading = std::atan2(Rotate(Quat{r0[0], r0[1], r0[2], r0[3]}, Vec3{0, 0, 1}).x, + const f32 start_heading = ::atan2(Rotate(Quat{r0[0], r0[1], r0[2], r0[3]}, Vec3{0, 0, 1}).x, Rotate(Quat{r0[0], r0[1], r0[2], r0[3]}, Vec3{0, 0, 1}).z); for (int i = 0; i < 60 * 3; ++i) { Vec3 pos{}; @@ -340,13 +341,13 @@ int main() { f32 r1[4]; world.GetVehicleTransform(trailer, &p1, r1); const Vec3 fdir = Rotate(Quat{r1[0], r1[1], r1[2], r1[3]}, Vec3{0, 0, 1}); - const f32 end_heading = std::atan2(fdir.x, fdir.z); + const f32 end_heading = ::atan2(fdir.x, fdir.z); f32 dh = end_heading - start_heading; while (dh > 3.14159265f) dh -= 6.2831853f; while (dh < -3.14159265f) dh += 6.2831853f; - std::fprintf(stderr, "(h) free-rolling heading change while steering=%.1f deg\n", + ::fprintf(stderr, "(h) free-rolling heading change while steering=%.1f deg\n", dh * 57.2958f); - if (std::fabs(dh) < 0.05f) return Fail("(h) free-rolling front axle did not steer"); + if (::fabs(dh) < 0.05f) return Fail("(h) free-rolling front axle did not steer"); } // (i) Manual transmission routes through traction control: on ice, TC-on holds @@ -369,12 +370,12 @@ int main() { world.Update(kDt); // Only sample once rolling: TC (like a real system) is disengaged at // launch, where the slip-ratio denominator makes the reading meaningless. - if (std::fabs(world.VehicleForwardSpeed(car)) < 5.0f) continue; + if (::fabs(world.VehicleForwardSpeed(car)) < 5.0f) continue; PhysicsWorld::VehicleState st; if (!world.GetVehicleState(car, &st)) continue; f32 peak = 0; for (u32 w = 0; w < st.wheel_count; ++w) - peak = std::max(peak, std::fabs(st.wheels[w].longitudinal_slip)); + peak = rx::Max(peak, ::fabs(st.wheels[w].longitudinal_slip)); slip_sum += peak; ++slip_n; } @@ -382,7 +383,7 @@ int main() { }; const f32 tc_off = mean_slip_on_ice(false); const f32 tc_on = mean_slip_on_ice(true); - std::fprintf(stderr, "(i) manual ice mean driven slip: TC-off=%.3f TC-on=%.3f\n", tc_off, + ::fprintf(stderr, "(i) manual ice mean driven slip: TC-off=%.3f TC-on=%.3f\n", tc_off, tc_on); if (tc_off <= 0.0f) return Fail("(i) manual ice run never got rolling to measure slip"); if (tc_on >= tc_off * 0.9f) return Fail("(i) manual mode did not apply traction control"); @@ -405,7 +406,7 @@ int main() { } PhysicsWorld::VehicleState dis; if (!world.GetVehicleState(car, &dis)) return Fail("(j) telemetry read failed"); - std::fprintf(stderr, + ::fprintf(stderr, "(j) clutch held: engine_torque=%.1f Nm rpm=%.0f is_shifting=%d load=%.3f\n", dis.engine_torque, dis.rpm, dis.is_shifting ? 1 : 0, dis.engine_load); if (dis.engine_torque > 1.0f) return Fail("(j) disengaged clutch still delivers wheel torque"); @@ -419,10 +420,10 @@ int main() { } PhysicsWorld::VehicleState eng; world.GetVehicleState(car, &eng); - std::fprintf(stderr, "(j) clutch engaged: engine_torque=%.1f Nm\n", eng.engine_torque); + ::fprintf(stderr, "(j) clutch engaged: engine_torque=%.1f Nm\n", eng.engine_torque); if (eng.engine_torque <= 1.0f) return Fail("(j) engaged clutch delivered no torque"); } - std::fprintf(stderr, "vehicle_test: all checks passed\n"); + ::fprintf(stderr, "vehicle_test: all checks passed\n"); return 0; } diff --git a/test/vfs_test.cc b/test/vfs_test.cc index c4f51dce..ebcccf8f 100644 --- a/test/vfs_test.cc +++ b/test/vfs_test.cc @@ -3,18 +3,20 @@ // override order, the pack writer/reader round trip (store + deflate + the // incompressible fallback), checksum tamper detection, and enumeration. -#include -#include -#include -#include -#include -#include +#include +#include #include "asset/asset_id.h" #include "asset/pack.h" #include "asset/vfs.h" - -namespace fs = std::filesystem; +#include "base/memory/mem_ops.h" +#include "base/memory/move.h" +#include "base/optional.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" + +namespace fs = rx::fs; namespace asset = rx::asset; namespace { @@ -24,20 +26,50 @@ int failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::fprintf(stderr, "vfs_test: FAIL %s:%d: %s\n", __FILE__, __LINE__, \ + ::fprintf(stderr, "vfs_test: FAIL %s:%d: %s\n", __FILE__, __LINE__, \ #cond); \ ++failures; \ } \ } while (0) -void WriteFile(const fs::path& path, std::string_view contents) { - fs::create_directories(path.parent_path()); - std::ofstream out(path, std::ios::binary | std::ios::trunc); - out.write(contents.data(), static_cast(contents.size())); +void WriteFile(base::StringRef path, base::StringRef contents) { + fs::CreateDirectories(fs::ParentPath(path)); + fs::WriteTextFile(path, contents); } -std::string AsString(const base::Vector& bytes) { - return std::string(reinterpret_cast(bytes.data()), bytes.size()); +// Mersenne Twister, as std::mt19937 specifies it. The incompressible payload +// was std::mt19937(1234) output, so this reproduces those exact bytes. +class Mt19937 { + public: + explicit Mt19937(u32 seed) { + state_[0] = seed; + for (u32 i = 1; i < kN; ++i) + state_[i] = 1812433253u * (state_[i - 1] ^ (state_[i - 1] >> 30)) + i; + } + u32 operator()() { + if (index_ == kN) Twist(); + u32 y = state_[index_++]; + y ^= y >> 11; + y ^= (y << 7) & 0x9d2c5680u; + y ^= (y << 15) & 0xefc60000u; + return y ^ (y >> 18); + } + + private: + static constexpr u32 kN = 624; + void Twist() { + for (u32 i = 0; i < kN; ++i) { + const u32 y = (state_[i] & 0x80000000u) | (state_[(i + 1) % kN] & 0x7fffffffu); + state_[i] = state_[(i + 397) % kN] ^ (y >> 1) ^ ((y & 1) ? 0x9908b0dfu : 0u); + } + index_ = 0; + } + u32 state_[kN]; + u32 index_ = kN; +}; + +base::String AsString(const base::Vector& bytes) { + return base::String(reinterpret_cast(bytes.data()), bytes.size()); } void TestSplitVirtualPath() { @@ -53,27 +85,27 @@ void TestSplitVirtualPath() { CHECK(vp.mount.empty() && vp.path == "a/b://c"); } -void TestMounts(const fs::path& tmp) { - const fs::path base_dir = tmp / "base"; - const fs::path patch_dir = tmp / "patch"; - const fs::path dlc_dir = tmp / "dlc"; - WriteFile(base_dir / "textures/rock.dds", "base rock"); - WriteFile(base_dir / "readme.txt", "base readme"); - WriteFile(patch_dir / "textures/rock.dds", "patched rock"); - WriteFile(dlc_dir / "extra.txt", "dlc extra"); +void TestMounts(base::StringRef tmp) { + const base::String base_dir = fs::Join(tmp, "base"); + const base::String patch_dir = fs::Join(tmp, "patch"); + const base::String dlc_dir = fs::Join(tmp, "dlc"); + WriteFile(fs::Join(base_dir, "textures/rock.dds"), "base rock"); + WriteFile(fs::Join(base_dir, "readme.txt"), "base readme"); + WriteFile(fs::Join(patch_dir, "textures/rock.dds"), "patched rock"); + WriteFile(fs::Join(dlc_dir, "extra.txt"), "dlc extra"); asset::Vfs vfs; // Legacy root namespace with override order: later mounts win. - vfs.Mount(asset::MakeLooseFileProvider(base_dir.string())); - vfs.Mount(asset::MakeLooseFileProvider(patch_dir.string())); + vfs.Mount(asset::MakeLooseFileProvider(base_dir)); + vfs.Mount(asset::MakeLooseFileProvider(patch_dir)); auto rock = vfs.Read("Textures\\Rock.DDS"); // normalization on the way in CHECK(rock && AsString(*rock) == "patched rock"); auto readme = vfs.Read("readme.txt"); CHECK(readme && AsString(*readme) == "base readme"); // Named mount, addressed by scheme; the scheme is case-insensitive. - vfs.Mount("game", asset::MakeLooseFileProvider(base_dir.string())); + vfs.Mount("game", asset::MakeLooseFileProvider(base_dir)); auto via_scheme = vfs.Read("GAME://Readme.TXT"); CHECK(via_scheme && AsString(*via_scheme) == "base readme"); CHECK(vfs.Contains("game://textures/rock.dds")); @@ -81,24 +113,24 @@ void TestMounts(const fs::path& tmp) { CHECK(!vfs.Contains("nosuch://readme.txt")); // Nested mount point: the provider's files appear under game://dlc/. - vfs.Mount("game://dlc/", asset::MakeLooseFileProvider(dlc_dir.string())); + vfs.Mount("game://dlc/", asset::MakeLooseFileProvider(dlc_dir)); auto extra = vfs.Read("game://dlc/extra.txt"); CHECK(extra && AsString(*extra) == "dlc extra"); CHECK(vfs.Contains("game://dlc/extra.txt")); CHECK(!vfs.Contains("dlc/extra.txt")); // not in the root namespace // A later mount on the same scheme overrides the earlier one. - vfs.Mount("game://", asset::MakeLooseFileProvider(patch_dir.string())); + vfs.Mount("game://", asset::MakeLooseFileProvider(patch_dir)); auto patched = vfs.Read("game://textures/rock.dds"); CHECK(patched && AsString(*patched) == "patched rock"); // Size without reading. - CHECK(vfs.Size("game://dlc/extra.txt") == std::optional(9)); + CHECK(vfs.Size("game://dlc/extra.txt").value_or(0) == 9); CHECK(!vfs.Size("game://dlc/none.txt")); // Enumerate emits full virtual paths; root entries stay schemeless. bool saw_root = false, saw_dlc = false, saw_game = false; - vfs.Enumerate([&](std::string_view path) { + vfs.Enumerate([&](base::StringRef path) { if (path == "readme.txt") saw_root = true; if (path == "game://dlc/extra.txt") saw_dlc = true; if (path == "game://readme.txt") saw_game = true; @@ -106,7 +138,7 @@ void TestMounts(const fs::path& tmp) { CHECK(saw_root && saw_dlc && saw_game); bool mount_scoped_dlc = false, mount_scoped_root = false; - vfs.EnumerateMount("game", [&](std::string_view path) { + vfs.EnumerateMount("game", [&](base::StringRef path) { if (path == "game://dlc/extra.txt") mount_scoped_dlc = true; if (path == "readme.txt") mount_scoped_root = true; }); @@ -116,21 +148,21 @@ void TestMounts(const fs::path& tmp) { CHECK(vfs.Unmount("game://dlc/") == 1); CHECK(!vfs.Contains("game://dlc/extra.txt")); const size_t before = vfs.mount_count(); - CHECK(vfs.UnmountByPrefix(patch_dir.string()) == 2); // root + game:// copies + CHECK(vfs.UnmountByPrefix(patch_dir) == 2); // root + game:// copies CHECK(vfs.mount_count() == before - 2); auto unpatched = vfs.Read("game://textures/rock.dds"); CHECK(unpatched && AsString(*unpatched) == "base rock"); } -void TestPackRoundTrip(const fs::path& tmp) { - fs::create_directories(tmp); - const fs::path archive = tmp / "test.rxp"; +void TestPackRoundTrip(base::StringRef tmp) { + fs::CreateDirectories(tmp); + const base::String archive = fs::Join(tmp, "test.rxp"); - std::string compressible(64 * 1024, 'a'); + base::String compressible(64 * 1024, 'a'); for (size_t i = 0; i < compressible.size(); i += 7) compressible[i] = 'b'; base::Vector incompressible(4096); - std::mt19937 rng(1234); + Mt19937 rng(1234); for (size_t i = 0; i < incompressible.size(); ++i) incompressible[i] = static_cast(rng()); @@ -138,30 +170,30 @@ void TestPackRoundTrip(const fs::path& tmp) { writer.Add("Meshes\\Rock.NIF", base::Vector(), asset::PackCompression::kStore); { base::Vector bytes(compressible.size()); - std::memcpy(bytes.data(), compressible.data(), compressible.size()); - writer.Add("textures/big.dds", std::move(bytes)); + base::MemCopy(bytes.data(), compressible.data(), compressible.size()); + writer.Add("textures/big.dds", base::move(bytes)); } - writer.Add("sound/noise.bin", std::move(incompressible)); // deflate must fall back + writer.Add("sound/noise.bin", base::move(incompressible)); // deflate must fall back { base::Vector bytes(5); - std::memcpy(bytes.data(), "wrong", 5); - writer.Add("dup.txt", std::move(bytes)); + base::MemCopy(bytes.data(), "wrong", 5); + writer.Add("dup.txt", base::move(bytes)); } { base::Vector bytes(5); - std::memcpy(bytes.data(), "right", 5); - writer.Add("DUP.txt", std::move(bytes)); // replaces: same normalized path + base::MemCopy(bytes.data(), "right", 5); + writer.Add("DUP.txt", base::move(bytes)); // replaces: same normalized path } CHECK(writer.entry_count() == 4); - CHECK(writer.WriteTo(archive.string())); + CHECK(writer.WriteTo(archive)); - base::UniquePointer pack = asset::PackFile::Open(archive.string()); + base::UniquePointer pack = asset::PackFile::Open(archive); CHECK(pack); if (!pack) return; CHECK(pack->entry_count() == 4); // Entries land under their normalized paths. - std::optional rock = pack->Find("meshes/rock.nif"); + base::Optional rock = pack->Find("meshes/rock.nif"); CHECK(rock); if (rock) { CHECK(pack->entry(*rock).size == 0); @@ -169,7 +201,7 @@ void TestPackRoundTrip(const fs::path& tmp) { CHECK(bytes && bytes->empty()); } - std::optional big = pack->Find("textures/big.dds"); + base::Optional big = pack->Find("textures/big.dds"); CHECK(big); if (big) { const asset::PackEntryView entry = pack->entry(*big); @@ -179,7 +211,7 @@ void TestPackRoundTrip(const fs::path& tmp) { CHECK(bytes && AsString(*bytes) == compressible); } - std::optional noise = pack->Find("sound/noise.bin"); + base::Optional noise = pack->Find("sound/noise.bin"); CHECK(noise); if (noise) { const asset::PackEntryView entry = pack->entry(*noise); @@ -187,7 +219,7 @@ void TestPackRoundTrip(const fs::path& tmp) { CHECK(entry.stored_size == entry.size); } - std::optional dup = pack->Find("dup.txt"); + base::Optional dup = pack->Find("dup.txt"); CHECK(dup); if (dup) { auto bytes = pack->ReadEntry(*dup); @@ -197,66 +229,68 @@ void TestPackRoundTrip(const fs::path& tmp) { // Served through the Vfs under a named mount. asset::Vfs vfs; - auto provider = asset::MakePackFileProvider(archive.string()); + auto provider = asset::MakePackFileProvider(archive); CHECK(provider); - vfs.Mount("pak", std::move(provider)); + vfs.Mount("pak", base::move(provider)); auto via_vfs = vfs.Read("pak://dup.txt"); CHECK(via_vfs && AsString(*via_vfs) == "right"); - CHECK(vfs.Size("pak://textures/big.dds") == std::optional(compressible.size())); + CHECK(vfs.Size("pak://textures/big.dds").value_or(0) == compressible.size()); } -void TestPackTamper(const fs::path& tmp) { - fs::create_directories(tmp); - const fs::path archive = tmp / "tamper.rxp"; +void TestPackTamper(base::StringRef tmp) { + fs::CreateDirectories(tmp); + const base::String archive = fs::Join(tmp, "tamper.rxp"); asset::PackWriter writer; { base::Vector bytes(11); - std::memcpy(bytes.data(), "hello world", 11); - writer.Add("a.txt", std::move(bytes), asset::PackCompression::kStore); + base::MemCopy(bytes.data(), "hello world", 11); + writer.Add("a.txt", base::move(bytes), asset::PackCompression::kStore); } - CHECK(writer.WriteTo(archive.string())); + CHECK(writer.WriteTo(archive)); // Flip one payload byte: the entry checksum must catch it. { - std::fstream f(archive, std::ios::binary | std::ios::in | std::ios::out); - f.seekp(-1, std::ios::end); - f.put('X'); + FILE* f = ::fopen(archive.c_str(), "r+b"); + ::fseek(f, -1, SEEK_END); + ::fputc('X', f); + ::fclose(f); } - base::UniquePointer pack = asset::PackFile::Open(archive.string()); + base::UniquePointer pack = asset::PackFile::Open(archive); CHECK(pack); if (pack) { - std::optional index = pack->Find("a.txt"); + base::Optional index = pack->Find("a.txt"); CHECK(index && !pack->ReadEntry(*index)); } // Corrupt the table of contents: Open must refuse the archive. { - std::fstream f(archive, std::ios::binary | std::ios::in | std::ios::out); - f.seekp(56); // inside the first TOC entry - f.put('X'); + FILE* f = ::fopen(archive.c_str(), "r+b"); + ::fseek(f, 56, SEEK_SET); // inside the first TOC entry + ::fputc('X', f); + ::fclose(f); } - CHECK(!asset::PackFile::Open(archive.string())); + CHECK(!asset::PackFile::Open(archive)); - CHECK(!asset::PackFile::Open((tmp / "missing.rxp").string())); + CHECK(!asset::PackFile::Open(fs::Join(tmp, "missing.rxp"))); } } // namespace int main() { - const fs::path tmp = fs::temp_directory_path() / "rx_vfs_test"; - fs::remove_all(tmp); - fs::create_directories(tmp); + const base::String tmp = fs::Join(fs::TempDirectory(), "rx_vfs_test"); + fs::RemoveAll(tmp); + fs::CreateDirectories(tmp); TestSplitVirtualPath(); - TestMounts(tmp / "mounts"); - TestPackRoundTrip(tmp / "pack"); - TestPackTamper(tmp / "tamper"); + TestMounts(fs::Join(tmp, "mounts")); + TestPackRoundTrip(fs::Join(tmp, "pack")); + TestPackTamper(fs::Join(tmp, "tamper")); - fs::remove_all(tmp); + fs::RemoveAll(tmp); if (failures) { - std::fprintf(stderr, "vfs_test: %d failures\n", failures); + ::fprintf(stderr, "vfs_test: %d failures\n", failures); return 1; } - std::printf("vfs_test: OK\n"); + ::printf("vfs_test: OK\n"); return 0; } diff --git a/test/walk_style_test.cc b/test/walk_style_test.cc index 85bd2434..3ce67853 100644 --- a/test/walk_style_test.cc +++ b/test/walk_style_test.cc @@ -1,10 +1,10 @@ -#include -#include -#include -#include +#include +#include #include "anim/locomotion.h" #include "asset/asset_id.h" +#include "base/strings/xstring.h" +#include "core/scalar.h" namespace { @@ -16,13 +16,13 @@ int failures = 0; void Check(bool condition, const char *message) { if (condition) return; - std::fprintf(stderr, "walk_style_test: FAIL: %s\n", message); + ::fprintf(stderr, "walk_style_test: FAIL: %s\n", message); ++failures; } f32 RotationDelta(const Quat &a, const Quat &b) { - const f32 dot = std::fabs(a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w); - return 1.0f - std::min(dot, 1.0f); + const f32 dot = ::fabs(a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w); + return 1.0f - rx::Min(dot, 1.0f); } void AddBone(asset::Skeleton *skeleton, const char *name, i32 parent, @@ -73,8 +73,8 @@ void TestPresetsAndBlend() { const f32 sway_phase = AdvancePhase(0, 1.35f, 0.25f, sway); const f32 march_phase = AdvancePhase(0, 1.35f, 0.25f, march); Check(march_phase > sway_phase, "style cadence affects phase advance"); - Check(std::string(WalkStyleName(WalkStyleKind::kHipSway)) == "Hip Sway" && - std::string(WalkStyleName(WalkStyleKind::kMarch)) == "March", + Check(base::String(WalkStyleName(WalkStyleKind::kHipSway)) == "Hip Sway" && + base::String(WalkStyleName(WalkStyleKind::kMarch)) == "March", "walk styles have stable editor-facing names"); } @@ -102,8 +102,8 @@ void TestGenesisAliases() { march_pose.rotation[hip]) * 2.0f, "Genesis aliases receive stronger Hip Sway pelvis rotation"); - Check(std::fabs(sway_pose.translation[hip].x) > - std::fabs(march_pose.translation[hip].x) * 4.0f, + Check(::fabs(sway_pose.translation[hip].x) > + ::fabs(march_pose.translation[hip].x) * 4.0f, "Genesis hip receives stronger Hip Sway lateral travel"); Check(RotationDelta(skeleton.bones[thigh].bind_rotation, march_pose.rotation[thigh]) > @@ -122,7 +122,7 @@ void TestGenesisAliases() { "styled Genesis pose produces a complete model palette"); for (const Mat4 &matrix : matrices) for (f32 value : matrix.m) - Check(std::isfinite(value), "styled pose matrices stay finite"); + Check(::isfinite(value), "styled pose matrices stay finite"); } } // namespace @@ -131,9 +131,9 @@ int main() { TestPresetsAndBlend(); TestGenesisAliases(); if (failures == 0) { - std::puts("walk_style_test: PASS"); + ::puts("walk_style_test: PASS"); return 0; } - std::fprintf(stderr, "walk_style_test: %d failure(s)\n", failures); + ::fprintf(stderr, "walk_style_test: %d failure(s)\n", failures); return 1; } diff --git a/test/weather_test.cc b/test/weather_test.cc index 39204c57..d1d9be5e 100644 --- a/test/weather_test.cc +++ b/test/weather_test.cc @@ -4,12 +4,12 @@ // lightning integrals it writes into the two renderer value structs. No GPU, no // device: everything here runs on plain scalars and the module's own PRNG. +#include "core/scalar.h" #include "weather/weather.h" -#include -#include -#include -#include +#include +#include +#include namespace { @@ -20,13 +20,13 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "weather_test: FAIL: %s\n", message); + ::fprintf(stderr, "weather_test: FAIL: %s\n", message); ++failures; } void Near(f32 actual, f32 expected, const char* message, f32 epsilon = 1e-3f) { - if (std::fabs(actual - expected) <= epsilon) return; - std::fprintf(stderr, "weather_test: FAIL: %s (got %.4f, expected %.4f)\n", message, actual, + if (::fabs(actual - expected) <= epsilon) return; + ::fprintf(stderr, "weather_test: FAIL: %s (got %.4f, expected %.4f)\n", message, actual, expected); ++failures; } @@ -94,7 +94,7 @@ void TestDeterministic() { lhs.Update(0.5f, Vec3{0, 0, 0}, 0.5f); rhs.Update(0.5f, Vec3{0, 0, 0}, 0.5f); if (lhs.active_state() != rhs.active_state() || lhs.target_state() != rhs.target_state() || - std::fabs(lhs.transition() - rhs.transition()) > 1e-6f) { + ::fabs(lhs.transition() - rhs.transition()) > 1e-6f) { identical = false; } if (lhs.active_state() != last) saw_change = true; @@ -159,8 +159,8 @@ void TestTransitionBlend() { sys.Update(2.0f, Vec3{0, 0, 0}, 0.5f); { const render::CloudscapeControls& mid = sys.cloudscape(); - f32 lo_b = std::min(s0.base_altitude, s1.base_altitude); - f32 hi_b = std::max(s0.base_altitude, s1.base_altitude); + f32 lo_b = rx::Min(s0.base_altitude, s1.base_altitude); + f32 hi_b = rx::Max(s0.base_altitude, s1.base_altitude); Check(mid.bottom > lo_b && mid.bottom < hi_b, "shell base blends through the transition"); Check(mid.darkness > s0.darkness && mid.darkness < s1.darkness, "darkness blends through the transition"); @@ -254,7 +254,7 @@ void TestWindYawWrap() { sys.ForceState(0, 0.0f); sys.ForceState(1, 4.0f); sys.Update(2.0f, Vec3{0, 0, 0}, 0.5f); - Check(std::fabs(sys.cloudscape().wind_yaw) > 3.0f, + Check(::fabs(sys.cloudscape().wind_yaw) > 3.0f, "wind yaw crosses the wrap without reversing through zero"); } @@ -323,7 +323,7 @@ void TestInvalidRegionInputs() { sys.AddState(global); sys.AddState(regional); WeatherRegion invalid; - invalid.min_xz = {std::numeric_limits::quiet_NaN(), -10.0f}; + invalid.min_xz = {NAN, -10.0f}; invalid.max_xz = {10.0f, 10.0f}; invalid.states.push_back(1u); sys.AddRegion(invalid); @@ -343,7 +343,7 @@ void TestInvalidRegionInputs() { region.min_xz = {-10.0f, -10.0f}; region.max_xz = {10.0f, 10.0f}; region.states.push_back(1u); - region.weights.push_back(std::numeric_limits::infinity()); + region.weights.push_back(INFINITY); sys.AddRegion(region); sys.ForceState(0, 0.0f); sys.ClearForced(); @@ -423,7 +423,7 @@ void TestInvalidDeltaTime() { sys.Update(1.0f, Vec3{0, 0, 0}, 0.5f); f32 offset = sys.cloudscape().map_offset.x; sys.Update(-1.0f, Vec3{0, 0, 0}, 0.5f); - sys.Update(std::numeric_limits::quiet_NaN(), Vec3{0, 0, 0}, 0.5f); + sys.Update(NAN, Vec3{0, 0, 0}, 0.5f); Near(sys.cloudscape().map_offset.x, offset, "negative and non-finite delta time leave integrals unchanged"); } @@ -432,27 +432,27 @@ void TestHugeDeltaTimeIsBounded() { WeatherSystem sys(21u); sys.AddState(Clear(1)); sys.ForceState(0, 0.0f); - sys.Update(std::numeric_limits::max(), Vec3{0, 0, 0}, 0.5f); - Check(std::isfinite(sys.cloudscape().map_offset.x), + sys.Update(FLT_MAX, Vec3{0, 0, 0}, 0.5f); + Check(::isfinite(sys.cloudscape().map_offset.x), "a huge finite delta is bounded and returns finite output"); } void TestStateValidation() { WeatherSystem sys(18u); WeatherState invalid = Storm(1); - invalid.coverage = std::numeric_limits::quiet_NaN(); + invalid.coverage = NAN; invalid.density = -2.0f; invalid.base_altitude = 5000.0f; invalid.top_altitude = 1000.0f; invalid.fog_height = -4.0f; sys.AddState(invalid); sys.SetGroundHeight([](f32, f32) { - return std::numeric_limits::quiet_NaN(); + return NAN; }); sys.ForceState(0, 0.0f); sys.Update(0.0f, Vec3{0, 0, 0}, 0.5f); const render::CloudscapeControls& controls = sys.cloudscape(); - Check(std::isfinite(controls.map_a.coverage) && controls.map_a.coverage == 0.0f, + Check(::isfinite(controls.map_a.coverage) && controls.map_a.coverage == 0.0f, "non-finite state controls are sanitized at registration"); Check(controls.density == 0.0f, "negative density is clamped"); Check(controls.top > controls.bottom, "cloud shell keeps a positive thickness"); @@ -532,7 +532,7 @@ void TestLightning() { if (w.strike_age >= 0.0f) { struck = true; f32 dx = w.strike_pos.x - player.x, dz = w.strike_pos.z - player.z; - f32 d = std::sqrt(dx * dx + dz * dz); + f32 d = ::sqrt(dx * dx + dz * dz); if (d < 90.0f || d > 320.0f) in_range = false; } if (w.lightning > 0.0f) flashed = true; @@ -607,9 +607,9 @@ int main() { TestTornadoLifecycle(); if (failures == 0) { - std::printf("weather_test: OK\n"); + ::printf("weather_test: OK\n"); return 0; } - std::fprintf(stderr, "weather_test: %d failure(s)\n", failures); + ::fprintf(stderr, "weather_test: %d failure(s)\n", failures); return failures; } diff --git a/test/world_bake_test.cc b/test/world_bake_test.cc index 9136ab3c..46e93409 100644 --- a/test/world_bake_test.cc +++ b/test/world_bake_test.cc @@ -5,22 +5,23 @@ // apart in a way neither half's own tests can see. // // argv[1] is the rxworld binary (CMake passes $). -#include -#include -#include -#include -#include +#include +#include #include "asset/pack.h" #include "asset/vfs.h" +#include "base/containers/span.h" +#include "base/memory/move.h" +#include "base/strings/xstring.h" #include "ecs/world.h" #include "scene/components.h" #include "world/world_map.h" #include "world/world_stream.h" +#include "core/file_system.h" namespace { -namespace fs = std::filesystem; +namespace fs = rx::fs; using namespace rx::world; using rx::asset::Vfs; using rx::f32; @@ -35,7 +36,7 @@ int g_failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++g_failures; \ } \ } while (0) @@ -81,7 +82,7 @@ Renderable.mesh = "meshes/tree.gltf" void Tick(WorldStreamer* streamer, const WorldStreamObservation& observer, u32 count) { for (u32 i = 0; i < count; ++i) { - streamer->Update(std::span(&observer, 1)); + streamer->Update(base::Span(&observer, 1)); } } @@ -108,41 +109,37 @@ WorldStreamPolicy Policy(f32 load, f32 retain) { int main(int argc, char** argv) { if (argc < 2) { - std::fprintf(stderr, "usage: world_bake_test \n"); + ::fprintf(stderr, "usage: world_bake_test \n"); return 2; } - const std::string rxworld = argv[1]; - - const fs::path tmp = fs::temp_directory_path() / "rx_world_bake_test"; - fs::remove_all(tmp); - fs::create_directories(tmp); - const fs::path scene = tmp / "town.rxscene"; - const fs::path archive = tmp / "town.rxp"; - { - std::ofstream file(scene); - file << kScene; - CHECK(file.good()); - } - - const std::string command = "\"" + rxworld + "\" bake \"" + scene.string() + "\" \"" + - archive.string() + "\" --name town --cell-size 32"; - const int status = std::system(command.c_str()); + const base::String rxworld = argv[1]; + + const base::String tmp = fs::Join(fs::TempDirectory(), "rx_world_bake_test"); + fs::RemoveAll(tmp); + fs::CreateDirectories(tmp); + const base::String scene = fs::Join(tmp, "town.rxscene"); + const base::String archive = fs::Join(tmp, "town.rxp"); + CHECK(fs::WriteTextFile(scene, kScene)); + + const base::String command = "\"" + rxworld + "\" bake \"" + scene + "\" \"" + + archive + "\" --name town --cell-size 32"; + const int status = ::system(command.c_str()); if (status != 0) { - std::fprintf(stderr, "FAIL: rxworld bake exited %d\n", status); - fs::remove_all(tmp); + ::fprintf(stderr, "FAIL: rxworld bake exited %d\n", status); + fs::RemoveAll(tmp); return 1; } - CHECK(fs::exists(archive)); + CHECK(fs::Exists(archive)); // Everything past here is the engine reading what the tool wrote. Vfs vfs; - auto provider = rx::asset::MakePackFileProvider(archive.string()); + auto provider = rx::asset::MakePackFileProvider(archive); CHECK(provider != nullptr); if (!provider) return 1; - vfs.Mount("world", std::move(provider)); + vfs.Mount("world", base::move(provider)); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://town/town.rxworld", &error)); CHECK(error.empty()); CHECK(map.index().cells.size() == 2); @@ -163,8 +160,8 @@ int main(int argc, char** argv) { Tick(&streamer, At(10, 10), 30); CHECK(streamer.errors().empty()); if (!streamer.errors().empty()) { - for (const std::string& message : streamer.errors()) { - std::fprintf(stderr, " error: %s\n", message.c_str()); + for (const base::String& message : streamer.errors()) { + ::fprintf(stderr, " error: %s\n", message.c_str()); } } // Three plain entities became ECS rows; the two renderables became instance @@ -209,15 +206,15 @@ int main(int argc, char** argv) { // Re-baking the same input must produce the same bake id, or every overlay // and save keyed to it is invalidated by a no-op rebuild. - const fs::path second = tmp / "town2.rxp"; - const std::string again = "\"" + rxworld + "\" bake \"" + scene.string() + "\" \"" + - second.string() + "\" --name town --cell-size 32"; - CHECK(std::system(again.c_str()) == 0); + const base::String second = fs::Join(tmp, "town2.rxp"); + const base::String again = "\"" + rxworld + "\" bake \"" + scene + "\" \"" + + second + "\" --name town --cell-size 32"; + CHECK(::system(again.c_str()) == 0); Vfs second_vfs; - auto second_provider = rx::asset::MakePackFileProvider(second.string()); + auto second_provider = rx::asset::MakePackFileProvider(second); CHECK(second_provider != nullptr); if (second_provider) { - second_vfs.Mount("world", std::move(second_provider)); + second_vfs.Mount("world", base::move(second_provider)); WorldMap second_map; CHECK(second_map.Load(second_vfs, "world://town/town.rxworld", &error)); CHECK(second_map.index().bake_id == map.index().bake_id); @@ -226,24 +223,24 @@ int main(int argc, char** argv) { // A different cook of the same scene is a different bake, so its payloads // must not be readable through the first index. - const fs::path other = tmp / "town3.rxp"; - const std::string different = "\"" + rxworld + "\" bake \"" + scene.string() + "\" \"" + - other.string() + "\" --name town --cell-size 64"; - CHECK(std::system(different.c_str()) == 0); + const base::String other = fs::Join(tmp, "town3.rxp"); + const base::String different = "\"" + rxworld + "\" bake \"" + scene + "\" \"" + + other + "\" --name town --cell-size 64"; + CHECK(::system(different.c_str()) == 0); Vfs other_vfs; - auto other_provider = rx::asset::MakePackFileProvider(other.string()); + auto other_provider = rx::asset::MakePackFileProvider(other); if (other_provider) { - other_vfs.Mount("world", std::move(other_provider)); + other_vfs.Mount("world", base::move(other_provider)); WorldMap other_map; CHECK(other_map.Load(other_vfs, "world://town/town.rxworld", &error)); CHECK(other_map.index().bake_id != map.index().bake_id); } - fs::remove_all(tmp); + fs::RemoveAll(tmp); if (g_failures) { - std::fprintf(stderr, "world_bake_test: %d failure(s)\n", g_failures); + ::fprintf(stderr, "world_bake_test: %d failure(s)\n", g_failures); return 1; } - std::puts("world_bake_test: ok"); + ::puts("world_bake_test: ok"); return 0; } diff --git a/test/world_classify_test.cc b/test/world_classify_test.cc index d419af32..a5649437 100644 --- a/test/world_classify_test.cc +++ b/test/world_classify_test.cc @@ -7,9 +7,9 @@ // is pinned here rather than left to be checked by baking things and looking. #include "world/world_bake.h" -#include -#include +#include +#include "base/strings/xstring.h" #include "ecs/world.h" #include "scene/components.h" @@ -32,7 +32,7 @@ int g_failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++g_failures; \ } \ } while (0) @@ -86,7 +86,7 @@ void TestClassifyIgnoresWhatTheDropWouldRemove() { rx::ecs::World world; const WorldBakeOptions options = Options(); - // Name holds a std::string, so it cannot be baked and is dropped. The entity + // Name holds a base::String, so it cannot be baked and is dropped. The entity // is still an entity: classifying after the drop would quietly turn a named, // authored thing into a page row with no identity at all. const Entity named = world.Create(); @@ -113,7 +113,7 @@ void TestRefusals() { world.Add(child, Parent{parent}); const BakeVerdict parented = ClassifyForBake(world, child, options); CHECK(parented.role == BakeRole::kRefused); - CHECK(parented.refusal.find("Parent") != std::string::npos); + CHECK(parented.refusal.find("Parent") != base::String::npos); // No Transform: the cook has nowhere to put it. const Entity placeless = world.Create(); @@ -205,9 +205,9 @@ int main() { TestCellAndBounds(); TestInstanceSetIsConfigurable(); if (g_failures) { - std::fprintf(stderr, "world_classify_test: %d failure(s)\n", g_failures); + ::fprintf(stderr, "world_classify_test: %d failure(s)\n", g_failures); return 1; } - std::puts("world_classify_test: ok"); + ::puts("world_classify_test: ok"); return 0; } diff --git a/test/world_format_test.cc b/test/world_format_test.cc index c7612f9b..0eb61248 100644 --- a/test/world_format_test.cc +++ b/test/world_format_test.cc @@ -1,12 +1,15 @@ // The baked map formats: round trip, the cook-time consistency checks that keep // a broken world from ever reaching an archive, and the load-time checks that // make a corrupted or stale one fail loudly instead of materializing garbage. +#include "base/containers/span.h" +#include "base/memory/mem_ops.h" +#include "base/strings/string_ref.h" +#include "base/strings/xstring.h" #include "world/world_format.h" -#include -#include -#include -#include +#include +#include +#include namespace { @@ -23,19 +26,19 @@ int g_failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++g_failures; \ } \ } while (0) -void CheckRejected(bool decoded, const std::string& error, const char* what) { +void CheckRejected(bool decoded, const base::String& error, const char* what) { if (decoded) { - std::fprintf(stderr, "FAIL: %s was accepted\n", what); + ::fprintf(stderr, "FAIL: %s was accepted\n", what); ++g_failures; return; } if (error.empty()) { - std::fprintf(stderr, "FAIL: %s was rejected without a message\n", what); + ::fprintf(stderr, "FAIL: %s was rejected without a message\n", what); ++g_failures; } } @@ -79,12 +82,12 @@ void TestIndexRoundTrip() { writer.AddPayload(3, Domain::kGameplay, Tier::kStandard, 5000, 13); base::Vector bytes; - std::string error; + base::String error; CHECK(writer.Encode(&bytes, &error)); CHECK(error.empty()); WorldIndexData index; - CHECK(DecodeWorldIndex(std::span(bytes.data(), bytes.size()), &index, &error)); + CHECK(DecodeWorldIndex(base::Span(bytes.data(), bytes.size()), &index, &error)); CHECK(error.empty()); CHECK(index.world_id == 0xfeedu); CHECK(index.bake_id == kBakeId); @@ -139,12 +142,12 @@ void TestStableIdLookupIgnoresCellOrder() { writer.AddPayload(1, Domain::kGameplay, Tier::kStandard, 16, 1); base::Vector bytes; - std::string error; + base::String error; CHECK(writer.Encode(&bytes, &error)); CHECK(error.empty()); WorldIndexData index; - CHECK(DecodeWorldIndex(std::span(bytes.data(), bytes.size()), &index, &error)); + CHECK(DecodeWorldIndex(base::Span(bytes.data(), bytes.size()), &index, &error)); CHECK(index.stable_id_order.size() == 3); // only the cells that own ids CHECK(index.FindCellByStableId(950) == index.FindCell(1)); @@ -160,7 +163,7 @@ void TestStableIdLookupIgnoresCellOrder() { } void TestIndexRefusesInconsistentWorlds() { - std::string error; + base::String error; base::Vector bytes; { WorldIndexWriter writer; @@ -203,41 +206,41 @@ void TestIndexRefusesCorruptedBytes() { writer.AddCell(2, {16, 0, 0}, {32, 16, 16}, 0, 4, 4); writer.AddPayload(1, Domain::kGameplay, Tier::kStandard, 64, 4); base::Vector good; - std::string error; + base::String error; CHECK(writer.Encode(&good, &error)); WorldIndexData index; { base::Vector bad(good); bad[0] = 'X'; - CheckRejected(DecodeWorldIndex(std::span(bad.data(), bad.size()), &index, &error), + CheckRejected(DecodeWorldIndex(base::Span(bad.data(), bad.size()), &index, &error), error, "a bad magic"); } { base::Vector bad(good); bad[8] = 9; // version - CheckRejected(DecodeWorldIndex(std::span(bad.data(), bad.size()), &index, &error), + CheckRejected(DecodeWorldIndex(base::Span(bad.data(), bad.size()), &index, &error), error, "a future version"); } { // Flip a byte in the body: the checksum must catch it. base::Vector bad(good); bad[bad.size() - 1] ^= 0xff; - CheckRejected(DecodeWorldIndex(std::span(bad.data(), bad.size()), &index, &error), + CheckRejected(DecodeWorldIndex(base::Span(bad.data(), bad.size()), &index, &error), error, "a flipped body byte"); } { base::Vector bad(good); bad.erase(bad.end() - 1); - CheckRejected(DecodeWorldIndex(std::span(bad.data(), bad.size()), &index, &error), + CheckRejected(DecodeWorldIndex(base::Span(bad.data(), bad.size()), &index, &error), error, "a truncated body"); } { base::Vector bad; - CheckRejected(DecodeWorldIndex(std::span(bad.data(), bad.size()), &index, &error), + CheckRejected(DecodeWorldIndex(base::Span(bad.data(), bad.size()), &index, &error), error, "an empty file"); } - CHECK(DecodeWorldIndex(std::span(good.data(), good.size()), &index, &error)); + CHECK(DecodeWorldIndex(base::Span(good.data(), good.size()), &index, &error)); } void TestEntityPayloadRoundTrip() { @@ -250,16 +253,16 @@ void TestEntityPayloadRoundTrip() { const u32 archetype = writer.BeginArchetype(3); const base::Vector column = BytesOf(positions, sizeof(positions)); writer.AddColumn(archetype, "Position", sizeof(Position), layout, - std::span(column.data(), column.size())); - writer.SetStableIds(archetype, std::span(ids, 3)); + base::Span(column.data(), column.size())); + writer.SetStableIds(archetype, base::Span(ids, 3)); base::Vector bytes; - std::string error; + base::String error; CHECK(writer.Encode(&bytes, &error)); CHECK(error.empty()); WorldCellPayload payload; - CHECK(DecodeCellPayload(std::span(bytes.data(), bytes.size()), &payload, &error)); + CHECK(DecodeCellPayload(base::Span(bytes.data(), bytes.size()), &payload, &error)); CHECK(payload.kind == PayloadKind::kEntities); CHECK(payload.cell_id == 42); CHECK(payload.bake_id == kBakeId); @@ -275,11 +278,11 @@ void TestEntityPayloadRoundTrip() { CHECK(payload.columns[0].stride == sizeof(Position)); CHECK(payload.columns[0].layout_hash == layout); - const std::span decoded = payload.ColumnBytes(payload.columns[0]); + const base::Span decoded = payload.ColumnBytes(payload.columns[0]); CHECK(decoded.size() == sizeof(positions)); - CHECK(std::memcmp(decoded.data(), positions, sizeof(positions)) == 0); + CHECK(base::MemCompare(decoded.data(), positions, sizeof(positions)) == 0); - const std::span decoded_ids = payload.StableIds(payload.archetypes[0]); + const base::Span decoded_ids = payload.StableIds(payload.archetypes[0]); CHECK(decoded_ids.size() == 3); CHECK(decoded_ids.size() == 3 && decoded_ids[0] == 10 && decoded_ids[2] == 12); } @@ -294,11 +297,11 @@ void TestInstancePayloadRoundTrip() { writer.AddInstance(501, tree, {4, 5, 6}, {0, 0.7071f, 0, 0.7071f}, 2.0f); base::Vector bytes; - std::string error; + base::String error; CHECK(writer.Encode(&bytes, &error)); WorldCellPayload payload; - CHECK(DecodeCellPayload(std::span(bytes.data(), bytes.size()), &payload, &error)); + CHECK(DecodeCellPayload(base::Span(bytes.data(), bytes.size()), &payload, &error)); CHECK(payload.kind == PayloadKind::kInstances); CHECK(payload.prototypes.size() == 2); CHECK(payload.instances.size() == 2); @@ -313,7 +316,7 @@ void TestInstancePayloadRoundTrip() { void TestPayloadRefusesInconsistentContent() { base::Vector bytes; - std::string error; + base::String error; const u64 layout = HashComponentLayout("Position", sizeof(Position), {}, {}, {}); const Position positions[2] = {{}, {}}; const base::Vector two_rows = BytesOf(positions, sizeof(positions)); @@ -323,43 +326,43 @@ void TestPayloadRefusesInconsistentContent() { CellPayloadWriter writer(1, Domain::kGameplay, Tier::kStandard); const u32 archetype = writer.BeginArchetype(3); // three rows, two rows of bytes writer.AddColumn(archetype, "Position", sizeof(Position), layout, - std::span(two_rows.data(), two_rows.size())); + base::Span(two_rows.data(), two_rows.size())); const u64 three[3] = {1, 2, 3}; - writer.SetStableIds(archetype, std::span(three, 3)); + writer.SetStableIds(archetype, base::Span(three, 3)); CheckRejected(writer.Encode(&bytes, &error), error, "a column short of its row count"); } { CellPayloadWriter writer(1, Domain::kGameplay, Tier::kStandard); const u32 archetype = writer.BeginArchetype(2); writer.AddColumn(archetype, "Position", sizeof(Position), layout, - std::span(two_rows.data(), two_rows.size())); + base::Span(two_rows.data(), two_rows.size())); CheckRejected(writer.Encode(&bytes, &error), error, "an archetype with no stable ids"); } { CellPayloadWriter writer(1, Domain::kGameplay, Tier::kStandard); const u32 archetype = writer.BeginArchetype(2); writer.AddColumn(archetype, "Position", sizeof(Position), layout, - std::span(two_rows.data(), two_rows.size())); + base::Span(two_rows.data(), two_rows.size())); writer.AddColumn(archetype, "Position", sizeof(Position), layout, - std::span(two_rows.data(), two_rows.size())); - writer.SetStableIds(archetype, std::span(ids, 2)); + base::Span(two_rows.data(), two_rows.size())); + writer.SetStableIds(archetype, base::Span(ids, 2)); CheckRejected(writer.Encode(&bytes, &error), error, "one component listed twice"); } { CellPayloadWriter writer(1, Domain::kGameplay, Tier::kStandard); const u32 archetype = writer.BeginArchetype(2); writer.AddColumn(archetype, "Position", sizeof(Position), layout, - std::span(two_rows.data(), two_rows.size())); + base::Span(two_rows.data(), two_rows.size())); const u64 duplicated[2] = {5, 5}; - writer.SetStableIds(archetype, std::span(duplicated, 2)); + writer.SetStableIds(archetype, base::Span(duplicated, 2)); CheckRejected(writer.Encode(&bytes, &error), error, "a duplicated stable id"); } { CellPayloadWriter writer(1, Domain::kGameplay, Tier::kStandard); const u32 archetype = writer.BeginArchetype(2); writer.AddColumn(archetype, "Position", sizeof(Position), layout, - std::span(two_rows.data(), two_rows.size())); - writer.SetStableIds(archetype, std::span(ids, 2)); + base::Span(two_rows.data(), two_rows.size())); + writer.SetStableIds(archetype, base::Span(ids, 2)); writer.AddInstance(1, writer.AddPrototype("prop/rock"), {}, {}, 1); CheckRejected(writer.Encode(&bytes, &error), error, "entity and instance content mixed"); } @@ -374,17 +377,17 @@ void TestPayloadRefusesCorruptedBytes() { const u32 archetype = writer.BeginArchetype(2); writer.AddColumn(archetype, "Position", sizeof(Position), HashComponentLayout("Position", sizeof(Position), {}, {}, {}), - std::span(column.data(), column.size())); - writer.SetStableIds(archetype, std::span(ids, 2)); + base::Span(column.data(), column.size())); + writer.SetStableIds(archetype, base::Span(ids, 2)); base::Vector good; - std::string error; + base::String error; CHECK(writer.Encode(&good, &error)); WorldCellPayload payload; { base::Vector bad(good); bad[3] = 'X'; - CheckRejected(DecodeCellPayload(std::span(bad.data(), bad.size()), &payload, &error), + CheckRejected(DecodeCellPayload(base::Span(bad.data(), bad.size()), &payload, &error), error, "a payload with a bad magic"); } { @@ -392,22 +395,22 @@ void TestPayloadRefusesCorruptedBytes() { // otherwise be copied into an entity column verbatim. base::Vector bad(good); bad[bad.size() - 5] ^= 0x01; - CheckRejected(DecodeCellPayload(std::span(bad.data(), bad.size()), &payload, &error), + CheckRejected(DecodeCellPayload(base::Span(bad.data(), bad.size()), &payload, &error), error, "a flipped byte in the data section"); } { base::Vector bad(good); bad.erase(bad.end() - 4); - CheckRejected(DecodeCellPayload(std::span(bad.data(), bad.size()), &payload, &error), + CheckRejected(DecodeCellPayload(base::Span(bad.data(), bad.size()), &payload, &error), error, "a truncated payload"); } { base::Vector bad; bad.insert(bad.end(), good.begin(), good.begin() + 10); - CheckRejected(DecodeCellPayload(std::span(bad.data(), bad.size()), &payload, &error), + CheckRejected(DecodeCellPayload(base::Span(bad.data(), bad.size()), &payload, &error), error, "a payload cut off inside its header"); } - CHECK(DecodeCellPayload(std::span(good.data(), good.size()), &payload, &error)); + CHECK(DecodeCellPayload(base::Span(good.data(), good.size()), &payload, &error)); } // The checksum now covers the header, so a structural field cannot be edited @@ -450,10 +453,10 @@ void TestPayloadRefusesCraftedStructure() { const u32 archetype = writer.BeginArchetype(2); writer.AddColumn(archetype, "Position", sizeof(Position), HashComponentLayout("Position", sizeof(Position), {}, {}, {}), - std::span(column.data(), column.size())); - writer.SetStableIds(archetype, std::span(ids, 2)); + base::Span(column.data(), column.size())); + writer.SetStableIds(archetype, base::Span(ids, 2)); base::Vector good; - std::string error; + base::String error; CHECK(writer.Encode(&good, &error)); WorldCellPayload payload; @@ -463,21 +466,21 @@ void TestPayloadRefusesCraftedStructure() { base::Vector bad(good); WriteU32(&bad, kPayloadHeaderBytes + 4, 1); // column_first past the single column RepairChecksum(&bad, kPayloadHeaderBytes); - CheckRejected(DecodeCellPayload(std::span(bad.data(), bad.size()), &payload, &error), + CheckRejected(DecodeCellPayload(base::Span(bad.data(), bad.size()), &payload, &error), error, "an archetype whose columns start past the table"); } { base::Vector bad(good); WriteU32(&bad, kPayloadHeaderBytes + 8, 2); // two columns where one exists RepairChecksum(&bad, kPayloadHeaderBytes); - CheckRejected(DecodeCellPayload(std::span(bad.data(), bad.size()), &payload, &error), + CheckRejected(DecodeCellPayload(base::Span(bad.data(), bad.size()), &payload, &error), error, "an archetype claiming more columns than exist"); } { base::Vector bad(good); WriteU32(&bad, kPayloadHeaderBytes, 3); // three rows of a two-row column RepairChecksum(&bad, kPayloadHeaderBytes); - CheckRejected(DecodeCellPayload(std::span(bad.data(), bad.size()), &payload, &error), + CheckRejected(DecodeCellPayload(base::Span(bad.data(), bad.size()), &payload, &error), error, "a row count its columns cannot fill"); } { @@ -485,28 +488,28 @@ void TestPayloadRefusesCraftedStructure() { base::Vector bad(good); WriteU32(&bad, kPayloadHeaderBytes + 20, 9999); RepairChecksum(&bad, kPayloadHeaderBytes); - CheckRejected(DecodeCellPayload(std::span(bad.data(), bad.size()), &payload, &error), + CheckRejected(DecodeCellPayload(base::Span(bad.data(), bad.size()), &payload, &error), error, "a column name outside the string table"); } { base::Vector bad(good); WriteU32(&bad, 12, 7); // kind RepairChecksum(&bad, kPayloadHeaderBytes); - CheckRejected(DecodeCellPayload(std::span(bad.data(), bad.size()), &payload, &error), + CheckRejected(DecodeCellPayload(base::Span(bad.data(), bad.size()), &payload, &error), error, "an unknown payload kind"); } { base::Vector bad(good); WriteU32(&bad, 12, static_cast(PayloadKind::kInstances)); RepairChecksum(&bad, kPayloadHeaderBytes); - CheckRejected(DecodeCellPayload(std::span(bad.data(), bad.size()), &payload, &error), + CheckRejected(DecodeCellPayload(base::Span(bad.data(), bad.size()), &payload, &error), error, "an instance payload carrying entity tables"); } { base::Vector bad(good); bad[8] = 2; // version RepairChecksum(&bad, kPayloadHeaderBytes); - CheckRejected(DecodeCellPayload(std::span(bad.data(), bad.size()), &payload, &error), + CheckRejected(DecodeCellPayload(base::Span(bad.data(), bad.size()), &payload, &error), error, "a future payload version"); } { @@ -516,7 +519,7 @@ void TestPayloadRefusesCraftedStructure() { base::Vector bad(good); for (u32 i = 0; i < 8; ++i) bad[bad.size() - 8 + i] = bad[bad.size() - 16 + i]; RepairChecksum(&bad, kPayloadHeaderBytes); - CheckRejected(DecodeCellPayload(std::span(bad.data(), bad.size()), &payload, &error), + CheckRejected(DecodeCellPayload(base::Span(bad.data(), bad.size()), &payload, &error), error, "two rows sharing a stable id"); } { @@ -526,7 +529,7 @@ void TestPayloadRefusesCraftedStructure() { base::Vector bad(good); WriteU32(&bad, kPayloadHeaderBytes, 0xffffffffu); // row_count RepairChecksum(&bad, kPayloadHeaderBytes); - CheckRejected(DecodeCellPayload(std::span(bad.data(), bad.size()), &payload, &error), + CheckRejected(DecodeCellPayload(base::Span(bad.data(), bad.size()), &payload, &error), error, "more rows than a cell may hold"); } { @@ -534,10 +537,10 @@ void TestPayloadRefusesCraftedStructure() { // than reaching the cross-check that used to be its only guard. base::Vector bad(good); bad[24] ^= 0xff; - CheckRejected(DecodeCellPayload(std::span(bad.data(), bad.size()), &payload, &error), + CheckRejected(DecodeCellPayload(base::Span(bad.data(), bad.size()), &payload, &error), error, "a header field edited without repairing the checksum"); } - CHECK(DecodeCellPayload(std::span(good.data(), good.size()), &payload, &error)); + CHECK(DecodeCellPayload(base::Span(good.data(), good.size()), &payload, &error)); } // The grid fields sit in the header: cell_size at 32, grid_origin at 36..48. @@ -549,11 +552,11 @@ void TestIndexRefusesCorruptGrid() { writer.set_grid(64.0f, {1, 2, 3}); writer.AddCell(1, {}, {16, 16, 16}, 0, 0, 4); base::Vector good; - std::string error; + base::String error; CHECK(writer.Encode(&good, &error)); WorldIndexData index; - CHECK(DecodeWorldIndex(std::span(good.data(), good.size()), &index, &error)); + CHECK(DecodeWorldIndex(base::Span(good.data(), good.size()), &index, &error)); CHECK(index.cell_size == 64.0f); const u32 kNaN = 0x7fc00000; @@ -561,21 +564,21 @@ void TestIndexRefusesCorruptGrid() { base::Vector bad(good); WriteU32(&bad, 32, kNaN); RepairChecksum(&bad, kIndexHeaderBytes); - CheckRejected(DecodeWorldIndex(std::span(bad.data(), bad.size()), &index, &error), + CheckRejected(DecodeWorldIndex(base::Span(bad.data(), bad.size()), &index, &error), error, "a non-finite grid cell size"); } { base::Vector bad(good); WriteU32(&bad, 32, 0xbf800000); // -1.0f RepairChecksum(&bad, kIndexHeaderBytes); - CheckRejected(DecodeWorldIndex(std::span(bad.data(), bad.size()), &index, &error), + CheckRejected(DecodeWorldIndex(base::Span(bad.data(), bad.size()), &index, &error), error, "a negative grid cell size"); } { base::Vector bad(good); WriteU32(&bad, 40, kNaN); // grid_origin.y RepairChecksum(&bad, kIndexHeaderBytes); - CheckRejected(DecodeWorldIndex(std::span(bad.data(), bad.size()), &index, &error), + CheckRejected(DecodeWorldIndex(base::Span(bad.data(), bad.size()), &index, &error), error, "a non-finite grid origin"); } { @@ -585,7 +588,7 @@ void TestIndexRefusesCorruptGrid() { WriteU32(&fine, 32, 0); RepairChecksum(&fine, kIndexHeaderBytes); WorldIndexData off_grid; - CHECK(DecodeWorldIndex(std::span(fine.data(), fine.size()), &off_grid, &error)); + CHECK(DecodeWorldIndex(base::Span(fine.data(), fine.size()), &off_grid, &error)); CHECK(off_grid.cell_size == 0.0f); } @@ -593,14 +596,14 @@ void TestIndexRefusesCorruptGrid() { // an archive its own loader will not open. { WorldIndexWriter nan_grid; - nan_grid.set_grid(std::nanf(""), {}); + nan_grid.set_grid(::nanf(""), {}); nan_grid.AddCell(1, {}, {16, 16, 16}, 0, 0, 4); base::Vector bytes; CheckRejected(nan_grid.Encode(&bytes, &error), error, "encoding a non-finite grid cell size"); } { WorldIndexWriter nan_bounds; - nan_bounds.AddCell(1, {}, {std::nanf(""), 16, 16}, 0, 0, 4); + nan_bounds.AddCell(1, {}, {::nanf(""), 16, 16}, 0, 0, 4); base::Vector bytes; CheckRejected(nan_bounds.Encode(&bytes, &error), error, "encoding non-finite cell bounds"); } @@ -610,7 +613,7 @@ void TestIndexRefusesCorruptGrid() { // the false return holding half a world stamped with the bad file's bake id, // which every cross-check downstream then accepts. void TestRefusedDecodeLeavesTheOutputAlone() { - std::string error; + base::String error; WorldIndexWriter writer; writer.set_world_id(0x1234); @@ -621,13 +624,13 @@ void TestRefusedDecodeLeavesTheOutputAlone() { CHECK(writer.Encode(&good, &error)); WorldIndexData index; - CHECK(DecodeWorldIndex(std::span(good.data(), good.size()), &index, &error)); + CHECK(DecodeWorldIndex(base::Span(good.data(), good.size()), &index, &error)); CHECK(index.cells.size() == 2); base::Vector bad(good); WriteU32(&bad, kIndexHeaderBytes + 16, 0x7fc00000); // cell 1 minimum.z = NaN RepairChecksum(&bad, kIndexHeaderBytes); - CheckRejected(DecodeWorldIndex(std::span(bad.data(), bad.size()), &index, &error), + CheckRejected(DecodeWorldIndex(base::Span(bad.data(), bad.size()), &index, &error), error, "an index with non-finite bounds"); CHECK(index.cells.size() == 2); CHECK(index.world_id == 0x1234); @@ -640,14 +643,14 @@ void TestRefusedDecodeLeavesTheOutputAlone() { CHECK(payload_writer.Encode(&payload_bytes, &error)); WorldCellPayload payload; - CHECK(DecodeCellPayload(std::span(payload_bytes.data(), payload_bytes.size()), &payload, + CHECK(DecodeCellPayload(base::Span(payload_bytes.data(), payload_bytes.size()), &payload, &error)); CHECK(payload.instances.size() == 1); base::Vector truncated(payload_bytes); truncated.pop_back(); CheckRejected( - DecodeCellPayload(std::span(truncated.data(), truncated.size()), &payload, &error), + DecodeCellPayload(base::Span(truncated.data(), truncated.size()), &payload, &error), error, "a truncated payload"); CHECK(payload.instances.size() == 1); CHECK(payload.cell_id == 1); @@ -660,7 +663,7 @@ void TestIndexRefusesCraftedStructure() { writer.AddCell(2, {16, 0, 0}, {32, 16, 16}, 0, 4, 4); writer.AddPayload(1, Domain::kGameplay, Tier::kStandard, 64, 4); base::Vector good; - std::string error; + base::String error; CHECK(writer.Encode(&good, &error)); WorldIndexData index; @@ -669,7 +672,7 @@ void TestIndexRefusesCraftedStructure() { base::Vector bad(good); WriteU32(&bad, kIndexHeaderBytes + 52, 5); // payload_first past the table RepairChecksum(&bad, kIndexHeaderBytes); - CheckRejected(DecodeWorldIndex(std::span(bad.data(), bad.size()), &index, &error), + CheckRejected(DecodeWorldIndex(base::Span(bad.data(), bad.size()), &index, &error), error, "a cell spanning past the payload table"); } { @@ -677,21 +680,21 @@ void TestIndexRefusesCraftedStructure() { // Give cell 1 a range that swallows cell 2's. WriteU32(&bad, kIndexHeaderBytes + 48, 100); // stable_id_count RepairChecksum(&bad, kIndexHeaderBytes); - CheckRejected(DecodeWorldIndex(std::span(bad.data(), bad.size()), &index, &error), + CheckRejected(DecodeWorldIndex(base::Span(bad.data(), bad.size()), &index, &error), error, "overlapping stable-id ranges on the read side"); } { base::Vector bad(good); WriteU32(&bad, kIndexHeaderBytes + 16, 0x7fc00000); // minimum.z = NaN RepairChecksum(&bad, kIndexHeaderBytes); - CheckRejected(DecodeWorldIndex(std::span(bad.data(), bad.size()), &index, &error), + CheckRejected(DecodeWorldIndex(base::Span(bad.data(), bad.size()), &index, &error), error, "a cell with non-finite bounds"); } { base::Vector bad(good); WriteU32(&bad, kIndexHeaderBytes + 16, 0x42c80000); // minimum.z = 100 > maximum.z RepairChecksum(&bad, kIndexHeaderBytes); - CheckRejected(DecodeWorldIndex(std::span(bad.data(), bad.size()), &index, &error), + CheckRejected(DecodeWorldIndex(base::Span(bad.data(), bad.size()), &index, &error), error, "a cell with inverted bounds"); } { @@ -700,14 +703,14 @@ void TestIndexRefusesCraftedStructure() { base::Vector bad(good); bad[kIndexHeaderBytes + 2 * 60 + 12] = 42; // domain RepairChecksum(&bad, kIndexHeaderBytes); - CheckRejected(DecodeWorldIndex(std::span(bad.data(), bad.size()), &index, &error), + CheckRejected(DecodeWorldIndex(base::Span(bad.data(), bad.size()), &index, &error), error, "a payload with an unknown domain"); } - CHECK(DecodeWorldIndex(std::span(good.data(), good.size()), &index, &error)); + CHECK(DecodeWorldIndex(base::Span(good.data(), good.size()), &index, &error)); } void TestLayoutHashSeparatesShapes() { - const std::string_view names[2] = {"x", "y"}; + const base::StringRef names[2] = {"x", "y"}; const u32 types[2] = {4, 4}; const u32 offsets[2] = {0, 4}; const u32 moved[2] = {0, 8}; @@ -719,11 +722,11 @@ void TestLayoutHashSeparatesShapes() { CHECK(base_hash != HashComponentLayout("Health", 8, names, types, moved)); const u32 retyped[2] = {4, 5}; CHECK(base_hash != HashComponentLayout("Health", 8, names, retyped, offsets)); - const std::string_view renamed[2] = {"x", "z"}; + const base::StringRef renamed[2] = {"x", "z"}; CHECK(base_hash != HashComponentLayout("Health", 8, renamed, types, offsets)); // A prefix of the fields is not the same shape as all of them. - CHECK(base_hash != HashComponentLayout("Health", 8, std::span(names).first(1), - std::span(types).first(1), std::span(offsets).first(1))); + CHECK(base_hash != HashComponentLayout("Health", 8, base::Span(names).first(1), + base::Span(types).first(1), base::Span(offsets).first(1))); } } // namespace @@ -744,9 +747,9 @@ int main() { TestRefusedDecodeLeavesTheOutputAlone(); TestLayoutHashSeparatesShapes(); if (g_failures) { - std::fprintf(stderr, "world_format_test: %d failure(s)\n", g_failures); + ::fprintf(stderr, "world_format_test: %d failure(s)\n", g_failures); return 1; } - std::puts("world_format_test: ok"); + ::puts("world_format_test: ok"); return 0; } diff --git a/test/world_fuzz_test.cc b/test/world_fuzz_test.cc index 6c6fca64..ecea7c61 100644 --- a/test/world_fuzz_test.cc +++ b/test/world_fuzz_test.cc @@ -10,11 +10,11 @@ // hang, leak, or read out of bounds; anything accepted must survive its own // accessors. Run under -DRX_SANITIZE=ON for that to mean what it says. // Deterministic: fixed seed, fixed iteration count; pass a count to run longer. -#include -#include -#include -#include +#include +#include +#include "base/containers/span.h" +#include "base/strings/xstring.h" #include "world/world_format.h" #include "world/world_overlay.h" @@ -26,6 +26,38 @@ using rx::u32; using rx::u64; using rx::u8; +// std::mt19937_64, exactly: the fuzz corpus is defined by this sequence. +class Mt19937_64 { + public: + explicit Mt19937_64(u64 seed) { + state_[0] = seed; + for (u32 i = 1; i < kN; ++i) + state_[i] = 6364136223846793005ull * (state_[i - 1] ^ (state_[i - 1] >> 62)) + i; + } + u64 operator()() { + if (index_ == kN) Twist(); + u64 y = state_[index_++]; + y ^= (y >> 29) & 0x5555555555555555ull; + y ^= (y << 17) & 0x71d67fffeda60000ull; + y ^= (y << 37) & 0xfff7eee000000000ull; + return y ^ (y >> 43); + } + + private: + static constexpr u32 kN = 312; + void Twist() { + for (u32 i = 0; i < kN; ++i) { + const u64 y = (state_[i] & 0xffffffff80000000ull) | + (state_[(i + 1) % kN] & 0x7fffffffull); + state_[i] = state_[(i + 156) % kN] ^ (y >> 1) ^ + ((y & 1) ? 0xb5026f5aa96619e9ull : 0ull); + } + index_ = 0; + } + u64 state_[kN]; + u32 index_ = kN; +}; + struct Position { f32 x = 0, y = 0, z = 0; }; @@ -42,7 +74,7 @@ base::Vector SeedIndex() { writer.AddPayload(1, Domain::kGameplay, Tier::kProxy, 64, 2); writer.AddPayload(2, Domain::kRepresentation, Tier::kFull, 256, 4); base::Vector bytes; - std::string error; + base::String error; writer.Encode(&bytes, &error); return bytes; } @@ -55,10 +87,10 @@ base::Vector SeedEntities() { const u32 archetype = writer.BeginArchetype(3); writer.AddColumn(archetype, "Position", sizeof(Position), HashComponentLayout("Position", sizeof(Position), {}, {}, {}), - std::span(reinterpret_cast(positions), sizeof(positions))); - writer.SetStableIds(archetype, std::span(ids, 3)); + base::Span(reinterpret_cast(positions), sizeof(positions))); + writer.SetStableIds(archetype, base::Span(ids, 3)); base::Vector bytes; - std::string error; + base::String error; writer.Encode(&bytes, &error); return bytes; } @@ -71,7 +103,7 @@ base::Vector SeedInstances() { writer.AddInstance(8 + i, prototype, {static_cast(i), 0, 0}, {0, 0, 0, 1}, 1.0f); } base::Vector bytes; - std::string error; + base::String error; writer.Encode(&bytes, &error); return bytes; } @@ -83,7 +115,7 @@ base::Vector SeedOverlay() { overlay.Destroy(9); overlay.Move(1, {1, 2, 3}, {0, 0, 0, 1}, 2.0f); base::Vector bytes; - std::string error; + base::String error; overlay.Encode(&bytes, &error); return bytes; } @@ -119,15 +151,15 @@ void RepairChecksum(base::Vector* bytes, size_t header) { } // namespace int main(int argc, char** argv) { - const rx::u64 iterations = argc > 1 ? std::strtoull(argv[1], nullptr, 10) : 200000; + const rx::u64 iterations = argc > 1 ? ::strtoull(argv[1], nullptr, 10) : 200000; const base::Vector seeds[4] = {SeedIndex(), SeedEntities(), SeedInstances(), SeedOverlay()}; for (const base::Vector& seed : seeds) { if (!seed.empty()) continue; - std::fprintf(stderr, "world_fuzz_test: a seed failed to encode\n"); + ::fprintf(stderr, "world_fuzz_test: a seed failed to encode\n"); return 1; } - std::mt19937_64 rng(0x9e3779b97f4a7c15ull); + Mt19937_64 rng(0x9e3779b97f4a7c15ull); rx::u64 accepted = 0; rx::u64 refused = 0; for (rx::u64 i = 0; i < iterations; ++i) { @@ -144,8 +176,8 @@ int main(int argc, char** argv) { } if ((rng() % 2) == 0) RepairChecksum(&bytes, HeaderBytes(which)); - const std::span span(bytes.data(), bytes.size()); - std::string error; + const base::Span span(bytes.data(), bytes.size()); + base::String error; bool ok = false; if (which == 0) { WorldIndexData index; @@ -190,7 +222,7 @@ int main(int argc, char** argv) { } ++refused; if (!error.empty()) continue; - std::fprintf(stderr, "world_fuzz_test: iteration %llu refused with no message\n", + ::fprintf(stderr, "world_fuzz_test: iteration %llu refused with no message\n", static_cast(i)); return 1; } @@ -199,12 +231,12 @@ int main(int argc, char** argv) { // mean the mutations never got past the checksum, all-accepted that the // decoder validates nothing. if (accepted == 0 || refused == 0) { - std::fprintf(stderr, "world_fuzz_test: %llu accepted, %llu refused - one-sided\n", + ::fprintf(stderr, "world_fuzz_test: %llu accepted, %llu refused - one-sided\n", static_cast(accepted), static_cast(refused)); return 1; } - std::printf("world_fuzz_test: ok (%llu iterations, %llu accepted, %llu refused)\n", + ::printf("world_fuzz_test: ok (%llu iterations, %llu accepted, %llu refused)\n", static_cast(iterations), static_cast(accepted), static_cast(refused)); diff --git a/test/world_map_test.cc b/test/world_map_test.cc index 2b49f4bc..a8be4136 100644 --- a/test/world_map_test.cc +++ b/test/world_map_test.cc @@ -3,16 +3,17 @@ // refusals that keep a stale or mismatched archive from being streamed. #include "world/world_map.h" -#include -#include -#include +#include #include "asset/pack.h" #include "asset/vfs.h" +#include "base/memory/move.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" namespace { -namespace fs = std::filesystem; +namespace fs = rx::fs; using namespace rx::world; using rx::asset::PackWriter; using rx::asset::Vfs; @@ -27,7 +28,7 @@ int g_failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++g_failures; \ } \ } while (0) @@ -59,16 +60,16 @@ base::Vector BakeIndex() { writer.AddPayload(0, Domain::kCollision, Tier::kStandard, 512, 1); base::Vector bytes; - std::string error; + base::String error; if (!writer.Encode(&bytes, &error)) { - std::fprintf(stderr, "FAIL: baking the index: %s\n", error.c_str()); + ::fprintf(stderr, "FAIL: baking the index: %s\n", error.c_str()); ++g_failures; } return bytes; } // Writes the world into an archive at `archive` and mounts it at "world://". -void MountWorld(const fs::path& archive, Vfs* vfs, bool skip_one_payload = false, +void MountWorld(base::StringRef archive, Vfs* vfs, bool skip_one_payload = false, u64 payload_bake_id = kBakeId) { PackWriter pack; pack.Add("city/city.rxworld", BakeIndex()); @@ -81,27 +82,27 @@ void MountWorld(const fs::path& archive, Vfs* vfs, bool skip_one_payload = false const u32 prototype = writer.AddPrototype("prop/rock"); writer.AddInstance(id * 100, prototype, {}, {0, 0, 0, 1}, 1.0f); base::Vector bytes; - std::string error; + base::String error; if (!writer.Encode(&bytes, &error)) { - std::fprintf(stderr, "FAIL: baking a payload: %s\n", error.c_str()); + ::fprintf(stderr, "FAIL: baking a payload: %s\n", error.c_str()); ++g_failures; } - pack.Add(CellPayloadPath("city", id, Domain::kRepresentation, Tier::kFull), std::move(bytes)); + pack.Add(CellPayloadPath("city", id, Domain::kRepresentation, Tier::kFull), base::move(bytes)); } } - CHECK(pack.WriteTo(archive.string())); - auto provider = rx::asset::MakePackFileProvider(archive.string()); + CHECK(pack.WriteTo(archive)); + auto provider = rx::asset::MakePackFileProvider(archive); CHECK(provider != nullptr); - if (provider) vfs->Mount("world", std::move(provider)); + if (provider) vfs->Mount("world", base::move(provider)); } -void TestLoadFromArchive(const fs::path& directory) { - fs::create_directories(directory); +void TestLoadFromArchive(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "city.rxp", &vfs); + MountWorld(fs::Join(directory, "city.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); CHECK(error.empty()); CHECK(map.loaded()); @@ -131,9 +132,9 @@ void TestLoadFromArchive(const fs::path& directory) { // "world://city.rxworld" belongs to the scheme separator - cutting there gives // "world:/", which matches no mount, so the index would load and every single // payload read would then fail as a missing entry. -void TestIndexAtTheRootOfItsScheme(const fs::path& directory) { - fs::create_directories(directory); - const fs::path archive = directory / "root.rxp"; +void TestIndexAtTheRootOfItsScheme(base::StringRef directory) { + fs::CreateDirectories(directory); + const base::String archive = fs::Join(directory, "root.rxp"); PackWriter pack; pack.Add("city.rxworld", BakeIndex()); CellPayloadWriter writer(CellId(0, 0), Domain::kRepresentation, Tier::kFull); @@ -141,18 +142,18 @@ void TestIndexAtTheRootOfItsScheme(const fs::path& directory) { const u32 prototype = writer.AddPrototype("prop/rock"); writer.AddInstance(0, prototype, {}, {0, 0, 0, 1}, 1.0f); base::Vector bytes; - std::string error; + base::String error; CHECK(writer.Encode(&bytes, &error)); // Empty prefix: the payloads sit beside the index, at the scheme root. pack.Add(CellPayloadPath("", CellId(0, 0), Domain::kRepresentation, Tier::kFull), - std::move(bytes)); - CHECK(pack.WriteTo(archive.string())); + base::move(bytes)); + CHECK(pack.WriteTo(archive)); Vfs vfs; - auto provider = rx::asset::MakePackFileProvider(archive.string()); + auto provider = rx::asset::MakePackFileProvider(archive); CHECK(provider != nullptr); if (!provider) return; - vfs.Mount("world", std::move(provider)); + vfs.Mount("world", base::move(provider)); WorldMap map; CHECK(map.Load(vfs, "world://city.rxworld", &error)); @@ -162,12 +163,12 @@ void TestIndexAtTheRootOfItsScheme(const fs::path& directory) { CHECK(payload.instances.size() == 1); } -void TestMissingAndStaleArchives(const fs::path& directory) { - fs::create_directories(directory); +void TestMissingAndStaleArchives(base::StringRef directory) { + fs::CreateDirectories(directory); { Vfs vfs; WorldMap map; - std::string error; + base::String error; CHECK(!map.Load(vfs, "world://city/city.rxworld", &error)); CHECK(!error.empty()); CHECK(!map.loaded()); @@ -176,26 +177,26 @@ void TestMissingAndStaleArchives(const fs::path& directory) { // The index lists cell 3's payload; the archive does not carry it. The read // must name the path rather than quietly produce an empty cell. Vfs vfs; - MountWorld(directory / "holes.rxp", &vfs, /*skip_one_payload=*/true); + MountWorld(fs::Join(directory, "holes.rxp"), &vfs, /*skip_one_payload=*/true); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); WorldCellPayload payload; CHECK(!map.ReadPayload(vfs, 3, Domain::kRepresentation, Tier::kFull, &payload, &error)); - CHECK(error.find("0000000000000003") != std::string::npos); + CHECK(error.find("0000000000000003") != base::String::npos); } { // Payloads from a different cook than the index. This is the failure the // bake id exists for: every byte decodes, and every entity would be wrong. Vfs vfs; - MountWorld(directory / "stale.rxp", &vfs, /*skip_one_payload=*/false, + MountWorld(fs::Join(directory, "stale.rxp"), &vfs, /*skip_one_payload=*/false, /*payload_bake_id=*/kBakeId + 1); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); WorldCellPayload payload; CHECK(!map.ReadPayload(vfs, 0, Domain::kRepresentation, Tier::kFull, &payload, &error)); - CHECK(error.find("baked by") != std::string::npos); + CHECK(error.find("baked by") != base::String::npos); } } @@ -204,8 +205,8 @@ void TestMissingAndStaleArchives(const fs::path& directory) { // an id outside its cell's range would be unreachable by Resolve and silently // exempt from every overlay delta - a fence that comes back after the player // broke it - so it is refused rather than loaded. -void TestPayloadIdsMustLieInTheCellsRange(const fs::path& directory) { - fs::create_directories(directory); +void TestPayloadIdsMustLieInTheCellsRange(base::StringRef directory) { + fs::CreateDirectories(directory); WorldIndexWriter writer; writer.set_bake_id(kBakeId); writer.AddCell(1, {}, {64, 32, 64}, 0, 100, 10); @@ -213,7 +214,7 @@ void TestPayloadIdsMustLieInTheCellsRange(const fs::path& directory) { writer.AddPayload(1, Domain::kRepresentation, Tier::kFull, 64, 2); writer.AddPayload(2, Domain::kRepresentation, Tier::kFull, 64, 1); base::Vector index_bytes; - std::string error; + base::String error; CHECK(writer.Encode(&index_bytes, &error)); auto page = [&](u64 cell, u64 first_id, u32 count) { @@ -229,50 +230,50 @@ void TestPayloadIdsMustLieInTheCellsRange(const fs::path& directory) { }; PackWriter pack; - pack.Add("city/city.rxworld", std::move(index_bytes)); + pack.Add("city/city.rxworld", base::move(index_bytes)); // Cell 1's page strays one id past its range; cell 2's is written under // cell 1's path, so it decodes and describes the wrong cell. pack.Add(CellPayloadPath("city", 1, Domain::kRepresentation, Tier::kFull), page(1, 109, 2)); pack.Add(CellPayloadPath("city", 2, Domain::kRepresentation, Tier::kFull), page(1, 100, 1)); - CHECK(pack.WriteTo((directory / "ranges.rxp").string())); + CHECK(pack.WriteTo(fs::Join(directory, "ranges.rxp"))); Vfs vfs; - auto provider = rx::asset::MakePackFileProvider((directory / "ranges.rxp").string()); + auto provider = rx::asset::MakePackFileProvider(fs::Join(directory, "ranges.rxp")); CHECK(provider != nullptr); if (!provider) return; - vfs.Mount("world", std::move(provider)); + vfs.Mount("world", base::move(provider)); WorldMap map; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); WorldCellPayload payload; CHECK(!map.ReadPayload(vfs, 1, Domain::kRepresentation, Tier::kFull, &payload, &error)); - CHECK(error.find("outside the range") != std::string::npos); + CHECK(error.find("outside the range") != base::String::npos); CHECK(!map.ReadPayload(vfs, 2, Domain::kRepresentation, Tier::kFull, &payload, &error)); - CHECK(error.find("not cell 2") != std::string::npos); + CHECK(error.find("not cell 2") != base::String::npos); // The same page inside the range loads. PackWriter good; base::Vector good_index; CHECK(writer.Encode(&good_index, &error)); - good.Add("city/city.rxworld", std::move(good_index)); + good.Add("city/city.rxworld", base::move(good_index)); good.Add(CellPayloadPath("city", 1, Domain::kRepresentation, Tier::kFull), page(1, 100, 2)); - CHECK(good.WriteTo((directory / "ok.rxp").string())); + CHECK(good.WriteTo(fs::Join(directory, "ok.rxp"))); Vfs ok_vfs; - auto ok_provider = rx::asset::MakePackFileProvider((directory / "ok.rxp").string()); + auto ok_provider = rx::asset::MakePackFileProvider(fs::Join(directory, "ok.rxp")); if (!ok_provider) return; - ok_vfs.Mount("world", std::move(ok_provider)); + ok_vfs.Mount("world", base::move(ok_provider)); WorldMap ok_map; CHECK(ok_map.Load(ok_vfs, "world://city/city.rxworld", &error)); CHECK(ok_map.ReadPayload(ok_vfs, 1, Domain::kRepresentation, Tier::kFull, &payload, &error)); CHECK(payload.instances.size() == 2); } -void TestPerDomainBubbles(const fs::path& directory) { - fs::create_directories(directory); +void TestPerDomainBubbles(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "bubbles.rxp", &vfs); + MountWorld(fs::Join(directory, "bubbles.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); WorldStreamPolicy policy; @@ -332,25 +333,25 @@ void TestPerDomainBubbles(const fs::path& directory) { // reaches much further than the cell's own box along its short axis. A gather // that missed it would read to the planner as the cell having left the world, // and the resident cell would be retired on the spot, retain radius ignored. -void TestMovingObserverRetainsElongatedCells(const fs::path& directory) { - fs::create_directories(directory); +void TestMovingObserverRetainsElongatedCells(base::StringRef directory) { + fs::CreateDirectories(directory); WorldIndexWriter writer; writer.set_bake_id(kBakeId); // 100 m wide on x, 2 m deep on z: bounding radius about 50, box depth 1. writer.AddCell(1, {-50, -1, 0}, {50, 1, 2}, 0, 0, 0); writer.AddPayload(1, Domain::kGameplay, Tier::kStandard, 64, 1); base::Vector index_bytes; - std::string error; + base::String error; CHECK(writer.Encode(&index_bytes, &error)); PackWriter pack; - pack.Add("thin/thin.rxworld", std::move(index_bytes)); - CHECK(pack.WriteTo((directory / "thin.rxp").string())); + pack.Add("thin/thin.rxworld", base::move(index_bytes)); + CHECK(pack.WriteTo(fs::Join(directory, "thin.rxp"))); Vfs vfs; - auto provider = rx::asset::MakePackFileProvider((directory / "thin.rxp").string()); + auto provider = rx::asset::MakePackFileProvider(fs::Join(directory, "thin.rxp")); CHECK(provider != nullptr); if (!provider) return; - vfs.Mount("world", std::move(provider)); + vfs.Mount("world", base::move(provider)); WorldMap map; CHECK(map.Load(vfs, "world://thin/thin.rxworld", &error)); @@ -386,12 +387,12 @@ void TestMovingObserverRetainsElongatedCells(const fs::path& directory) { CHECK(gathered.empty()); } -void TestTargetTier(const fs::path& directory) { - fs::create_directories(directory); +void TestTargetTier(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "tiers.rxp", &vfs); + MountWorld(fs::Join(directory, "tiers.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); const WorldCellRecord* cell = map.index().FindCell(0); CHECK(cell != nullptr); @@ -419,23 +420,23 @@ void TestTargetTier(const fs::path& directory) { } // namespace int main() { - const fs::path tmp = fs::temp_directory_path() / "rx_world_map_test"; - fs::remove_all(tmp); - fs::create_directories(tmp); - - TestLoadFromArchive(tmp / "load"); - TestIndexAtTheRootOfItsScheme(tmp / "schemeroot"); - TestMissingAndStaleArchives(tmp / "stale"); - TestPayloadIdsMustLieInTheCellsRange(tmp / "ranges"); - TestPerDomainBubbles(tmp / "bubbles"); - TestMovingObserverRetainsElongatedCells(tmp / "thin"); - TestTargetTier(tmp / "tiers"); - - fs::remove_all(tmp); + const base::String tmp = fs::Join(fs::TempDirectory(), "rx_world_map_test"); + fs::RemoveAll(tmp); + fs::CreateDirectories(tmp); + + TestLoadFromArchive(fs::Join(tmp, "load")); + TestIndexAtTheRootOfItsScheme(fs::Join(tmp, "schemeroot")); + TestMissingAndStaleArchives(fs::Join(tmp, "stale")); + TestPayloadIdsMustLieInTheCellsRange(fs::Join(tmp, "ranges")); + TestPerDomainBubbles(fs::Join(tmp, "bubbles")); + TestMovingObserverRetainsElongatedCells(fs::Join(tmp, "thin")); + TestTargetTier(fs::Join(tmp, "tiers")); + + fs::RemoveAll(tmp); if (g_failures) { - std::fprintf(stderr, "world_map_test: %d failure(s)\n", g_failures); + ::fprintf(stderr, "world_map_test: %d failure(s)\n", g_failures); return 1; } - std::puts("world_map_test: ok"); + ::puts("world_map_test: ok"); return 0; } diff --git a/test/world_overlay_test.cc b/test/world_overlay_test.cc index e664c8eb..6f8896bf 100644 --- a/test/world_overlay_test.cc +++ b/test/world_overlay_test.cc @@ -1,12 +1,13 @@ // The mutable third layer: what a save file records on top of an immutable // bake, and the invariants that let the streamer consult it with a binary // search while a cell materializes. +#include "base/algorithm.h" +#include "base/containers/span.h" +#include "base/strings/xstring.h" #include "world/world_overlay.h" -#include -#include -#include -#include +#include +#include namespace { @@ -23,19 +24,19 @@ int g_failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++g_failures; \ } \ } while (0) -void CheckRejected(bool decoded, const std::string& error, const char* what) { +void CheckRejected(bool decoded, const base::String& error, const char* what) { if (decoded) { - std::fprintf(stderr, "FAIL: %s was accepted\n", what); + ::fprintf(stderr, "FAIL: %s was accepted\n", what); ++g_failures; return; } if (error.empty()) { - std::fprintf(stderr, "FAIL: %s was rejected without a message\n", what); + ::fprintf(stderr, "FAIL: %s was rejected without a message\n", what); ++g_failures; } } @@ -75,9 +76,9 @@ void TestDeltas() { // And a value the decoder would refuse never reaches the file: a save that // writes and then cannot be loaded is worse than one that says no. - const f32 nan = std::numeric_limits::quiet_NaN(); + const f32 nan = NAN; CHECK(!overlay.Move(11, {nan, 0, 0}, {0, 0, 0, 1}, 1.0f)); - CHECK(!overlay.Move(11, {0, 0, 0}, {0, 0, 0, 1}, std::numeric_limits::infinity())); + CHECK(!overlay.Move(11, {0, 0, 0}, {0, 0, 0, 1}, INFINITY)); CHECK(overlay.FindMove(11) == nullptr); CHECK(overlay.Move(11, {1, 2, 3}, {0, 0, 0, 1}, 1.0f)); CHECK(overlay.FindMove(11) != nullptr); @@ -121,12 +122,12 @@ void TestRoundTrip() { overlay.Move(50, {-1, -2, -3}, {0, 0, 0, 1}, 0.25f); base::Vector bytes; - std::string error; + base::String error; CHECK(overlay.Encode(&bytes, &error)); CHECK(error.empty()); WorldOverlay decoded; - CHECK(WorldOverlay::Decode(std::span(bytes.data(), bytes.size()), &decoded, &error)); + CHECK(WorldOverlay::Decode(base::Span(bytes.data(), bytes.size()), &decoded, &error)); // The bake it was recorded against travels with it, or the ids it holds mean // nothing checkable. CHECK(decoded.bake_id() == 0xfeedfacecafebeefull); @@ -146,7 +147,7 @@ void TestRoundTrip() { CHECK(fresh.Encode(&empty_bytes, &error)); WorldOverlay empty_decoded; empty_decoded.Destroy(1); - CHECK(WorldOverlay::Decode(std::span(empty_bytes.data(), empty_bytes.size()), + CHECK(WorldOverlay::Decode(base::Span(empty_bytes.data(), empty_bytes.size()), &empty_decoded, &error)); CHECK(empty_decoded.empty()); // Decode replaces, it does not merge } @@ -156,38 +157,38 @@ void TestRefusesCorruptedBytes() { overlay.Destroy(100); overlay.Move(250, {1, 2, 3}, {0, 0, 0, 1}, 1.0f); base::Vector good; - std::string error; + base::String error; CHECK(overlay.Encode(&good, &error)); WorldOverlay decoded; { base::Vector bad(good); bad[2] = 'X'; - CheckRejected(WorldOverlay::Decode(std::span(bad.data(), bad.size()), &decoded, + CheckRejected(WorldOverlay::Decode(base::Span(bad.data(), bad.size()), &decoded, &error), error, "a bad magic"); } { base::Vector bad(good); bad[bad.size() - 1] ^= 0xff; - CheckRejected(WorldOverlay::Decode(std::span(bad.data(), bad.size()), &decoded, + CheckRejected(WorldOverlay::Decode(base::Span(bad.data(), bad.size()), &decoded, &error), error, "a flipped body byte"); } { base::Vector bad(good); bad.erase(bad.end() - 1); - CheckRejected(WorldOverlay::Decode(std::span(bad.data(), bad.size()), &decoded, + CheckRejected(WorldOverlay::Decode(base::Span(bad.data(), bad.size()), &decoded, &error), error, "a truncated body"); } { base::Vector bad; - CheckRejected(WorldOverlay::Decode(std::span(bad.data(), bad.size()), &decoded, + CheckRejected(WorldOverlay::Decode(base::Span(bad.data(), bad.size()), &decoded, &error), error, "an empty file"); } - CHECK(WorldOverlay::Decode(std::span(good.data(), good.size()), &decoded, &error)); + CHECK(WorldOverlay::Decode(base::Span(good.data(), good.size()), &decoded, &error)); } // Rewrites the file's checksum after an edit, so only the structural check @@ -217,7 +218,7 @@ void TestRefusesInconsistentFiles() { overlay.Move(10, {1, 2, 3}, {0, 0, 0, 1}, 1.0f); overlay.Move(20, {4, 5, 6}, {0, 0, 0, 1}, 1.0f); base::Vector good; - std::string error; + base::String error; CHECK(overlay.Encode(&good, &error)); WorldOverlay decoded; @@ -225,9 +226,9 @@ void TestRefusesInconsistentFiles() { // Swap the two moves so the list is no longer sorted by stable id. base::Vector bad(good); constexpr size_t kMove = 40; - for (u32 i = 0; i < kMove; ++i) std::swap(bad[32 + i], bad[32 + kMove + i]); + for (u32 i = 0; i < kMove; ++i) base::Swap(bad[32 + i], bad[32 + kMove + i]); RepairChecksum(&bad); - CheckRejected(WorldOverlay::Decode(std::span(bad.data(), bad.size()), &decoded, + CheckRejected(WorldOverlay::Decode(base::Span(bad.data(), bad.size()), &decoded, &error), error, "an unsorted move list"); } @@ -238,7 +239,7 @@ void TestRefusesInconsistentFiles() { bad[32 + 36 + shift / 8] = static_cast(0x7fc00000u >> shift); } RepairChecksum(&bad); - CheckRejected(WorldOverlay::Decode(std::span(bad.data(), bad.size()), &decoded, + CheckRejected(WorldOverlay::Decode(base::Span(bad.data(), bad.size()), &decoded, &error), error, "a move with a non-finite transform"); } @@ -249,7 +250,7 @@ void TestRefusesInconsistentFiles() { bad[20] = 1; // destroyed_count bad[24] = 1; // move_count RepairChecksum(&bad); - CheckRejected(WorldOverlay::Decode(std::span(bad.data(), bad.size()), &decoded, + CheckRejected(WorldOverlay::Decode(base::Span(bad.data(), bad.size()), &decoded, &error), error, "an id that is both destroyed and moved"); } @@ -257,7 +258,7 @@ void TestRefusesInconsistentFiles() { base::Vector bad(good); bad[8] = 9; // version RepairChecksum(&bad); - CheckRejected(WorldOverlay::Decode(std::span(bad.data(), bad.size()), &decoded, + CheckRejected(WorldOverlay::Decode(base::Span(bad.data(), bad.size()), &decoded, &error), error, "a future version"); } @@ -267,11 +268,11 @@ void TestRefusesInconsistentFiles() { // checksum instead. base::Vector bad(good); for (u32 i = 0; i < 8; ++i) bad[12 + i] = 0; - CheckRejected(WorldOverlay::Decode(std::span(bad.data(), bad.size()), &decoded, + CheckRejected(WorldOverlay::Decode(base::Span(bad.data(), bad.size()), &decoded, &error), error, "a zeroed bake id"); } - CHECK(WorldOverlay::Decode(std::span(good.data(), good.size()), &decoded, &error)); + CHECK(WorldOverlay::Decode(base::Span(good.data(), good.size()), &decoded, &error)); CHECK(decoded.bake_id() == 7); // And the same for the destroyed list, which IsDestroyed binary searches: out @@ -281,10 +282,10 @@ void TestRefusesInconsistentFiles() { destroys.Destroy(20); base::Vector sorted; CHECK(destroys.Encode(&sorted, &error)); - for (u32 i = 0; i < 8; ++i) std::swap(sorted[32 + i], sorted[32 + 8 + i]); + for (u32 i = 0; i < 8; ++i) base::Swap(sorted[32 + i], sorted[32 + 8 + i]); RepairChecksum(&sorted); CheckRejected( - WorldOverlay::Decode(std::span(sorted.data(), sorted.size()), &decoded, &error), + WorldOverlay::Decode(base::Span(sorted.data(), sorted.size()), &decoded, &error), error, "an unsorted destroyed list"); } @@ -297,9 +298,9 @@ int main() { TestRefusesCorruptedBytes(); TestRefusesInconsistentFiles(); if (g_failures) { - std::fprintf(stderr, "world_overlay_test: %d failure(s)\n", g_failures); + ::fprintf(stderr, "world_overlay_test: %d failure(s)\n", g_failures); return 1; } - std::puts("world_overlay_test: ok"); + ::puts("world_overlay_test: ok"); return 0; } diff --git a/test/world_stream_test.cc b/test/world_stream_test.cc index 71043c32..8fe0b066 100644 --- a/test/world_stream_test.cc +++ b/test/world_stream_test.cc @@ -5,13 +5,15 @@ // was cancelled, and a cook whose component layout no longer matches the build. #include "world/world_stream.h" -#include -#include -#include -#include +#include +#include #include "asset/pack.h" #include "asset/vfs.h" +#include "base/containers/span.h" +#include "base/memory/move.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" #include "ecs/world.h" #include "scene/components.h" #include "world/world_map.h" @@ -19,7 +21,7 @@ namespace { -namespace fs = std::filesystem; +namespace fs = rx::fs; using namespace rx::world; using rx::asset::PackWriter; using rx::asset::Vfs; @@ -37,7 +39,7 @@ int g_failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ - std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++g_failures; \ } \ } while (0) @@ -96,14 +98,14 @@ base::Vector BakeGameplay(u64 cell, const BakeOptions& options, : options.entity_reference_component ? "Parent" : "Transform"; writer.AddColumn(archetype, name, stride, layout, - std::span(reinterpret_cast(transforms.data()), + base::Span(reinterpret_cast(transforms.data()), transforms.size() * sizeof(Transform))); - writer.SetStableIds(archetype, std::span(ids.data(), ids.size())); + writer.SetStableIds(archetype, base::Span(ids.data(), ids.size())); base::Vector bytes; - std::string error; + base::String error; if (!writer.Encode(&bytes, &error)) { - std::fprintf(stderr, "FAIL: baking gameplay: %s\n", error.c_str()); + ::fprintf(stderr, "FAIL: baking gameplay: %s\n", error.c_str()); ++g_failures; } return bytes; @@ -118,9 +120,9 @@ base::Vector BakeRepresentation(u64 cell, const BakeOptions& options) { {static_cast(cell), static_cast(i), 0}, {0, 0, 0, 1}, 1.0f); } base::Vector bytes; - std::string error; + base::String error; if (!writer.Encode(&bytes, &error)) { - std::fprintf(stderr, "FAIL: baking representation: %s\n", error.c_str()); + ::fprintf(stderr, "FAIL: baking representation: %s\n", error.c_str()); ++g_failures; } return bytes; @@ -128,7 +130,7 @@ base::Vector BakeRepresentation(u64 cell, const BakeOptions& options) { // A 2x2 grid of 64 m cells on XZ, each with a gameplay and a representation // payload, packed into a real .rxp and mounted at world://. -void MountWorld(const fs::path& archive, Vfs* vfs, const BakeOptions& options = {}) { +void MountWorld(base::StringRef archive, Vfs* vfs, const BakeOptions& options = {}) { WorldIndexWriter index; index.set_world_id(7); index.set_bake_id(kBakeId); @@ -156,14 +158,14 @@ void MountWorld(const fs::path& archive, Vfs* vfs, const BakeOptions& options = } } base::Vector index_bytes; - std::string error; + base::String error; CHECK(index.Encode(&index_bytes, &error)); - pack.Add("city/city.rxworld", std::move(index_bytes)); - CHECK(pack.WriteTo(archive.string())); + pack.Add("city/city.rxworld", base::move(index_bytes)); + CHECK(pack.WriteTo(archive)); - auto provider = rx::asset::MakePackFileProvider(archive.string()); + auto provider = rx::asset::MakePackFileProvider(archive); CHECK(provider != nullptr); - if (provider) vfs->Mount("world", std::move(provider)); + if (provider) vfs->Mount("world", base::move(provider)); } WorldStreamPolicy TestPolicy(u32 rows_per_commit = 4096) { @@ -191,7 +193,7 @@ WorldStreamObservation At(f32 x, f32 z) { void Tick(WorldStreamer* streamer, const WorldStreamObservation& observer, u32 count) { for (u32 i = 0; i < count; ++i) { - streamer->Update(std::span(&observer, 1)); + streamer->Update(base::Span(&observer, 1)); } } @@ -221,7 +223,7 @@ class QueuedLoader final : public CellLoader { } void Poll(base::Vector* out) override { - for (CellLoadResult& result : ready_) out->push_back(std::move(result)); + for (CellLoadResult& result : ready_) out->push_back(base::move(result)); ready_.clear(); } @@ -254,7 +256,7 @@ class QueuedLoader final : public CellLoader { result.ok = map_.ReadPayload(vfs_, request.cell, request.domain, request.tier, &result.payload, &result.error); } - ready_.push_back(std::move(result)); + ready_.push_back(base::move(result)); } const WorldMap& map_; @@ -276,12 +278,12 @@ void Settle(WorldStreamer* streamer, QueuedLoader* loader, const WorldStreamObse } } -void TestStreamInAndOut(const fs::path& directory) { - fs::create_directories(directory); +void TestStreamInAndOut(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "world.rxp", &vfs); + MountWorld(fs::Join(directory, "world.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -358,12 +360,12 @@ void TestStreamInAndOut(const fs::path& directory) { CHECK(reloaded_transform && reloaded_transform->position[1] == 3.0f); } -void TestWideBubbleLoadsEveryCell(const fs::path& directory) { - fs::create_directories(directory); +void TestWideBubbleLoadsEveryCell(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "wide.rxp", &vfs); + MountWorld(fs::Join(directory, "wide.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -387,12 +389,12 @@ void TestWideBubbleLoadsEveryCell(const fs::path& directory) { } } -void TestBudgetedCommitIsNotResolvableHalfway(const fs::path& directory) { - fs::create_directories(directory); +void TestBudgetedCommitIsNotResolvableHalfway(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "budget.rxp", &vfs); + MountWorld(fs::Join(directory, "budget.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -424,12 +426,12 @@ void TestBudgetedCommitIsNotResolvableHalfway(const fs::path& directory) { CHECK(streamer.stats().entities == kEntitiesPerCell); } -void TestUnloadDuringCommitDestroysExactlyWhatWasMade(const fs::path& directory) { - fs::create_directories(directory); +void TestUnloadDuringCommitDestroysExactlyWhatWasMade(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "interrupt.rxp", &vfs); + MountWorld(fs::Join(directory, "interrupt.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -460,12 +462,12 @@ void TestUnloadDuringCommitDestroysExactlyWhatWasMade(const fs::path& directory) CHECK(world.IsAlive(bystander)); } -void TestLateResultAfterCancelIsNotPublished(const fs::path& directory) { - fs::create_directories(directory); +void TestLateResultAfterCancelIsNotPublished(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "late.rxp", &vfs); + MountWorld(fs::Join(directory, "late.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -504,7 +506,7 @@ void TestLateResultAfterCancelIsNotPublished(const fs::path& directory) { CHECK(world.IsAlive(streamer.Resolve(EntityStableId(0, 0)))); } -void TestSchemaDriftIsRefusedLoudly(const fs::path& directory) { +void TestSchemaDriftIsRefusedLoudly(base::StringRef directory) { struct Case { const char* name; BakeOptions options; @@ -521,12 +523,12 @@ void TestSchemaDriftIsRefusedLoudly(const fs::path& directory) { }; for (const Case& test_case : cases) { - const fs::path sub = directory / test_case.name; - fs::create_directories(sub); + const base::String sub = fs::Join(directory, test_case.name); + fs::CreateDirectories(sub); Vfs vfs; - MountWorld(sub / "drift.rxp", &vfs, test_case.options); + MountWorld(fs::Join(sub, "drift.rxp"), &vfs, test_case.options); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -539,26 +541,26 @@ void TestSchemaDriftIsRefusedLoudly(const fs::path& directory) { CHECK(streamer.stats().entities == 0); CHECK(world.entity_count() == 0); bool named = false; - for (const std::string& message : streamer.errors()) { - if (message.find(test_case.expect) != std::string::npos) named = true; + for (const base::String& message : streamer.errors()) { + if (message.find(test_case.expect) != base::String::npos) named = true; } if (!named) { - std::fprintf(stderr, "FAIL: %s drift produced no message mentioning '%s'\n", test_case.name, + ::fprintf(stderr, "FAIL: %s drift produced no message mentioning '%s'\n", test_case.name, test_case.expect); - for (const std::string& message : streamer.errors()) { - std::fprintf(stderr, " had: %s\n", message.c_str()); + for (const base::String& message : streamer.errors()) { + ::fprintf(stderr, " had: %s\n", message.c_str()); } ++g_failures; } } } -void TestPromoteAnInstance(const fs::path& directory) { - fs::create_directories(directory); +void TestPromoteAnInstance(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "promote.rxp", &vfs); + MountWorld(fs::Join(directory, "promote.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -605,12 +607,12 @@ void TestPromoteAnInstance(const fs::path& directory) { CHECK(!world.IsAlive(promoted)); } -void TestShutdownEmptiesTheWorld(const fs::path& directory) { - fs::create_directories(directory); +void TestShutdownEmptiesTheWorld(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "shutdown.rxp", &vfs); + MountWorld(fs::Join(directory, "shutdown.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -626,12 +628,12 @@ void TestShutdownEmptiesTheWorld(const fs::path& directory) { CHECK(world.entity_count() == 0); } -void TestOverlayShapesTheCellOnTheWayIn(const fs::path& directory) { - fs::create_directories(directory); +void TestOverlayShapesTheCellOnTheWayIn(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "overlay.rxp", &vfs); + MountWorld(fs::Join(directory, "overlay.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); WorldOverlay overlay; @@ -700,12 +702,12 @@ void TestOverlayShapesTheCellOnTheWayIn(const fs::path& directory) { // An overlay recorded against a different cook names different rows. Applying // it would not fail: it would delete and move whatever now carries those ids. -void TestOverlayFromAnotherBakeIsRefused(const fs::path& directory) { - fs::create_directories(directory); +void TestOverlayFromAnotherBakeIsRefused(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "mismatch.rxp", &vfs); + MountWorld(fs::Join(directory, "mismatch.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -733,12 +735,12 @@ void TestOverlayFromAnotherBakeIsRefused(const fs::path& directory) { CHECK(streamer.SetOverlay(nullptr)); } -void TestOverlayThatDeletesEverythingLeavesNothing(const fs::path& directory) { - fs::create_directories(directory); +void TestOverlayThatDeletesEverythingLeavesNothing(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "wipe.rxp", &vfs); + MountWorld(fs::Join(directory, "wipe.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); WorldOverlay overlay; @@ -797,7 +799,7 @@ void TestClaimSet() { claims.Explain(2, ~u32{0}, &why); CHECK(why.size() == 2); CHECK(why.size() == 2 && why[0].kind == ClaimKind::kHard); - CHECK(why.size() == 2 && std::string(why[0].reason) == "the player is standing on it"); + CHECK(why.size() == 2 && base::String(why[0].reason) == "the player is standing on it"); // Pressure revokes the weak and never the hard. claims.set_weakest_honored(ClaimKind::kSoft); @@ -821,12 +823,12 @@ void TestClaimSet() { CHECK(claims.empty()); } -void TestClaimKeepsACellResident(const fs::path& directory) { - fs::create_directories(directory); +void TestClaimKeepsACellResident(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "claims.rxp", &vfs); + MountWorld(fs::Join(directory, "claims.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -876,14 +878,14 @@ void TestClaimKeepsACellResident(const fs::path& directory) { // observer closes on it. Without this the near tier is unreachable: a cell // always enters at roughly the load radius, which is by definition the far // band, and would keep that tier for as long as it stayed resident. -void TestTierRefinesWhenTheObserverCloses(const fs::path& directory) { - fs::create_directories(directory); +void TestTierRefinesWhenTheObserverCloses(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; BakeOptions options; options.tiered = true; - MountWorld(directory / "tiers.rxp", &vfs, options); + MountWorld(fs::Join(directory, "tiers.rxp"), &vfs, options); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -936,12 +938,12 @@ void TestTierRefinesWhenTheObserverCloses(const fs::path& directory) { // A world baked at one tier per domain must not churn: both bands resolve to // the same payload, so nothing about the region changes as the observer moves // and the cell is never reloaded. -void TestSingleTierWorldNeverReloads(const fs::path& directory) { - fs::create_directories(directory); +void TestSingleTierWorldNeverReloads(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "single.rxp", &vfs); // gameplay at kStandard only + MountWorld(fs::Join(directory, "single.rxp"), &vfs); // gameplay at kStandard only WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -969,14 +971,14 @@ void TestSingleTierWorldNeverReloads(const fs::path& directory) { // A cook error is deterministic: the same payload fails the same way every // time. Left alone the planner retries it for as long as the cell is in range, // re-reading and re-decoding broken bytes forever. -void TestPersistentFailuresStopRetrying(const fs::path& directory) { - fs::create_directories(directory); +void TestPersistentFailuresStopRetrying(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; BakeOptions options; options.corrupt_layout_hash = true; - MountWorld(directory / "broken.rxp", &vfs, options); + MountWorld(fs::Join(directory, "broken.rxp"), &vfs, options); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -995,7 +997,7 @@ void TestPersistentFailuresStopRetrying(const fs::path& directory) { // slack means the retry loop is still running. const u32 attempts = loader.begun(); if (attempts > 8) { - std::fprintf(stderr, "FAIL: a broken cell was read %u times in 400 ticks\n", attempts); + ::fprintf(stderr, "FAIL: a broken cell was read %u times in 400 ticks\n", attempts); ++g_failures; } @@ -1007,12 +1009,12 @@ void TestPersistentFailuresStopRetrying(const fs::path& directory) { // Teardown is budgeted on both paths. A cancel is the observer moving fast, // which is exactly when a whole cell destroyed in one frame would show. -void TestTeardownRespectsTheBudget(const fs::path& directory) { - fs::create_directories(directory); +void TestTeardownRespectsTheBudget(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "teardown.rxp", &vfs); + MountWorld(fs::Join(directory, "teardown.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -1047,14 +1049,14 @@ void TestTeardownRespectsTheBudget(const fs::path& directory) { // claim's own source stands at the cell's middle, so taking or dropping a lease // would evict and rebuild a cell that was already resident and correct - and a // lease that is re-issued periodically would put the cell on a treadmill. -void TestClaimDoesNotChangeAResidentCellsTier(const fs::path& directory) { - fs::create_directories(directory); +void TestClaimDoesNotChangeAResidentCellsTier(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; BakeOptions options; options.tiered = true; - MountWorld(directory / "claimtier.rxp", &vfs, options); + MountWorld(fs::Join(directory, "claimtier.rxp"), &vfs, options); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -1110,14 +1112,14 @@ void TestClaimDoesNotChangeAResidentCellsTier(const fs::path& directory) { // Suppression throttles a failing cell rather than removing it from the world // forever: the streamer cannot tell a broken cook from an archive that was // missing for a moment. -void TestSuppressionIsAThrottleNotABan(const fs::path& directory) { - fs::create_directories(directory); +void TestSuppressionIsAThrottleNotABan(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; BakeOptions options; options.corrupt_layout_hash = true; - MountWorld(directory / "throttle.rxp", &vfs, options); + MountWorld(fs::Join(directory, "throttle.rxp"), &vfs, options); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -1139,7 +1141,7 @@ void TestSuppressionIsAThrottleNotABan(const fs::path& directory) { const u32 after = loader.begun(); CHECK(after > throttled); if (after - throttled > 4) { - std::fprintf(stderr, "FAIL: a suppressed cell was retried %u times in 1800 ticks\n", + ::fprintf(stderr, "FAIL: a suppressed cell was retried %u times in 1800 ticks\n", after - throttled); ++g_failures; } @@ -1161,8 +1163,8 @@ void TestSuppressionIsAThrottleNotABan(const fs::path& directory) { // It is also the only cell here big enough to cross an ECS chunk, so it is what // proves the bulk copy advances by the run it is handed instead of assuming one // pointer covers the batch. -void TestManyArchetypesColumnsAndChunks(const fs::path& directory) { - fs::create_directories(directory); +void TestManyArchetypesColumnsAndChunks(base::StringRef directory) { + fs::CreateDirectories(directory); using rx::scene::Guid; using rx::scene::SpawnedFrom; @@ -1196,22 +1198,22 @@ void TestManyArchetypesColumnsAndChunks(const fs::path& directory) { writer.set_bake_id(kBakeId); const u32 wide = writer.BeginArchetype(kWide); writer.AddColumn(wide, "Transform", transform_stride, transform_layout, - std::span(reinterpret_cast(wide_transforms.data()), + base::Span(reinterpret_cast(wide_transforms.data()), wide_transforms.size() * sizeof(Transform))); writer.AddColumn(wide, "Guid", guid_stride, guid_layout, - std::span(reinterpret_cast(wide_guids.data()), + base::Span(reinterpret_cast(wide_guids.data()), wide_guids.size() * sizeof(Guid))); - writer.SetStableIds(wide, std::span(wide_ids.data(), wide_ids.size())); + writer.SetStableIds(wide, base::Span(wide_ids.data(), wide_ids.size())); const u32 second = writer.BeginArchetype(2); writer.AddColumn(second, "SpawnedFrom", spawned_stride, spawned_layout, - std::span(reinterpret_cast(spawned), sizeof(spawned))); + base::Span(reinterpret_cast(spawned), sizeof(spawned))); writer.AddColumn(second, "Transform", transform_stride, transform_layout, - std::span(reinterpret_cast(narrow), sizeof(narrow))); - writer.SetStableIds(second, std::span(narrow_ids, 2)); + base::Span(reinterpret_cast(narrow), sizeof(narrow))); + writer.SetStableIds(second, base::Span(narrow_ids, 2)); base::Vector payload; - std::string error; + base::String error; CHECK(writer.Encode(&payload, &error)); WorldIndexWriter index; @@ -1222,14 +1224,14 @@ void TestManyArchetypesColumnsAndChunks(const fs::path& directory) { CHECK(index.Encode(&index_bytes, &error)); PackWriter pack; - pack.Add("city/city.rxworld", std::move(index_bytes)); - pack.Add(CellPayloadPath("city", 0, Domain::kGameplay, Tier::kStandard), std::move(payload)); - CHECK(pack.WriteTo((directory / "wide.rxp").string())); + pack.Add("city/city.rxworld", base::move(index_bytes)); + pack.Add(CellPayloadPath("city", 0, Domain::kGameplay, Tier::kStandard), base::move(payload)); + CHECK(pack.WriteTo(fs::Join(directory, "wide.rxp"))); Vfs vfs; - auto provider = rx::asset::MakePackFileProvider((directory / "wide.rxp").string()); + auto provider = rx::asset::MakePackFileProvider(fs::Join(directory, "wide.rxp")); CHECK(provider != nullptr); if (!provider) return; - vfs.Mount("world", std::move(provider)); + vfs.Mount("world", base::move(provider)); WorldMap map; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); @@ -1254,8 +1256,8 @@ void TestManyArchetypesColumnsAndChunks(const fs::path& directory) { Settle(&streamer, &loader, At(32, 32), 60); CHECK(streamer.errors().empty()); - for (const std::string& message : streamer.errors()) { - std::fprintf(stderr, " error: %s\n", message.c_str()); + for (const base::String& message : streamer.errors()) { + ::fprintf(stderr, " error: %s\n", message.c_str()); } CHECK(streamer.stats().entities == kWide + 2 - 2); CHECK(world.entity_count() == kWide + 2 - 2); @@ -1269,7 +1271,7 @@ void TestManyArchetypesColumnsAndChunks(const fs::path& directory) { } const Entity entity = streamer.Resolve(i); if (!world.IsAlive(entity)) { - std::fprintf(stderr, "FAIL: row %u did not materialize\n", i); + ::fprintf(stderr, "FAIL: row %u did not materialize\n", i); ++g_failures; break; } @@ -1277,7 +1279,7 @@ void TestManyArchetypesColumnsAndChunks(const fs::path& directory) { const Guid* guid = world.Get(entity); if (!transform || !guid || guid->value != 1000 + i || (i != 400 && transform->position[0] != static_cast(i))) { - std::fprintf(stderr, "FAIL: row %u came back wrong\n", i); + ::fprintf(stderr, "FAIL: row %u came back wrong\n", i); ++g_failures; break; } @@ -1310,14 +1312,14 @@ void TestManyArchetypesColumnsAndChunks(const fs::path& directory) { // Several observers in one tick: the merge has to fold them to one candidate // per cell at the nearest distance, or a far observer's view of a cell decides // its tier. -void TestManyObserversFoldToTheNearest(const fs::path& directory) { - fs::create_directories(directory); +void TestManyObserversFoldToTheNearest(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; BakeOptions options; options.tiered = true; - MountWorld(directory / "observers.rxp", &vfs, options); + MountWorld(fs::Join(directory, "observers.rxp"), &vfs, options); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -1334,7 +1336,7 @@ void TestManyObserversFoldToTheNearest(const fs::path& directory) { const WorldStreamObservation observers[2] = {At(32, 32), At(96, 96)}; for (u32 i = 0; i < 40; ++i) { loader.CompleteAll(); - streamer.Update(std::span(observers, 2)); + streamer.Update(base::Span(observers, 2)); } CHECK(streamer.errors().empty()); CHECK(world.IsAlive(streamer.Resolve(EntityStableId(3, 5)))); // full tier, six rows @@ -1343,7 +1345,7 @@ void TestManyObserversFoldToTheNearest(const fs::path& directory) { const WorldStreamObservation reversed[2] = {At(96, 96), At(32, 32)}; for (u32 i = 0; i < 10; ++i) { loader.CompleteAll(); - streamer.Update(std::span(reversed, 2)); + streamer.Update(base::Span(reversed, 2)); } CHECK(world.IsAlive(streamer.Resolve(EntityStableId(3, 5)))); } @@ -1352,14 +1354,14 @@ void TestManyObserversFoldToTheNearest(const fs::path& directory) { // same cell is in flight. The two differ in tier, so adopting the stale one is // visible: the cell would publish the two rows of the proxy payload under the // generation that asked for the six of the full one. -void TestStalePayloadIsNotAdoptedByANewGeneration(const fs::path& directory) { - fs::create_directories(directory); +void TestStalePayloadIsNotAdoptedByANewGeneration(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; BakeOptions options; options.tiered = true; - MountWorld(directory / "stalegen.rxp", &vfs, options); + MountWorld(fs::Join(directory, "stalegen.rxp"), &vfs, options); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -1403,12 +1405,12 @@ void TestStalePayloadIsNotAdoptedByANewGeneration(const fs::path& directory) { // A cell that fails once and then reads cleanly must not carry the failure // forward: three unrelated transient failures over a session would otherwise // suppress a cell whose cook is fine. -void TestATransientFailureIsForgottenOnSuccess(const fs::path& directory) { - fs::create_directories(directory); +void TestATransientFailureIsForgottenOnSuccess(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "transient.rxp", &vfs); + MountWorld(fs::Join(directory, "transient.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -1440,12 +1442,12 @@ void TestATransientFailureIsForgottenOnSuccess(const fs::path& directory) { // destroys a streamed entity itself leaves the record behind until the cell // unloads, and handing that handle out is precisely the dangling reference // stable ids exist to prevent. -void TestResolveRefusesAnEntityTheGameDestroyed(const fs::path& directory) { - fs::create_directories(directory); +void TestResolveRefusesAnEntityTheGameDestroyed(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "destroyed.rxp", &vfs); + MountWorld(fs::Join(directory, "destroyed.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -1466,14 +1468,14 @@ void TestResolveRefusesAnEntityTheGameDestroyed(const fs::path& directory) { // The shipped defaults have to produce a world that actually streams, or every // game starts by discovering they do not. -void TestDefaultPolicyStreams(const fs::path& directory) { - fs::create_directories(directory); +void TestDefaultPolicyStreams(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; BakeOptions options; options.tiered = true; - MountWorld(directory / "defaults.rxp", &vfs, options); + MountWorld(fs::Join(directory, "defaults.rxp"), &vfs, options); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -1502,12 +1504,12 @@ void TestDefaultPolicyStreams(const fs::path& directory) { // observer moves fast, so it is the one that can least afford to destroy a // whole cell in a frame. A cell big enough for the budget to matter, cancelled // while it is still committing. -void TestCancelDuringCommitIsAlsoBudgeted(const fs::path& directory) { - fs::create_directories(directory); +void TestCancelDuringCommitIsAlsoBudgeted(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "cancelbudget.rxp", &vfs); + MountWorld(fs::Join(directory, "cancelbudget.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -1552,12 +1554,12 @@ void TestCancelDuringCommitIsAlsoBudgeted(const fs::path& directory) { // Shutdown has to hold with work still in flight, not only from a settled // world, because that is when a host swaps worlds or quits. -void TestShutdownMidFlight(const fs::path& directory) { - fs::create_directories(directory); +void TestShutdownMidFlight(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "midflight.rxp", &vfs); + MountWorld(fs::Join(directory, "midflight.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -1596,12 +1598,12 @@ void TestShutdownMidFlight(const fs::path& directory) { // carry the identical ticket. Cancelling one must not take the other's read // with it: the survivor would sit in the planner waiting for a result nobody // is going to produce, holding a pending slot, with no retry and no timeout. -void TestCancellingOneDomainLeavesTheOthersRead(const fs::path& directory) { - fs::create_directories(directory); +void TestCancellingOneDomainLeavesTheOthersRead(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "twodomains.rxp", &vfs); + MountWorld(fs::Join(directory, "twodomains.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); rx::ecs::World world; @@ -1630,12 +1632,12 @@ void TestCancellingOneDomainLeavesTheOthersRead(const fs::path& directory) { CHECK(streamer.stats().entities == 0); // gameplay really did go } -void TestDeterministicAcrossRuns(const fs::path& directory) { - fs::create_directories(directory); +void TestDeterministicAcrossRuns(base::StringRef directory) { + fs::CreateDirectories(directory); Vfs vfs; - MountWorld(directory / "deterministic.rxp", &vfs); + MountWorld(fs::Join(directory, "deterministic.rxp"), &vfs); WorldMap map; - std::string error; + base::String error; CHECK(map.Load(vfs, "world://city/city.rxworld", &error)); auto run = [&] { @@ -1672,45 +1674,45 @@ void TestDeterministicAcrossRuns(const fs::path& directory) { } // namespace int main() { - const fs::path tmp = fs::temp_directory_path() / "rx_world_stream_test"; - fs::remove_all(tmp); - fs::create_directories(tmp); - - TestStreamInAndOut(tmp / "roundtrip"); - TestWideBubbleLoadsEveryCell(tmp / "wide"); - TestBudgetedCommitIsNotResolvableHalfway(tmp / "budget"); - TestUnloadDuringCommitDestroysExactlyWhatWasMade(tmp / "interrupt"); - TestLateResultAfterCancelIsNotPublished(tmp / "late"); - TestSchemaDriftIsRefusedLoudly(tmp / "drift"); - TestOverlayShapesTheCellOnTheWayIn(tmp / "overlay"); - TestOverlayFromAnotherBakeIsRefused(tmp / "mismatch"); - TestOverlayThatDeletesEverythingLeavesNothing(tmp / "wipe"); - TestPromoteAnInstance(tmp / "promote"); - TestShutdownEmptiesTheWorld(tmp / "shutdown"); - TestTierRefinesWhenTheObserverCloses(tmp / "tiers"); - TestSingleTierWorldNeverReloads(tmp / "single"); - TestPersistentFailuresStopRetrying(tmp / "broken"); - TestTeardownRespectsTheBudget(tmp / "teardown"); + const base::String tmp = fs::Join(fs::TempDirectory(), "rx_world_stream_test"); + fs::RemoveAll(tmp); + fs::CreateDirectories(tmp); + + TestStreamInAndOut(fs::Join(tmp, "roundtrip")); + TestWideBubbleLoadsEveryCell(fs::Join(tmp, "wide")); + TestBudgetedCommitIsNotResolvableHalfway(fs::Join(tmp, "budget")); + TestUnloadDuringCommitDestroysExactlyWhatWasMade(fs::Join(tmp, "interrupt")); + TestLateResultAfterCancelIsNotPublished(fs::Join(tmp, "late")); + TestSchemaDriftIsRefusedLoudly(fs::Join(tmp, "drift")); + TestOverlayShapesTheCellOnTheWayIn(fs::Join(tmp, "overlay")); + TestOverlayFromAnotherBakeIsRefused(fs::Join(tmp, "mismatch")); + TestOverlayThatDeletesEverythingLeavesNothing(fs::Join(tmp, "wipe")); + TestPromoteAnInstance(fs::Join(tmp, "promote")); + TestShutdownEmptiesTheWorld(fs::Join(tmp, "shutdown")); + TestTierRefinesWhenTheObserverCloses(fs::Join(tmp, "tiers")); + TestSingleTierWorldNeverReloads(fs::Join(tmp, "single")); + TestPersistentFailuresStopRetrying(fs::Join(tmp, "broken")); + TestTeardownRespectsTheBudget(fs::Join(tmp, "teardown")); TestClaimSet(); - TestClaimKeepsACellResident(tmp / "claims"); - TestClaimDoesNotChangeAResidentCellsTier(tmp / "claimtier"); - TestSuppressionIsAThrottleNotABan(tmp / "throttle"); - TestManyArchetypesColumnsAndChunks(tmp / "wide"); - TestManyObserversFoldToTheNearest(tmp / "observers"); - TestStalePayloadIsNotAdoptedByANewGeneration(tmp / "stalegen"); - TestATransientFailureIsForgottenOnSuccess(tmp / "transient"); - TestResolveRefusesAnEntityTheGameDestroyed(tmp / "destroyed"); - TestDefaultPolicyStreams(tmp / "defaults"); - TestCancelDuringCommitIsAlsoBudgeted(tmp / "cancelbudget"); - TestShutdownMidFlight(tmp / "midflight"); - TestCancellingOneDomainLeavesTheOthersRead(tmp / "twodomains"); - TestDeterministicAcrossRuns(tmp / "deterministic"); - - fs::remove_all(tmp); + TestClaimKeepsACellResident(fs::Join(tmp, "claims")); + TestClaimDoesNotChangeAResidentCellsTier(fs::Join(tmp, "claimtier")); + TestSuppressionIsAThrottleNotABan(fs::Join(tmp, "throttle")); + TestManyArchetypesColumnsAndChunks(fs::Join(tmp, "wide")); + TestManyObserversFoldToTheNearest(fs::Join(tmp, "observers")); + TestStalePayloadIsNotAdoptedByANewGeneration(fs::Join(tmp, "stalegen")); + TestATransientFailureIsForgottenOnSuccess(fs::Join(tmp, "transient")); + TestResolveRefusesAnEntityTheGameDestroyed(fs::Join(tmp, "destroyed")); + TestDefaultPolicyStreams(fs::Join(tmp, "defaults")); + TestCancelDuringCommitIsAlsoBudgeted(fs::Join(tmp, "cancelbudget")); + TestShutdownMidFlight(fs::Join(tmp, "midflight")); + TestCancellingOneDomainLeavesTheOthersRead(fs::Join(tmp, "twodomains")); + TestDeterministicAcrossRuns(fs::Join(tmp, "deterministic")); + + fs::RemoveAll(tmp); if (g_failures) { - std::fprintf(stderr, "world_stream_test: %d failure(s)\n", g_failures); + ::fprintf(stderr, "world_stream_test: %d failure(s)\n", g_failures); return 1; } - std::puts("world_stream_test: ok"); + ::puts("world_stream_test: ok"); return 0; } diff --git a/test/world_streaming_test.cc b/test/world_streaming_test.cc index 57303cb1..86758075 100644 --- a/test/world_streaming_test.cc +++ b/test/world_streaming_test.cc @@ -1,10 +1,11 @@ +#include "base/containers/span.h" #include "scene/world_streaming.h" #include "scene/world_streaming_ecs.h" -#include -#include -#include -#include +#include +#include +#include +#include namespace { @@ -19,13 +20,13 @@ int failures = 0; void Check(bool condition, const char* message) { if (condition) return; - std::fprintf(stderr, "world_streaming_test: FAIL: %s\n", message); + ::fprintf(stderr, "world_streaming_test: FAIL: %s\n", message); ++failures; } void Near(f32 actual, f32 expected, const char* message, f32 epsilon = 1e-4f) { - if (std::abs(actual - expected) <= epsilon) return; - std::fprintf(stderr, "world_streaming_test: FAIL: %s (got %.6f, expected %.6f)\n", message, + if (::abs(actual - expected) <= epsilon) return; + ::fprintf(stderr, "world_streaming_test: FAIL: %s (got %.6f, expected %.6f)\n", message, actual, expected); ++failures; } @@ -55,7 +56,7 @@ const WorldStreamAction* FindAction(const base::Vector& actio WorldStreamTicket PrepareOne(WorldStreamPlan& plan, const WorldStreamObservation& observer, const WorldStreamRegion& region) { base::Vector actions; - AdvanceWorldStreaming(plan, std::span(&observer, 1), std::span(®ion, 1), {}, &actions); + AdvanceWorldStreaming(plan, base::Span(&observer, 1), base::Span(®ion, 1), {}, &actions); Check(actions.size() == 1 && actions[0].kind == WorldStreamActionKind::kPrepare, "an entering region emits one prepare"); return actions.empty() ? WorldStreamTicket{} : actions[0].ticket; @@ -66,7 +67,7 @@ void MakeResident(WorldStreamPlan& plan, const WorldStreamObservation& observer, Check(ApplyWorldStreamPrepareResult(plan, ticket, WorldStreamPrepareResult::kReady), "current prepare result is accepted"); base::Vector actions; - AdvanceWorldStreaming(plan, std::span(&observer, 1), std::span(®ion, 1), {}, &actions); + AdvanceWorldStreaming(plan, base::Span(&observer, 1), base::Span(®ion, 1), {}, &actions); const WorldStreamAction* commit = FindAction(actions, WorldStreamActionKind::kCommit, region.id); Check(commit != nullptr, "a ready region emits a commit quantum"); if (commit) { @@ -131,18 +132,18 @@ void TestDemandAndQueries() { demand = EvaluateWorldStreamDemand(observer, region); Check(demand.load, "invalid ignored-axis position does not disable observation"); - const f32 maximum = std::numeric_limits::max(); + const f32 maximum = FLT_MAX; observer = Observer({maximum, 0, 0}, 1, 2); observer.velocity = {maximum, maximum, 0}; observer.prediction_seconds = maximum; observer.maximum_prediction_distance = maximum; query = BuildWorldStreamQuery(observer); - Check(std::isfinite(query.predicted.x), "large finite prediction remains finite"); + Check(::isfinite(query.predicted.x), "large finite prediction remains finite"); demand = EvaluateWorldStreamDemand(observer, Region(3, -maximum, 0, -maximum, 1)); Check(!demand.load, "overflow-scale distances do not spuriously load a region"); const f32 large = 1.0e30f; - const f32 adjacent = std::nextafter(large, maximum); + const f32 adjacent = ::nextafter(large, maximum); observer = Observer({large, 0, 0}, 0, 0); observer.velocity = {adjacent - large, 0, 0}; observer.prediction_seconds = 1; @@ -161,17 +162,17 @@ void TestLifecycleAndHysteresis() { observer.position.x = -9; base::Vector actions; - AdvanceWorldStreaming(plan, std::span(&observer, 1), std::span(®ion, 1), {}, &actions); + AdvanceWorldStreaming(plan, base::Span(&observer, 1), base::Span(®ion, 1), {}, &actions); Check(actions.empty(), "resident content is stable in the hysteresis band"); observer.position.x = -11; - AdvanceWorldStreaming(plan, std::span(&observer, 1), std::span(®ion, 1), {}, &actions); + AdvanceWorldStreaming(plan, base::Span(&observer, 1), base::Span(®ion, 1), {}, &actions); Check(FindAction(actions, WorldStreamActionKind::kUnload, region.id) != nullptr, "resident content unloads beyond the retain distance"); Check(!IsWorldStreamTicketCurrent(plan, first), "unload invalidates the old generation"); observer.position = {}; - AdvanceWorldStreaming(plan, std::span(&observer, 1), std::span(®ion, 1), {}, &actions); + AdvanceWorldStreaming(plan, base::Span(&observer, 1), base::Span(®ion, 1), {}, &actions); Check(FindAction(actions, WorldStreamActionKind::kPrepare, region.id) == nullptr, "re-entry waits for old-generation retirement"); Check(ApplyWorldStreamRetireResult(plan, first), "synchronous unload retirement is accepted"); @@ -181,7 +182,7 @@ void TestLifecycleAndHysteresis() { "a late result from the old generation is rejected"); observer.position.x = -20; - AdvanceWorldStreaming(plan, std::span(&observer, 1), std::span(®ion, 1), {}, &actions); + AdvanceWorldStreaming(plan, base::Span(&observer, 1), base::Span(®ion, 1), {}, &actions); Check(FindAction(actions, WorldStreamActionKind::kCancel, region.id) != nullptr, "departing in-flight work emits cancellation"); Check(!IsWorldStreamTicketCurrent(plan, second), "cancellation invalidates immediately"); @@ -197,7 +198,7 @@ void TestBudgetsPriorityAndFairness() { budget.maximum_prepare_starts = 2; budget.maximum_pending = 1; base::Vector actions; - AdvanceWorldStreaming(plan, std::span(&observer, 1), regions, budget, &actions); + AdvanceWorldStreaming(plan, base::Span(&observer, 1), regions, budget, &actions); Check(actions.size() == 1 && actions[0].ticket.region == 2, "priority wins and the pending-work limit caps starts"); if (actions.empty()) return; @@ -205,7 +206,7 @@ void TestBudgetsPriorityAndFairness() { const WorldStreamTicket first = actions[0].ticket; ApplyWorldStreamPrepareResult(plan, first, WorldStreamPrepareResult::kReady); budget.maximum_pending = 3; - AdvanceWorldStreaming(plan, std::span(&observer, 1), regions, budget, &actions); + AdvanceWorldStreaming(plan, base::Span(&observer, 1), regions, budget, &actions); Check(FindAction(actions, WorldStreamActionKind::kPrepare, 1) != nullptr, "nearest equal-priority region starts next"); Check(FindAction(actions, WorldStreamActionKind::kPrepare, 3) != nullptr, @@ -222,13 +223,13 @@ void TestBudgetsPriorityAndFairness() { budget.maximum_prepare_starts = 0; budget.maximum_commit_steps = 1; - AdvanceWorldStreaming(plan, std::span(&observer, 1), regions, budget, &actions); + AdvanceWorldStreaming(plan, base::Span(&observer, 1), regions, budget, &actions); Check(actions.size() == 1 && actions[0].kind == WorldStreamActionKind::kCommit, "commit work is bounded to one quantum"); if (actions.empty()) return; const u64 served_first = actions[0].ticket.region; ApplyWorldStreamCommitResult(plan, actions[0].ticket, WorldStreamCommitResult::kMoreWork); - AdvanceWorldStreaming(plan, std::span(&observer, 1), regions, budget, &actions); + AdvanceWorldStreaming(plan, base::Span(&observer, 1), regions, budget, &actions); Check(actions.size() == 1 && actions[0].ticket.region != served_first, "round-robin age prevents a long commit from starving ready peers"); @@ -239,7 +240,7 @@ void TestBudgetsPriorityAndFairness() { one_start.maximum_pending = 1; const WorldStreamRegion low = Region(20, 1, 0, 2, 1, 0); const WorldStreamRegion high = Region(21, 2, 0, 3, 1, 100); - AdvanceWorldStreaming(retry_priority, std::span(&observer, 1), std::span(&low, 1), one_start, + AdvanceWorldStreaming(retry_priority, base::Span(&observer, 1), base::Span(&low, 1), one_start, &actions); if (actions.empty()) { Check(false, "retry setup emits a prepare"); @@ -247,26 +248,26 @@ void TestBudgetsPriorityAndFairness() { } ApplyWorldStreamPrepareResult(retry_priority, actions[0].ticket, WorldStreamPrepareResult::kFailed); - AdvanceWorldStreaming(retry_priority, std::span(&observer, 1), std::span(&low, 1), one_start, + AdvanceWorldStreaming(retry_priority, base::Span(&observer, 1), base::Span(&low, 1), one_start, &actions); const WorldStreamAction* cleanup = FindAction(actions, WorldStreamActionKind::kCancel, low.id); Check(cleanup != nullptr, "failed preparation requests cleanup before retry"); if (!cleanup) return; ApplyWorldStreamRetireResult(retry_priority, cleanup->ticket); const WorldStreamRegion retry_candidates[] = {low, high}; - AdvanceWorldStreaming(retry_priority, std::span(&observer, 1), retry_candidates, one_start, + AdvanceWorldStreaming(retry_priority, base::Span(&observer, 1), retry_candidates, one_start, &actions); Check(actions.size() == 1 && actions[0].ticket.region == high.id, "a low-priority retry cannot starve fresh high-priority demand"); WorldStreamPlan large_distance; const f32 farther = 1.0e30f; - const f32 nearer = std::nextafter(farther, 0.0f); + const f32 nearer = ::nextafter(farther, 0.0f); const WorldStreamObservation maximum_range = - Observer({0, 0, 0}, std::numeric_limits::max(), std::numeric_limits::max()); + Observer({0, 0, 0}, FLT_MAX, FLT_MAX); const WorldStreamRegion distant[] = {Region(1, farther, 0, farther, 1), Region(2, nearer, 0, nearer, 1)}; - AdvanceWorldStreaming(large_distance, std::span(&maximum_range, 1), distant, one_start, + AdvanceWorldStreaming(large_distance, base::Span(&maximum_range, 1), distant, one_start, &actions); Check(actions.size() == 1 && actions[0].ticket.region == 2, "large finite distances retain nearest-first priority ordering"); @@ -277,7 +278,7 @@ void TestBudgetsPriorityAndFairness() { WorldStreamTicket continuation_ticket = PrepareOne(continuation, observer, long_commit); ApplyWorldStreamPrepareResult(continuation, continuation_ticket, WorldStreamPrepareResult::kReady); - AdvanceWorldStreaming(continuation, std::span(&observer, 1), std::span(&long_commit, 1), {}, + AdvanceWorldStreaming(continuation, base::Span(&observer, 1), base::Span(&long_commit, 1), {}, &actions); const WorldStreamAction* initial_commit = FindAction(actions, WorldStreamActionKind::kCommit, long_commit.id); @@ -289,7 +290,7 @@ void TestBudgetsPriorityAndFairness() { WorldStreamFrameBudget admit_fresh; admit_fresh.maximum_commit_steps = 0; admit_fresh.maximum_pending = 2; - AdvanceWorldStreaming(continuation, std::span(&observer, 1), commit_candidates, admit_fresh, + AdvanceWorldStreaming(continuation, base::Span(&observer, 1), commit_candidates, admit_fresh, &actions); const WorldStreamAction* fresh_prepare = FindAction(actions, WorldStreamActionKind::kPrepare, fresh_commit.id); @@ -301,7 +302,7 @@ void TestBudgetsPriorityAndFairness() { one_commit.maximum_prepare_starts = 0; one_commit.maximum_commit_steps = 1; one_commit.maximum_pending = 2; - AdvanceWorldStreaming(continuation, std::span(&observer, 1), commit_candidates, one_commit, + AdvanceWorldStreaming(continuation, base::Span(&observer, 1), commit_candidates, one_commit, &actions); Check(actions.size() == 1 && actions[0].ticket.region == long_commit.id, "fresh ready work cannot starve an older commit continuation"); @@ -314,14 +315,14 @@ void TestBudgetsPriorityAndFairness() { ApplyWorldStreamPrepareResult(retry_fairness, failure_ticket, WorldStreamPrepareResult::kFailed); const WorldStreamRegion fairness_candidates[] = {repeated_failure, waiting_fresh}; - AdvanceWorldStreaming(retry_fairness, std::span(&observer, 1), fairness_candidates, one_start, + AdvanceWorldStreaming(retry_fairness, base::Span(&observer, 1), fairness_candidates, one_start, &actions); const WorldStreamAction* first_cleanup = FindAction(actions, WorldStreamActionKind::kCancel, repeated_failure.id); Check(first_cleanup != nullptr, "retry fairness setup emits cleanup"); if (!first_cleanup) return; ApplyWorldStreamRetireResult(retry_fairness, first_cleanup->ticket); - AdvanceWorldStreaming(retry_fairness, std::span(&observer, 1), fairness_candidates, one_start, + AdvanceWorldStreaming(retry_fairness, base::Span(&observer, 1), fairness_candidates, one_start, &actions); Check(actions.size() == 1 && actions[0].ticket.region == repeated_failure.id, "an older retry gets its next fair prepare turn"); @@ -329,14 +330,14 @@ void TestBudgetsPriorityAndFairness() { failure_ticket = actions[0].ticket; ApplyWorldStreamPrepareResult(retry_fairness, failure_ticket, WorldStreamPrepareResult::kFailed); - AdvanceWorldStreaming(retry_fairness, std::span(&observer, 1), fairness_candidates, one_start, + AdvanceWorldStreaming(retry_fairness, base::Span(&observer, 1), fairness_candidates, one_start, &actions); const WorldStreamAction* second_cleanup = FindAction(actions, WorldStreamActionKind::kCancel, repeated_failure.id); Check(second_cleanup != nullptr, "repeated failure emits another cleanup"); if (!second_cleanup) return; ApplyWorldStreamRetireResult(retry_fairness, second_cleanup->ticket); - AdvanceWorldStreaming(retry_fairness, std::span(&observer, 1), fairness_candidates, one_start, + AdvanceWorldStreaming(retry_fairness, base::Span(&observer, 1), fairness_candidates, one_start, &actions); Check(actions.size() == 1 && actions[0].ticket.region == waiting_fresh.id, "a repeatedly failing retry cannot starve older fresh demand"); @@ -349,7 +350,7 @@ base::Vector PrepareOrder(const WorldStreamRegion* regions, size_t count) { budget.maximum_prepare_starts = 10; budget.maximum_pending = 10; base::Vector actions; - AdvanceWorldStreaming(plan, std::span(&observer, 1), std::span(regions, count), budget, &actions); + AdvanceWorldStreaming(plan, base::Span(&observer, 1), base::Span(regions, count), budget, &actions); base::Vector order; for (const WorldStreamAction& action : actions) order.push_back(action.ticket.region); return order; @@ -387,9 +388,9 @@ void TestDeterminismAndMultipleObservers() { cleanup_budget.maximum_prepare_starts = 3; cleanup_budget.maximum_pending = 3; const WorldStreamObservation broad = Observer({0, 0, 0}, 100, 110); - AdvanceWorldStreaming(cleanup, std::span(&broad, 1), reverse, cleanup_budget, &actions); + AdvanceWorldStreaming(cleanup, base::Span(&broad, 1), reverse, cleanup_budget, &actions); const WorldStreamObservation departed = Observer({1000, 0, 1000}, 1, 2); - AdvanceWorldStreaming(cleanup, std::span(&departed, 1), reverse, cleanup_budget, &actions); + AdvanceWorldStreaming(cleanup, base::Span(&departed, 1), reverse, cleanup_budget, &actions); Check(actions.size() == 3 && actions[0].ticket.region == 2 && actions[1].ticket.region == 5 && actions[2].ticket.region == 9, "cancellation order is stable by region id"); @@ -404,7 +405,7 @@ void TestRetryResetAndGather() { ApplyWorldStreamPrepareResult(plan, failed, WorldStreamPrepareResult::kFailed); base::Vector actions; - AdvanceWorldStreaming(plan, std::span(&observer, 1), std::span(®ion, 1), {}, &actions); + AdvanceWorldStreaming(plan, base::Span(&observer, 1), base::Span(®ion, 1), {}, &actions); Check(FindAction(actions, WorldStreamActionKind::kPrepare, region.id) == nullptr, "failed work observes its retry delay"); const WorldStreamAction* failed_cleanup = @@ -412,7 +413,7 @@ void TestRetryResetAndGather() { Check(failed_cleanup != nullptr, "failed work is cleaned before its retry delay"); if (!failed_cleanup) return; ApplyWorldStreamRetireResult(plan, failed_cleanup->ticket); - AdvanceWorldStreaming(plan, std::span(&observer, 1), std::span(®ion, 1), {}, &actions); + AdvanceWorldStreaming(plan, base::Span(&observer, 1), base::Span(®ion, 1), {}, &actions); const WorldStreamAction* retry = FindAction(actions, WorldStreamActionKind::kPrepare, region.id); Check(retry && retry->ticket.generation != failed.generation, "retry starts with a new generation after the delay"); @@ -471,13 +472,13 @@ void TestRetirementMetadataAndPendingPayloads() { WorldStreamRegion moved = Region(10, 20, 0, 21, 1); base::Vector actions; - AdvanceWorldStreaming(plan, std::span(&observer, 1), std::span(&moved, 1), {}, &actions); + AdvanceWorldStreaming(plan, base::Span(&observer, 1), base::Span(&moved, 1), {}, &actions); Check(FindAction(actions, WorldStreamActionKind::kCancel, moved.id) != nullptr, "identity-affecting metadata changes retire the old payload"); Check(!ApplyWorldStreamPrepareResult(plan, old, WorldStreamPrepareResult::kReady), "metadata retirement rejects the old preparation result"); - AdvanceWorldStreaming(plan, std::span(&observer, 1), std::span(&moved, 1), {}, &actions); + AdvanceWorldStreaming(plan, base::Span(&observer, 1), base::Span(&moved, 1), {}, &actions); Check(FindAction(actions, WorldStreamActionKind::kPrepare, moved.id) == nullptr, "replacement does not overlap a retiring payload"); Check(ApplyWorldStreamRetireResult(plan, old), "metadata cleanup retirement is accepted"); @@ -490,11 +491,11 @@ void TestRetirementMetadataAndPendingPayloads() { budget.maximum_prepare_starts = 2; budget.maximum_commit_steps = 0; budget.maximum_pending = 1; - AdvanceWorldStreaming(bounded, std::span(&observer, 1), regions, budget, &actions); + AdvanceWorldStreaming(bounded, base::Span(&observer, 1), regions, budget, &actions); Check(actions.size() == 1, "pending cap admits only one preparation"); if (actions.empty()) return; ApplyWorldStreamPrepareResult(bounded, actions[0].ticket, WorldStreamPrepareResult::kReady); - AdvanceWorldStreaming(bounded, std::span(&observer, 1), regions, budget, &actions); + AdvanceWorldStreaming(bounded, base::Span(&observer, 1), regions, budget, &actions); Check(FindAction(actions, WorldStreamActionKind::kPrepare, 2) == nullptr, "prepared payloads continue to consume the pending cap"); @@ -502,14 +503,14 @@ void TestRetirementMetadataAndPendingPayloads() { ConfigureWorldStreaming(failed_metadata, {.retry_delay_ticks = 100}); const WorldStreamTicket failed = PrepareOne(failed_metadata, observer, original); ApplyWorldStreamPrepareResult(failed_metadata, failed, WorldStreamPrepareResult::kFailed); - AdvanceWorldStreaming(failed_metadata, std::span(&observer, 1), std::span(&moved, 1), {}, + AdvanceWorldStreaming(failed_metadata, base::Span(&observer, 1), base::Span(&moved, 1), {}, &actions); const WorldStreamAction* metadata_cleanup = FindAction(actions, WorldStreamActionKind::kCancel, original.id); Check(metadata_cleanup != nullptr, "failed old metadata is cleaned before replacement"); if (!metadata_cleanup) return; ApplyWorldStreamRetireResult(failed_metadata, metadata_cleanup->ticket); - AdvanceWorldStreaming(failed_metadata, std::span(&observer, 1), std::span(&moved, 1), {}, + AdvanceWorldStreaming(failed_metadata, base::Span(&observer, 1), base::Span(&moved, 1), {}, &actions); Check(FindAction(actions, WorldStreamActionKind::kPrepare, moved.id) != nullptr, "new metadata is not held behind an old generation's retry delay"); @@ -525,14 +526,14 @@ void TestFailuresCatalogRemovalAndUnloadAdmission() { WorldStreamTicket ticket = PrepareOne(prepare_failure, observer, region); ApplyWorldStreamPrepareResult(prepare_failure, ticket, WorldStreamPrepareResult::kFailed); observer.position.x = -6; - AdvanceWorldStreaming(prepare_failure, std::span(&observer, 1), std::span(®ion, 1), {}, + AdvanceWorldStreaming(prepare_failure, base::Span(&observer, 1), base::Span(®ion, 1), {}, &actions); const WorldStreamAction* prepare_cleanup = FindAction(actions, WorldStreamActionKind::kCancel, region.id); Check(prepare_cleanup != nullptr, "prepare failure requests partial-payload cleanup"); if (!prepare_cleanup) return; ApplyWorldStreamRetireResult(prepare_failure, prepare_cleanup->ticket); - AdvanceWorldStreaming(prepare_failure, std::span(&observer, 1), std::span(®ion, 1), {}, + AdvanceWorldStreaming(prepare_failure, base::Span(&observer, 1), base::Span(®ion, 1), {}, &actions); Check(FindAction(actions, WorldStreamActionKind::kPrepare, region.id) != nullptr, "failed requested content retries in the retain-only band"); @@ -542,7 +543,7 @@ void TestFailuresCatalogRemovalAndUnloadAdmission() { observer.position = {}; ticket = PrepareOne(commit_failure, observer, region); ApplyWorldStreamPrepareResult(commit_failure, ticket, WorldStreamPrepareResult::kReady); - AdvanceWorldStreaming(commit_failure, std::span(&observer, 1), std::span(®ion, 1), {}, + AdvanceWorldStreaming(commit_failure, base::Span(&observer, 1), base::Span(®ion, 1), {}, &actions); const WorldStreamAction* commit = FindAction(actions, WorldStreamActionKind::kCommit, region.id); Check(commit != nullptr, "prepared content reaches commit before failure test"); @@ -551,12 +552,12 @@ void TestFailuresCatalogRemovalAndUnloadAdmission() { ticket = commit->ticket; } observer.position.x = -6; - AdvanceWorldStreaming(commit_failure, std::span(&observer, 1), std::span(®ion, 1), {}, + AdvanceWorldStreaming(commit_failure, base::Span(&observer, 1), base::Span(®ion, 1), {}, &actions); Check(FindAction(actions, WorldStreamActionKind::kCancel, region.id) != nullptr, "failed partial commit requests cleanup"); ApplyWorldStreamRetireResult(commit_failure, ticket); - AdvanceWorldStreaming(commit_failure, std::span(&observer, 1), std::span(®ion, 1), {}, + AdvanceWorldStreaming(commit_failure, base::Span(&observer, 1), base::Span(®ion, 1), {}, &actions); Check(FindAction(actions, WorldStreamActionKind::kPrepare, region.id) != nullptr, "commit failure restarts while retained"); @@ -564,7 +565,7 @@ void TestFailuresCatalogRemovalAndUnloadAdmission() { WorldStreamPlan removed_pending; observer.position = {}; ticket = PrepareOne(removed_pending, observer, region); - AdvanceWorldStreaming(removed_pending, std::span(&observer, 1), {}, {}, &actions); + AdvanceWorldStreaming(removed_pending, base::Span(&observer, 1), {}, {}, &actions); Check(FindAction(actions, WorldStreamActionKind::kCancel, region.id) != nullptr, "catalog removal cancels pending content"); ApplyWorldStreamRetireResult(removed_pending, ticket); @@ -574,15 +575,15 @@ void TestFailuresCatalogRemovalAndUnloadAdmission() { MakeResident(delayed_unload, observer, region, ticket); WorldStreamFrameBudget no_unload; no_unload.maximum_unloads = 0; - AdvanceWorldStreaming(delayed_unload, std::span(&observer, 1), {}, no_unload, &actions); + AdvanceWorldStreaming(delayed_unload, base::Span(&observer, 1), {}, no_unload, &actions); Check(actions.empty() && GetWorldStreamStats(delayed_unload).resident == 1 && IsWorldStreamTicketCurrent(delayed_unload, ticket), "resident content stays current until an unload slot is admitted"); - AdvanceWorldStreaming(delayed_unload, std::span(&observer, 1), std::span(®ion, 1), no_unload, + AdvanceWorldStreaming(delayed_unload, base::Span(&observer, 1), base::Span(®ion, 1), no_unload, &actions); Check(actions.empty() && GetWorldStreamStats(delayed_unload).resident == 1, "re-entry before unload dispatch keeps the resident generation"); - AdvanceWorldStreaming(delayed_unload, std::span(&observer, 1), {}, {}, &actions); + AdvanceWorldStreaming(delayed_unload, base::Span(&observer, 1), {}, {}, &actions); Check(FindAction(actions, WorldStreamActionKind::kUnload, region.id) != nullptr, "catalog removal unloads resident content once admitted"); @@ -593,11 +594,11 @@ void TestFailuresCatalogRemovalAndUnloadAdmission() { fill.maximum_prepare_starts = 3; fill.maximum_commit_steps = 3; fill.maximum_pending = 3; - AdvanceWorldStreaming(bounded_unloads, std::span(&observer, 1), regions, fill, &actions); + AdvanceWorldStreaming(bounded_unloads, base::Span(&observer, 1), regions, fill, &actions); for (const WorldStreamAction& action : actions) { ApplyWorldStreamPrepareResult(bounded_unloads, action.ticket, WorldStreamPrepareResult::kReady); } - AdvanceWorldStreaming(bounded_unloads, std::span(&observer, 1), regions, fill, &actions); + AdvanceWorldStreaming(bounded_unloads, base::Span(&observer, 1), regions, fill, &actions); for (const WorldStreamAction& action : actions) { ApplyWorldStreamCommitResult(bounded_unloads, action.ticket, WorldStreamCommitResult::kComplete); @@ -605,7 +606,7 @@ void TestFailuresCatalogRemovalAndUnloadAdmission() { WorldStreamFrameBudget retire; retire.maximum_unloads = 3; retire.maximum_pending = 2; - AdvanceWorldStreaming(bounded_unloads, std::span(&observer, 1), {}, retire, &actions); + AdvanceWorldStreaming(bounded_unloads, base::Span(&observer, 1), {}, retire, &actions); const WorldStreamStats stats = GetWorldStreamStats(bounded_unloads); Check(actions.size() == 2 && stats.retiring == 2 && stats.resident == 1, "unload admission cannot exceed the pending payload cap"); @@ -621,7 +622,7 @@ void TestFailuresCatalogRemovalAndUnloadAdmission() { one_pending.maximum_prepare_starts = 1; one_pending.maximum_unloads = 1; one_pending.maximum_pending = 1; - AdvanceWorldStreaming(cleanup_first, std::span(&moved_observer, 1), moved_candidates, + AdvanceWorldStreaming(cleanup_first, base::Span(&moved_observer, 1), moved_candidates, one_pending, &actions); Check(FindAction(actions, WorldStreamActionKind::kUnload, old_region.id) != nullptr && FindAction(actions, WorldStreamActionKind::kPrepare, fresh_region.id) == nullptr, @@ -636,13 +637,13 @@ void TestFailuresCatalogRemovalAndUnloadAdmission() { fill_two.maximum_prepare_starts = 2; fill_two.maximum_commit_steps = 2; fill_two.maximum_pending = 2; - AdvanceWorldStreaming(unload_fairness, std::span(&broad, 1), resident_regions, fill_two, + AdvanceWorldStreaming(unload_fairness, base::Span(&broad, 1), resident_regions, fill_two, &actions); for (const WorldStreamAction& action : actions) { ApplyWorldStreamPrepareResult(unload_fairness, action.ticket, WorldStreamPrepareResult::kReady); } - AdvanceWorldStreaming(unload_fairness, std::span(&broad, 1), resident_regions, fill_two, + AdvanceWorldStreaming(unload_fairness, base::Span(&broad, 1), resident_regions, fill_two, &actions); for (const WorldStreamAction& action : actions) { ApplyWorldStreamCommitResult(unload_fairness, action.ticket, @@ -654,11 +655,11 @@ void TestFailuresCatalogRemovalAndUnloadAdmission() { hold_unloads.maximum_commit_steps = 0; hold_unloads.maximum_unloads = 0; hold_unloads.maximum_pending = 1; - AdvanceWorldStreaming(unload_fairness, std::span(&near_low, 1), resident_regions, hold_unloads, + AdvanceWorldStreaming(unload_fairness, base::Span(&near_low, 1), resident_regions, hold_unloads, &actions); const WorldStreamObservation far_away = Observer({1000, 0, 0}, 1, 2); hold_unloads.maximum_unloads = 1; - AdvanceWorldStreaming(unload_fairness, std::span(&far_away, 1), resident_regions, hold_unloads, + AdvanceWorldStreaming(unload_fairness, base::Span(&far_away, 1), resident_regions, hold_unloads, &actions); Check(actions.size() == 1 && actions[0].ticket.region == high_id.id, "the oldest unload request wins before a newly obsolete lower id"); @@ -680,7 +681,7 @@ void TestBoundedTraversalStress() { candidates.push_back( Region(static_cast(x + 1000), static_cast(x), 0, static_cast(x + 1), 1)); } - AdvanceWorldStreaming(plan, std::span(&observer, 1), candidates, budget, &actions); + AdvanceWorldStreaming(plan, base::Span(&observer, 1), candidates, budget, &actions); for (const WorldStreamAction& action : actions) { if (action.kind == WorldStreamActionKind::kPrepare) { ApplyWorldStreamPrepareResult(plan, action.ticket, WorldStreamPrepareResult::kReady); @@ -712,9 +713,9 @@ int main() { TestBoundedTraversalStress(); if (failures != 0) { - std::fprintf(stderr, "world_streaming_test: %d failure(s)\n", failures); + ::fprintf(stderr, "world_streaming_test: %d failure(s)\n", failures); return 1; } - std::printf("world_streaming_test: PASS\n"); + ::printf("world_streaming_test: PASS\n"); return 0; } diff --git a/third_party/equilibrium b/third_party/equilibrium index 6a1e78cf..f2538c15 160000 --- a/third_party/equilibrium +++ b/third_party/equilibrium @@ -1 +1 @@ -Subproject commit 6a1e78cffcdc542cf19c53df7c1ddb156858b9f3 +Subproject commit f2538c15743c27451247f46e5c2b4cbead5e7e87 diff --git a/tools/rxcall.cc b/tools/rxcall.cc index c349911f..088ce0c5 100644 --- a/tools/rxcall.cc +++ b/tools/rxcall.cc @@ -16,13 +16,16 @@ #include #include -#include -#include -#include -#include -#include -#include - +#include +#include +#include +#include + +#include "base/containers/vector.h" +#include "base/memory/mem_ops.h" +#include "base/optional.h" +#include "base/strings/xstring.h" +#include "core/format.h" #include "rpc/rpc_message.h" #include "rpc/rpc_value.h" @@ -32,30 +35,30 @@ using rx::u32; namespace { -int Fail(const std::string& message) { - std::fprintf(stderr, "rxcall: %s\n", message.c_str()); +int Fail(const base::String& message) { + ::fprintf(stderr, "rxcall: %s\n", message.c_str()); return 1; } rpc::RpcValue ParseArg(const char* text) { - if (std::strcmp(text, "true") == 0) return rpc::RpcValue(true); - if (std::strcmp(text, "false") == 0) return rpc::RpcValue(false); + if (::strcmp(text, "true") == 0) return rpc::RpcValue(true); + if (::strcmp(text, "false") == 0) return rpc::RpcValue(false); char* end = nullptr; - const long long as_int = std::strtoll(text, &end, 10); + const long long as_int = ::strtoll(text, &end, 10); if (end != text && *end == '\0') return rpc::RpcValue(static_cast(as_int)); - const double as_float = std::strtod(text, &end); + const double as_float = ::strtod(text, &end); if (end != text && *end == '\0') return rpc::RpcValue(as_float); - return rpc::RpcValue(std::string(text)); + return rpc::RpcValue(base::String(text)); } -std::string Format(const rpc::RpcValue& value) { +base::String Format(const rpc::RpcValue& value) { switch (value.type()) { case rpc::RpcValue::Type::kNull: return "null"; case rpc::RpcValue::Type::kBool: return value.as_bool() ? "true" : "false"; - case rpc::RpcValue::Type::kInt: return std::to_string(value.as_int()); + case rpc::RpcValue::Type::kInt: return rx::ToString(value.as_int()); case rpc::RpcValue::Type::kFloat: { char buffer[64]; - std::snprintf(buffer, sizeof(buffer), "%g", value.as_float()); + ::snprintf(buffer, sizeof(buffer), "%g", value.as_float()); return buffer; } case rpc::RpcValue::Type::kString: return value.as_string(); @@ -83,10 +86,10 @@ bool ReadExactly(int fd, u8* out, size_t count) { int main(int argc, char** argv) { if (argc < 3) { - std::fprintf(stderr, "usage: rxcall [args...]\n"); + ::fprintf(stderr, "usage: rxcall [args...]\n"); return 2; } - const std::string socket_path = argv[1]; + const base::String socket_path = argv[1]; rpc::RpcCall call; call.name = argv[2]; @@ -95,19 +98,19 @@ int main(int argc, char** argv) { sockaddr_un addr{}; addr.sun_family = AF_UNIX; if (socket_path.size() >= sizeof(addr.sun_path)) return Fail("socket path is too long"); - std::memcpy(addr.sun_path, socket_path.c_str(), socket_path.size()); + base::MemCopy(addr.sun_path, socket_path.c_str(), socket_path.size()); const int fd = ::socket(AF_UNIX, SOCK_STREAM, 0); - if (fd < 0) return Fail(std::string("socket: ") + std::strerror(errno)); + if (fd < 0) return Fail(base::String("socket: ") + ::strerror(errno)); if (::connect(fd, reinterpret_cast(&addr), sizeof(addr)) != 0) { - const std::string reason = std::strerror(errno); + const base::String reason = ::strerror(errno); ::close(fd); return Fail("connect " + socket_path + ": " + reason + " (is rx running with --authoring-endpoint?)"); } - std::vector frame; - const std::vector payload = rpc::EncodeCall(call); + base::Vector frame; + const base::Vector payload = rpc::EncodeCall(call); for (int i = 0; i < 4; ++i) frame.push_back(static_cast(static_cast(payload.size()) >> (8 * i))); frame.insert(frame.end(), payload.begin(), payload.end()); @@ -123,24 +126,24 @@ int main(int argc, char** argv) { } const u32 length = u32(header[0]) | u32(header[1]) << 8 | u32(header[2]) << 16 | u32(header[3]) << 24; - std::vector reply_bytes(length); + base::Vector reply_bytes(length); if (length && !ReadExactly(fd, reply_bytes.data(), length)) { ::close(fd); return Fail("truncated reply"); } ::close(fd); - std::optional reply = rpc::DecodeCall(reply_bytes.data(), reply_bytes.size()); + base::Optional reply = rpc::DecodeCall(reply_bytes.data(), reply_bytes.size()); if (!reply) return Fail("malformed reply"); if (reply->name != "ok") { return Fail(reply->args.empty() ? "refused" : Format(reply->args[0])); } - std::string out; + base::String out; for (const rpc::RpcValue& value : reply->args) { if (!out.empty()) out += ' '; out += Format(value); } - std::printf("%s\n", out.c_str()); + ::printf("%s\n", out.c_str()); return 0; } diff --git a/tools/rxdiff.cc b/tools/rxdiff.cc index 02cb4d67..d8e84f13 100644 --- a/tools/rxdiff.cc +++ b/tools/rxdiff.cc @@ -21,15 +21,15 @@ // // Exit 0 within tolerance, 1 when either metric is over, 2 on usage/io error. -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include #include +#include "base/containers/vector.h" +#include "base/strings/xstring.h" namespace { @@ -57,8 +57,8 @@ struct Image { unsigned char* pixels = nullptr; // rgb8, stbi-owned }; -int Fail(const std::string& message) { - std::fprintf(stderr, "rxdiff: %s\n", message.c_str()); +int Fail(const base::String& message) { + ::fprintf(stderr, "rxdiff: %s\n", message.c_str()); return 2; } @@ -80,7 +80,7 @@ struct Result { }; Result Compare(const Image& a, const Image& b, float hot_delta, - std::vector* diff) { + base::Vector* diff) { Result result; result.total_pixels = static_cast(a.width) * a.height; if (diff) diff->assign(static_cast(result.total_pixels) * 3, 0); @@ -97,7 +97,7 @@ Result Compare(const Image& a, const Image& b, float hot_delta, (static_cast(a.pixels[base + c]) - static_cast(b.pixels[base + c])) / 255.0f; sum_squares += static_cast(delta) * delta; - const float magnitude = std::fabs(delta); + const float magnitude = ::fabs(delta); if (magnitude > worst) worst = magnitude; // Amplified so a difference the eye cannot find in the source frames is // obvious in the diff; saturating is the point, not a defect. @@ -115,7 +115,7 @@ Result Compare(const Image& a, const Image& b, float hot_delta, if (y > result.max_y) result.max_y = y; } } - result.rmse = std::sqrt(sum_squares / (static_cast(result.total_pixels) * 3.0)); + result.rmse = ::sqrt(sum_squares / (static_cast(result.total_pixels) * 3.0)); return result; } @@ -131,11 +131,11 @@ int main(int argc, char** argv) { bool json = false; for (int i = 1; i < argc; ++i) { - const std::string arg = argv[i]; + const base::String arg = argv[i]; auto next = [&]() -> const char* { return i + 1 < argc ? argv[++i] : ""; }; - if (arg == "--rmse") rmse_limit = std::strtof(next(), nullptr); - else if (arg == "--hot") hot_limit = std::strtof(next(), nullptr); - else if (arg == "--hot-delta") hot_delta = std::strtof(next(), nullptr); + if (arg == "--rmse") rmse_limit = ::strtof(next(), nullptr); + else if (arg == "--hot") hot_limit = ::strtof(next(), nullptr); + else if (arg == "--hot-delta") hot_delta = ::strtof(next(), nullptr); else if (arg == "--diff") diff_path = next(); else if (arg == "--json") json = true; else if (!path_a) path_a = argv[i]; @@ -143,7 +143,7 @@ int main(int argc, char** argv) { else return Fail("unexpected argument '" + arg + "'"); } if (!path_a || !path_b) { - std::fprintf(stderr, + ::fprintf(stderr, "usage: rxdiff [--rmse ] [--hot ] [--hot-delta ]\n" " [--diff ] [--json]\n" " defaults: --rmse %g --hot %g --hot-delta %g, measured against this " @@ -155,19 +155,19 @@ int main(int argc, char** argv) { Image a; Image b; - if (!Load(path_a, &a)) return Fail(std::string("cannot read '") + path_a + "'"); - if (!Load(path_b, &b)) return Fail(std::string("cannot read '") + path_b + "'"); + if (!Load(path_a, &a)) return Fail(base::String("cannot read '") + path_a + "'"); + if (!Load(path_b, &b)) return Fail(base::String("cannot read '") + path_b + "'"); if (a.width != b.width || a.height != b.height) { char message[160]; - std::snprintf(message, sizeof(message), "size mismatch: %dx%d vs %dx%d", a.width, a.height, + ::snprintf(message, sizeof(message), "size mismatch: %dx%d vs %dx%d", a.width, a.height, b.width, b.height); return Fail(message); } - std::vector diff; + base::Vector diff; const Result result = Compare(a, b, hot_delta, diff_path ? &diff : nullptr); if (diff_path && !stbi_write_png(diff_path, a.width, a.height, 3, diff.data(), a.width * 3)) { - return Fail(std::string("cannot write '") + diff_path + "'"); + return Fail(base::String("cannot write '") + diff_path + "'"); } const double hot_fraction = @@ -175,31 +175,31 @@ int main(int argc, char** argv) { const bool pass = result.rmse <= rmse_limit && hot_fraction <= hot_limit; if (json) { - std::printf("{\n \"a\": \"%s\",\n \"b\": \"%s\",\n \"width\": %d,\n \"height\": %d,\n", + ::printf("{\n \"a\": \"%s\",\n \"b\": \"%s\",\n \"width\": %d,\n \"height\": %d,\n", path_a, path_b, a.width, a.height); - std::printf(" \"rmse\": %.6f,\n \"rmse_limit\": %.6f,\n", result.rmse, + ::printf(" \"rmse\": %.6f,\n \"rmse_limit\": %.6f,\n", result.rmse, static_cast(rmse_limit)); - std::printf(" \"max_delta\": %.6f,\n \"hot_pixels\": %lld,\n \"hot_fraction\": %.6f,\n" + ::printf(" \"max_delta\": %.6f,\n \"hot_pixels\": %lld,\n \"hot_fraction\": %.6f,\n" " \"hot_limit\": %.6f,\n", result.max_delta, result.hot_pixels, hot_fraction, static_cast(hot_limit)); if (result.max_x >= result.min_x) { - std::printf(" \"hot_bounds\": {\"x\": %d, \"y\": %d, \"w\": %d, \"h\": %d},\n", + ::printf(" \"hot_bounds\": {\"x\": %d, \"y\": %d, \"w\": %d, \"h\": %d},\n", result.min_x, result.min_y, result.max_x - result.min_x + 1, result.max_y - result.min_y + 1); } - std::printf(" \"pass\": %s\n}\n", pass ? "true" : "false"); + ::printf(" \"pass\": %s\n}\n", pass ? "true" : "false"); } else { - std::printf("%s vs %s (%dx%d): rmse %.5f (limit %.5f), max delta %.5f, " + ::printf("%s vs %s (%dx%d): rmse %.5f (limit %.5f), max delta %.5f, " "%lld hot pixel(s) = %.4f%% (limit %.4f%%)\n", path_a, path_b, a.width, a.height, result.rmse, static_cast(rmse_limit), result.max_delta, result.hot_pixels, hot_fraction * 100.0, static_cast(hot_limit) * 100.0); if (result.max_x >= result.min_x) { - std::printf(" hot region: %dx%d at %d,%d\n", result.max_x - result.min_x + 1, + ::printf(" hot region: %dx%d at %d,%d\n", result.max_x - result.min_x + 1, result.max_y - result.min_y + 1, result.min_x, result.min_y); } - std::printf(" %s\n", pass ? "PASS" : "FAIL"); + ::printf(" %s\n", pass ? "PASS" : "FAIL"); } stbi_image_free(a.pixels); diff --git a/tools/rxpack.cc b/tools/rxpack.cc index 47fcce15..41c85cfa 100644 --- a/tools/rxpack.cc +++ b/tools/rxpack.cc @@ -10,23 +10,25 @@ // fall back to raw storage automatically). extract writes entries back out as // loose files. verify decompresses every entry against its checksum. -#include -#include -#include -#include -#include -#include +#include +#include +#include #include "asset/asset_id.h" #include "asset/pack.h" +#include "base/memory/move.h" +#include "base/optional.h" +#include "base/strings/xstring.h" +#include "core/file_system.h" +#include "core/format.h" -namespace fs = std::filesystem; +namespace fs = rx::fs; namespace asset = rx::asset; namespace { int Usage() { - std::fprintf(stderr, + ::fprintf(stderr, "usage: rxpack create [--store] [--level N]\n" " rxpack list \n" " rxpack extract [virtual paths...]\n" @@ -34,54 +36,52 @@ int Usage() { return 2; } -int Fail(const std::string& msg) { - std::fprintf(stderr, "rxpack: %s\n", msg.c_str()); +int Fail(const base::String& msg) { + ::fprintf(stderr, "rxpack: %s\n", msg.c_str()); return 1; } -std::optional> ReadFileBytes(const fs::path& path) { - std::ifstream file(path, std::ios::binary | std::ios::ate); - if (!file) return std::nullopt; - base::Vector data(static_cast(file.tellg())); - file.seekg(0); - file.read(reinterpret_cast(data.data()), static_cast(data.size())); - if (!file) return std::nullopt; +base::Optional> ReadFileBytes(base::StringRef path) { + base::Vector data; + if (!fs::ReadFile(path, &data)) return base::nullopt; return data; } int Create(int argc, char** argv) { if (argc < 4) return Usage(); - const std::string archive = argv[2]; - const fs::path input_dir = argv[3]; + const base::String archive = argv[2]; + const base::String input_dir = argv[3]; asset::PackCompression compression = asset::PackCompression::kDeflate; asset::PackWriter writer; for (int i = 4; i < argc; ++i) { - if (std::strcmp(argv[i], "--store") == 0) { + if (::strcmp(argv[i], "--store") == 0) { compression = asset::PackCompression::kStore; - } else if (std::strcmp(argv[i], "--level") == 0 && i + 1 < argc) { - writer.set_compression_level(std::atoi(argv[++i])); + } else if (::strcmp(argv[i], "--level") == 0 && i + 1 < argc) { + writer.set_compression_level(::atoi(argv[++i])); } else { return Usage(); } } - if (!fs::is_directory(input_dir)) return Fail("not a directory: " + input_dir.string()); + if (!fs::IsDirectory(input_dir)) return Fail("not a directory: " + input_dir); u64 total_in = 0; - for (const auto& entry : fs::recursive_directory_iterator(input_dir)) { - if (!entry.is_regular_file()) continue; - const std::string virtual_path = - asset::NormalizePath(fs::relative(entry.path(), input_dir).generic_string()); - std::optional> bytes = ReadFileBytes(entry.path()); - if (!bytes) return Fail("cannot read " + entry.path().string()); + base::Vector entries; + if (!fs::ListDirectory(input_dir, &entries, /*recursive=*/true)) + return Fail("cannot list " + input_dir); + for (const fs::DirEntry& entry : entries) { + if (!entry.is_regular) continue; + const base::String virtual_path = + asset::NormalizePath(fs::GenericString(fs::Relative(entry.path, input_dir))); + base::Optional> bytes = ReadFileBytes(entry.path); + if (!bytes) return Fail("cannot read " + entry.path); total_in += bytes->size(); - writer.Add(virtual_path, std::move(*bytes), compression); + writer.Add(virtual_path, base::move(*bytes), compression); } - if (writer.entry_count() == 0) return Fail("no files under " + input_dir.string()); + if (writer.entry_count() == 0) return Fail("no files under " + input_dir); if (!writer.WriteTo(archive)) return Fail("cannot write " + archive); - std::error_code ec; - const u64 total_out = fs::file_size(archive, ec); - std::printf("%s: %zu entries, %llu -> %llu bytes (%.1f%%)\n", archive.c_str(), + const u64 total_out = fs::FileSize(archive).value_or(0); + ::printf("%s: %zu entries, %llu -> %llu bytes (%.1f%%)\n", archive.c_str(), writer.entry_count(), static_cast(total_in), static_cast(total_out), total_in ? 100.0 * static_cast(total_out) / static_cast(total_in) @@ -92,19 +92,19 @@ int Create(int argc, char** argv) { int List(int argc, char** argv) { if (argc != 3) return Usage(); base::UniquePointer pack = asset::PackFile::Open(argv[2]); - if (!pack) return Fail(std::string("cannot open ") + argv[2]); + if (!pack) return Fail(base::String("cannot open ") + argv[2]); u64 total_size = 0, total_stored = 0; - std::printf("%12s %12s %-7s %s\n", "size", "stored", "method", "path"); + ::printf("%12s %12s %-7s %s\n", "size", "stored", "method", "path"); for (size_t i = 0; i < pack->entry_count(); ++i) { const asset::PackEntryView entry = pack->entry(i); total_size += entry.size; total_stored += entry.stored_size; - std::printf("%12llu %12llu %-7s %.*s\n", static_cast(entry.size), + ::printf("%12llu %12llu %-7s %.*s\n", static_cast(entry.size), static_cast(entry.stored_size), entry.compression == asset::PackCompression::kDeflate ? "deflate" : "store", static_cast(entry.path.size()), entry.path.data()); } - std::printf("%12llu %12llu %zu entries\n", static_cast(total_size), + ::printf("%12llu %12llu %zu entries\n", static_cast(total_size), static_cast(total_stored), pack->entry_count()); return 0; } @@ -112,49 +112,46 @@ int List(int argc, char** argv) { int Extract(int argc, char** argv) { if (argc < 4) return Usage(); base::UniquePointer pack = asset::PackFile::Open(argv[2]); - if (!pack) return Fail(std::string("cannot open ") + argv[2]); - const fs::path out_dir = argv[3]; + if (!pack) return Fail(base::String("cannot open ") + argv[2]); + const base::String out_dir = argv[3]; base::Vector indices; if (argc == 4) { for (size_t i = 0; i < pack->entry_count(); ++i) indices.push_back(i); } else { for (int i = 4; i < argc; ++i) { - const std::optional index = pack->Find(asset::NormalizePath(argv[i])); - if (!index) return Fail(std::string("no such entry: ") + argv[i]); + const base::Optional index = pack->Find(asset::NormalizePath(argv[i])); + if (!index) return Fail(base::String("no such entry: ") + argv[i]); indices.push_back(*index); } } for (size_t index : indices) { const asset::PackEntryView entry = pack->entry(index); - std::optional> bytes = pack->ReadEntry(index); - if (!bytes) return Fail("corrupt entry: " + std::string(entry.path)); - const fs::path out_path = out_dir / fs::path(entry.path); - std::error_code ec; - fs::create_directories(out_path.parent_path(), ec); - std::ofstream out(out_path, std::ios::binary | std::ios::trunc); - out.write(reinterpret_cast(bytes->data()), - static_cast(bytes->size())); - if (!out) return Fail("cannot write " + out_path.string()); + base::Optional> bytes = pack->ReadEntry(index); + if (!bytes) return Fail("corrupt entry: " + base::String(entry.path)); + const base::String out_path = fs::Join(out_dir, entry.path); + fs::CreateDirectories(fs::ParentPath(out_path)); + if (!fs::WriteFile(out_path, base::Span(bytes->data(), bytes->size()))) + return Fail("cannot write " + out_path); } - std::printf("extracted %zu entries to %s\n", indices.size(), out_dir.string().c_str()); + ::printf("extracted %zu entries to %s\n", indices.size(), out_dir.c_str()); return 0; } int Verify(int argc, char** argv) { if (argc != 3) return Usage(); base::UniquePointer pack = asset::PackFile::Open(argv[2]); - if (!pack) return Fail(std::string("cannot open ") + argv[2]); + if (!pack) return Fail(base::String("cannot open ") + argv[2]); size_t bad = 0; for (size_t i = 0; i < pack->entry_count(); ++i) { if (!pack->ReadEntry(i)) { - std::fprintf(stderr, "rxpack: CORRUPT %s\n", std::string(pack->entry(i).path).c_str()); + ::fprintf(stderr, "rxpack: CORRUPT %s\n", base::String(pack->entry(i).path).c_str()); ++bad; } } - if (bad) return Fail(std::to_string(bad) + " corrupt entries"); - std::printf("%s: %zu entries OK\n", argv[2], pack->entry_count()); + if (bad) return Fail(rx::ToString(bad) + " corrupt entries"); + ::printf("%s: %zu entries OK\n", argv[2], pack->entry_count()); return 0; } @@ -162,9 +159,9 @@ int Verify(int argc, char** argv) { int main(int argc, char** argv) { if (argc < 2) return Usage(); - if (std::strcmp(argv[1], "create") == 0) return Create(argc, argv); - if (std::strcmp(argv[1], "list") == 0) return List(argc, argv); - if (std::strcmp(argv[1], "extract") == 0) return Extract(argc, argv); - if (std::strcmp(argv[1], "verify") == 0) return Verify(argc, argv); + if (::strcmp(argv[1], "create") == 0) return Create(argc, argv); + if (::strcmp(argv[1], "list") == 0) return List(argc, argv); + if (::strcmp(argv[1], "extract") == 0) return Extract(argc, argv); + if (::strcmp(argv[1], "verify") == 0) return Verify(argc, argv); return Usage(); } diff --git a/tools/rxworld.cc b/tools/rxworld.cc index d2755d58..4d212a35 100644 --- a/tools/rxworld.cc +++ b/tools/rxworld.cc @@ -17,15 +17,16 @@ // stable id. inspect prints the index: the cheapest view of what a streaming // decision works from. -#include -#include -#include -#include +#include +#include +#include #include #include "asset/pack.h" #include "asset/vfs.h" +#include "base/memory/move.h" +#include "base/strings/xstring.h" #include "world/world_bake.h" #include "world/world_format.h" #include "world/world_map.h" @@ -40,21 +41,21 @@ using rx::u32; using rx::u64; int Usage() { - std::fprintf(stderr, + ::fprintf(stderr, "usage: rxworld bake [--name city] [--cell-size 64]\n" " [--instance ] [--bake-id N] [--skip-unknown]\n" " rxworld inspect [--name city]\n"); return 2; } -int Fail(const std::string& message) { - std::fprintf(stderr, "rxworld: %s\n", message.c_str()); +int Fail(const base::String& message) { + ::fprintf(stderr, "rxworld: %s\n", message.c_str()); return 1; } bool ParseU64(const char* text, u64* out) { char* end = nullptr; - const unsigned long long value = std::strtoull(text, &end, 0); + const unsigned long long value = ::strtoull(text, &end, 0); if (end == text || *end != '\0') return false; *out = value; return true; @@ -62,21 +63,21 @@ bool ParseU64(const char* text, u64* out) { int Bake(int argc, char** argv) { if (argc < 4) return Usage(); - const std::string scene_path = argv[2]; - const std::string archive_path = argv[3]; + const base::String scene_path = argv[2]; + const base::String archive_path = argv[3]; world::WorldBakeOptions options; for (int i = 4; i < argc; ++i) { const bool has_value = i + 1 < argc; - if (std::strcmp(argv[i], "--name") == 0 && has_value) { + if (::strcmp(argv[i], "--name") == 0 && has_value) { options.name = argv[++i]; - } else if (std::strcmp(argv[i], "--cell-size") == 0 && has_value) { - options.cell_size = static_cast(std::atof(argv[++i])); - } else if (std::strcmp(argv[i], "--instance") == 0 && has_value) { + } else if (::strcmp(argv[i], "--cell-size") == 0 && has_value) { + options.cell_size = static_cast(::atof(argv[++i])); + } else if (::strcmp(argv[i], "--instance") == 0 && has_value) { options.instance_components.push_back(argv[++i]); - } else if (std::strcmp(argv[i], "--bake-id") == 0 && has_value) { + } else if (::strcmp(argv[i], "--bake-id") == 0 && has_value) { if (!ParseU64(argv[++i], &options.bake_id)) return Fail("--bake-id must be a number"); - } else if (std::strcmp(argv[i], "--skip-unknown") == 0) { + } else if (::strcmp(argv[i], "--skip-unknown") == 0) { options.skip_unknown = true; } else { return Usage(); @@ -84,31 +85,31 @@ int Bake(int argc, char** argv) { } world::WorldBakeResult result; - std::string error; + base::String error; if (!world::BakeWorld(scene_path, options, archive_path, &result, &error)) return Fail(error); - for (const std::string& name : result.dropped) { - std::fprintf(stderr, + for (const base::String& name : result.dropped) { + ::fprintf(stderr, "rxworld: warning: '%s' holds an indirection and cannot be baked; every entity " "carrying it loses it\n", name.c_str()); } - std::printf("rxworld: %s -> %s\n", scene_path.c_str(), archive_path.c_str()); - std::printf(" %u cells, %u entities, %u static instances, bake %llu\n", result.cells, + ::printf("rxworld: %s -> %s\n", scene_path.c_str(), archive_path.c_str()); + ::printf(" %u cells, %u entities, %u static instances, bake %llu\n", result.cells, result.entities, result.instances, static_cast(result.bake_id)); - std::printf(" index at %s/%s.rxworld\n", result.name.c_str(), result.name.c_str()); + ::printf(" index at %s/%s.rxworld\n", result.name.c_str(), result.name.c_str()); return 0; } int Inspect(int argc, char** argv) { if (argc < 3) return Usage(); - const std::string archive_path = argv[2]; + const base::String archive_path = argv[2]; // Same default the cook uses, so inspecting an archive baked with no --name // needs no --name either. - std::string name = world::WorldNameForArchive(archive_path); + base::String name = world::WorldNameForArchive(archive_path); for (int i = 3; i < argc; ++i) { - if (std::strcmp(argv[i], "--name") == 0 && i + 1 < argc) { + if (::strcmp(argv[i], "--name") == 0 && i + 1 < argc) { name = argv[++i]; } else { return Usage(); @@ -118,20 +119,20 @@ int Inspect(int argc, char** argv) { asset::Vfs vfs; auto provider = asset::MakePackFileProvider(archive_path); if (!provider) return Fail(archive_path + ": not a readable .rxp"); - vfs.Mount("world", std::move(provider)); + vfs.Mount("world", base::move(provider)); world::WorldMap map; - std::string error; + base::String error; if (!map.Load(vfs, "world://" + name + "/" + name + ".rxworld", &error)) return Fail(error); const world::WorldIndexData& index = map.index(); - std::printf("world %llu, bake %llu, %llu cells, cell size %.1f\n", + ::printf("world %llu, bake %llu, %llu cells, cell size %.1f\n", static_cast(index.world_id), static_cast(index.bake_id), static_cast(index.cells.size()), static_cast(index.cell_size)); for (const world::WorldCellRecord& cell : index.cells) { - std::printf(" cell %016llx [%.1f %.1f %.1f]..[%.1f %.1f %.1f] ids %llu+%u\n", + ::printf(" cell %016llx [%.1f %.1f %.1f]..[%.1f %.1f %.1f] ids %llu+%u\n", static_cast(cell.id), static_cast(cell.minimum.x), static_cast(cell.minimum.y), static_cast(cell.minimum.z), static_cast(cell.maximum.x), static_cast(cell.maximum.y), @@ -139,7 +140,7 @@ int Inspect(int argc, char** argv) { static_cast(cell.stable_id_first), cell.stable_id_count); for (u32 i = 0; i < cell.payload_count; ++i) { const world::WorldPayloadRecord& payload = index.payloads[cell.payload_first + i]; - std::printf(" %-15s %-9s %6u rows %8llu bytes resident\n", + ::printf(" %-15s %-9s %6u rows %8llu bytes resident\n", world::DomainName(payload.domain), world::TierName(payload.tier), payload.row_count, static_cast(payload.resident_bytes)); @@ -152,7 +153,7 @@ int Inspect(int argc, char** argv) { int main(int argc, char** argv) { if (argc < 2) return Usage(); - if (std::strcmp(argv[1], "bake") == 0) return Bake(argc, argv); - if (std::strcmp(argv[1], "inspect") == 0) return Inspect(argc, argv); + if (::strcmp(argv[1], "bake") == 0) return Bake(argc, argv); + if (::strcmp(argv[1], "inspect") == 0) return Inspect(argc, argv); return Usage(); }