v0.4.0: Direct3D 11/12 backends, in-game renderer selection, per-run logging
- ra3.dx: D3D11 and D3D12 presentation backends (runtime HLSL via d3dcompiler); 2D image blit and the GPU heightfield terrain raymarch, with the corner minimap/FPS overlays. Non-Windows builds link a null fallback. - ra3.display: preferred backend plus ordered fallback (Vulkan/D3D11/D3D12/SDL); menu gains a Renderer option and the CLI gains --dx11/--dx12/--sdl, which the render command now honours too. - vendor libenderlog (MIT): every run writes openra3.log next to the exe and archives the previous run's log; records at warn and above carry a call stack (native fallback, since libc++ has no <stacktrace>). - Windows crash reporter writes openra3_crash.log (faulting module + backtrace); D3D/DXGI diagnostics are routed through the logger.
This commit is contained in:
@@ -499,6 +499,15 @@ export namespace ra3::client {
|
||||
this->shutdown();
|
||||
return true;
|
||||
}
|
||||
|
||||
/** GPU terrain viewer with a corner minimap overlay. */
|
||||
[[nodiscard]] auto run_terrain(const display_options &options, const ra3::terrain::gpu_terrain &terrain, ra3::render::camera3d camera,
|
||||
const image &minimap) -> bool {
|
||||
if (!this->init(options)) return false;
|
||||
(void)this->terrain_loop(terrain, camera, minimap);
|
||||
this->shutdown();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+104
-45
@@ -8,14 +8,18 @@ import ra3.terrain;
|
||||
import ra3.client;
|
||||
import ra3.ui;
|
||||
import ra3.vulkan;
|
||||
import ra3.dx;
|
||||
|
||||
/**
|
||||
* Backend selection for the presentation layer.
|
||||
*
|
||||
* The app talks only to `ra3::client::display`; this module picks the concrete
|
||||
* backend (Vulkan when available, otherwise SDL) so no caller has to know which
|
||||
* one is in use. The GPU terrain path is Vulkan-only and reports failure so the
|
||||
* caller can fall back to the software renderer.
|
||||
* backend so no caller has to know which one is in use. The available backends
|
||||
* are Vulkan, Direct3D 11 and Direct3D 12 (both GPU terrain), and the SDL
|
||||
* software blit fallback. The caller may name a preferred backend (the in-game
|
||||
* menu exposes this); if it does not start, the others are tried in turn. The
|
||||
* GPU terrain path is Vulkan/D3D-only and reports failure so the caller can fall
|
||||
* back to the software renderer.
|
||||
*/
|
||||
export namespace ra3::display {
|
||||
using ra3::client::display_options;
|
||||
@@ -27,72 +31,127 @@ export namespace ra3::display {
|
||||
using menu_frame = std::function<std::optional<image>(const ui_event &, ra3::core::uint32, ra3::core::uint32, bool &)>;
|
||||
using camera_frame = std::function<image(const camera3d &, ra3::core::uint32, ra3::core::uint32)>;
|
||||
|
||||
/** The backend that actually initialized, or `none`. */
|
||||
enum class backend { none, vulkan, sdl };
|
||||
/** A concrete presentation backend, or `none`. */
|
||||
enum class backend { none, vulkan, d3d11, d3d12, sdl };
|
||||
|
||||
/**
|
||||
* Create and initialize the preferred display (Vulkan, then SDL), or return
|
||||
* null when neither starts. The caller owns the display and may reuse it for
|
||||
* a menu, a loading bar and a viewer in sequence.
|
||||
*/
|
||||
[[nodiscard]] inline auto create(const display_options &options, bool prefer_vulkan) -> std::unique_ptr<ra3::client::display> {
|
||||
const auto try_vulkan = [&]() -> std::unique_ptr<ra3::client::display> {
|
||||
auto d = std::make_unique<ra3::vulkan::vulkan_display>();
|
||||
if (d->init(options)) return d;
|
||||
return nullptr;
|
||||
};
|
||||
const auto try_sdl = [&]() -> std::unique_ptr<ra3::client::display> {
|
||||
auto d = std::make_unique<ra3::ui::sdl_display>();
|
||||
if (d->init(options)) return d;
|
||||
return nullptr;
|
||||
};
|
||||
if (prefer_vulkan) {
|
||||
if (auto d = try_vulkan()) return d;
|
||||
return try_sdl();
|
||||
[[nodiscard]] inline auto backend_name(backend which) -> std::string_view {
|
||||
switch (which) {
|
||||
case backend::vulkan: return "vulkan";
|
||||
case backend::d3d11: return "d3d11";
|
||||
case backend::d3d12: return "d3d12";
|
||||
case backend::sdl: return "sdl";
|
||||
default: return "none";
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto make_backend(backend which) -> std::unique_ptr<ra3::client::display> {
|
||||
switch (which) {
|
||||
case backend::vulkan: return std::make_unique<ra3::vulkan::vulkan_display>();
|
||||
case backend::d3d11: return std::make_unique<ra3::dx::d3d11_display>();
|
||||
case backend::d3d12: return std::make_unique<ra3::dx::d3d12_display>();
|
||||
case backend::sdl: return std::make_unique<ra3::ui::sdl_display>();
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/** The order in which backends are attempted for a preferred one. */
|
||||
[[nodiscard]] inline auto backend_order(backend preferred) -> std::array<backend, 4> {
|
||||
switch (preferred) {
|
||||
case backend::d3d11: return {backend::d3d11, backend::d3d12, backend::vulkan, backend::sdl};
|
||||
case backend::d3d12: return {backend::d3d12, backend::d3d11, backend::vulkan, backend::sdl};
|
||||
case backend::sdl: return {backend::sdl, backend::d3d11, backend::d3d12, backend::vulkan};
|
||||
case backend::vulkan:
|
||||
default: return {backend::vulkan, backend::d3d11, backend::d3d12, backend::sdl};
|
||||
}
|
||||
if (auto d = try_sdl()) return d;
|
||||
return try_vulkan();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run `action` on the first display that initializes: Vulkan, then SDL.
|
||||
* Create and initialize the preferred backend, falling back to the others,
|
||||
* or return null when none starts. The caller owns the display and may reuse
|
||||
* it for a menu, a loading bar and a viewer in sequence.
|
||||
*/
|
||||
[[nodiscard]] inline auto create(const display_options &options, backend preferred) -> std::unique_ptr<ra3::client::display> {
|
||||
for (const auto which: backend_order(preferred)) {
|
||||
auto candidate = make_backend(which);
|
||||
if (candidate && candidate->init(options)) return candidate;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/** Create and initialize the preferred display (Vulkan, then SDL). */
|
||||
[[nodiscard]] inline auto create(const display_options &options, bool prefer_vulkan) -> std::unique_ptr<ra3::client::display> {
|
||||
return create(options, prefer_vulkan ? backend::vulkan : backend::sdl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run `action` on the first backend that initializes.
|
||||
*
|
||||
* `action` must call one of the shared `display` loops; it returns false to
|
||||
* mean "this backend did not start", so the next one is tried.
|
||||
*/
|
||||
template<typename Fn>
|
||||
[[nodiscard]] auto with_display(bool prefer_vulkan, Fn &&action) -> bool {
|
||||
if (prefer_vulkan) {
|
||||
if (auto vk = std::make_unique<ra3::vulkan::vulkan_display>(); action(*vk)) return true;
|
||||
}
|
||||
if (auto sdl = std::make_unique<ra3::ui::sdl_display>(); action(*sdl)) return true;
|
||||
if (!prefer_vulkan) {
|
||||
if (auto vk = std::make_unique<ra3::vulkan::vulkan_display>(); action(*vk)) return true;
|
||||
[[nodiscard]] auto with_display(backend preferred, Fn &&action) -> bool {
|
||||
for (const auto which: backend_order(preferred)) {
|
||||
auto candidate = make_backend(which);
|
||||
if (candidate && action(*candidate)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Interactive menu on the first available backend. */
|
||||
[[nodiscard]] inline auto run_menu(const display_options &options, const menu_frame &frame) -> bool {
|
||||
return with_display(true, [&](ra3::client::display &d) { return d.run_menu(options, frame); });
|
||||
/** Run `action` on the first display that initializes: Vulkan, then SDL. */
|
||||
template<typename Fn>
|
||||
[[nodiscard]] auto with_display(bool prefer_vulkan, Fn &&action) -> bool {
|
||||
return with_display(prefer_vulkan ? backend::vulkan : backend::sdl, std::forward<Fn>(action));
|
||||
}
|
||||
|
||||
/** Pan/zoom image viewer on the first available backend. */
|
||||
/** Interactive menu on the preferred (or first available) backend. */
|
||||
[[nodiscard]] inline auto run_menu(const display_options &options, const menu_frame &frame, backend preferred = backend::vulkan) -> bool {
|
||||
return with_display(preferred, [&](ra3::client::display &d) { return d.run_menu(options, frame); });
|
||||
}
|
||||
|
||||
/** Pan/zoom image viewer on the preferred (or first available) backend. */
|
||||
[[nodiscard]] inline auto run_image(const display_options &options, const image &scene, view_camera camera, backend preferred) -> bool {
|
||||
return with_display(preferred, [&](ra3::client::display &d) { return d.run_image(options, scene, camera); });
|
||||
}
|
||||
|
||||
/** Pan/zoom image viewer on the preferred (or first available) backend. */
|
||||
[[nodiscard]] inline auto run_image(const display_options &options, const image &scene, view_camera camera, bool prefer_vulkan) -> bool {
|
||||
return with_display(prefer_vulkan, [&](ra3::client::display &d) { return d.run_image(options, scene, camera); });
|
||||
return run_image(options, scene, camera, prefer_vulkan ? backend::vulkan : backend::sdl);
|
||||
}
|
||||
|
||||
/** Software 3D camera viewer on the first available backend. */
|
||||
/** Software 3D camera viewer on the preferred (or first available) backend. */
|
||||
[[nodiscard]] inline auto run_camera(const display_options &options, const camera_frame &provider, camera3d camera, bool prefer_vulkan) -> bool {
|
||||
return with_display(prefer_vulkan, [&](ra3::client::display &d) { return d.run_camera(options, provider, camera); });
|
||||
return with_display(prefer_vulkan ? backend::vulkan : backend::sdl,
|
||||
[&](ra3::client::display &d) { return d.run_camera(options, provider, camera); });
|
||||
}
|
||||
|
||||
/**
|
||||
* GPU terrain viewer. Vulkan only: returns false when Vulkan is unavailable
|
||||
* so the caller can render offscreen instead.
|
||||
* GPU terrain viewer on the preferred (or first available) backend. Returns
|
||||
* false when no GPU backend that offers terrain starts, so the caller can
|
||||
* render offscreen instead.
|
||||
*/
|
||||
[[nodiscard]] inline auto run_terrain(const display_options &options, const ra3::terrain::gpu_terrain &terrain, camera3d camera,
|
||||
backend preferred) -> bool {
|
||||
return with_display(preferred, [&](ra3::client::display &d) {
|
||||
if (!d.supports_terrain()) return false;
|
||||
return d.run_terrain(options, terrain, camera);
|
||||
});
|
||||
}
|
||||
|
||||
/** GPU terrain viewer with a corner minimap overlay. */
|
||||
[[nodiscard]] inline auto run_terrain(const display_options &options, const ra3::terrain::gpu_terrain &terrain, camera3d camera,
|
||||
const image &minimap, backend preferred) -> bool {
|
||||
return with_display(preferred, [&](ra3::client::display &d) {
|
||||
if (!d.supports_terrain()) return false;
|
||||
return d.run_terrain(options, terrain, camera, minimap);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* GPU terrain viewer. Vulkan/D3D only: returns false when the preferred GPU
|
||||
* backend is unavailable so the caller can render offscreen instead.
|
||||
*/
|
||||
[[nodiscard]] inline auto run_terrain(const display_options &options, const ra3::terrain::gpu_terrain &terrain, camera3d camera) -> bool {
|
||||
auto vk = std::make_unique<ra3::vulkan::vulkan_display>();
|
||||
return vk->run_terrain(options, terrain, camera);
|
||||
return run_terrain(options, terrain, camera, backend::vulkan);
|
||||
}
|
||||
}
|
||||
|
||||
+1392
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
export module ra3.dx;
|
||||
|
||||
import std;
|
||||
|
||||
export import ra3.core;
|
||||
import ra3.render;
|
||||
import ra3.client;
|
||||
|
||||
/**
|
||||
* Fallback Direct3D backends used when the build has no Windows/D3D (for
|
||||
* example the Linux development build). `init` fails so the caller can select
|
||||
* another display; the class names match the real `ra3.dx` module so the
|
||||
* backend factory in `ra3.display` compiles unchanged.
|
||||
*/
|
||||
export namespace ra3::dx {
|
||||
class d3d11_display final : public ra3::client::display {
|
||||
public:
|
||||
[[nodiscard]] auto init(const ra3::client::display_options &) -> bool override { return false; }
|
||||
[[nodiscard]] auto present(const ra3::render::image &, const ra3::render::view_rect &, bool) -> bool override { return false; }
|
||||
[[nodiscard]] auto poll_event(ra3::render::ui_event &) -> bool override { return false; }
|
||||
[[nodiscard]] auto window_size() const -> std::pair<int, int> override { return {0, 0}; }
|
||||
[[nodiscard]] auto key_down(ra3::render::ui_key) const -> bool override { return false; }
|
||||
auto shutdown() -> void override {}
|
||||
[[nodiscard]] auto name() const -> std::string_view override { return "d3d11(null)"; }
|
||||
};
|
||||
|
||||
class d3d12_display final : public ra3::client::display {
|
||||
public:
|
||||
[[nodiscard]] auto init(const ra3::client::display_options &) -> bool override { return false; }
|
||||
[[nodiscard]] auto present(const ra3::render::image &, const ra3::render::view_rect &, bool) -> bool override { return false; }
|
||||
[[nodiscard]] auto poll_event(ra3::render::ui_event &) -> bool override { return false; }
|
||||
[[nodiscard]] auto window_size() const -> std::pair<int, int> override { return {0, 0}; }
|
||||
[[nodiscard]] auto key_down(ra3::render::ui_key) const -> bool override { return false; }
|
||||
auto shutdown() -> void override {}
|
||||
[[nodiscard]] auto name() const -> std::string_view override { return "d3d12(null)"; }
|
||||
};
|
||||
}
|
||||
@@ -18,4 +18,5 @@ export import ra3.render;
|
||||
export import ra3.terrain;
|
||||
export import ra3.ui;
|
||||
export import ra3.vulkan;
|
||||
export import ra3.dx;
|
||||
export import ra3.display;
|
||||
|
||||
Reference in New Issue
Block a user