mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 20:57:08 +02:00
Draw the launcher on the best GPU; hide the CoreGameAssets layer
A blank launcher window (Launcher/gui_renderer.cpp, Launcher/gui.cpp) Reported as a launcher that showed nothing, from someone with a GT 1030. The log named the card it had used: Intel HD Graphics 4600, the onboard chip beside it. - D3D12CreateDevice(nullptr, ...) takes whichever adapter DXGI lists first, which on a PC with onboard graphics beside a real card is the onboard one. It now walks EnumAdapterByGpuPreference with HIGH_PERFORMANCE, skips software adapters and takes the first that gives a device, as a game would. The old default adapter and WARP remain behind it, so no working setup loses its device. - That chip's Direct3D 12 driver cannot build a pipeline, and the ImGui backend creates one on the first frame while ignoring whether it worked (imgui_impl_dx12.cpp:792). Every draw then does nothing and the window shows only ClearRenderTargetView's colour, which is near black. Untextured shapes sample the font atlas's white pixel, so a failed atlas looks identical -- the drawn background disappears with the text. The pipeline and font texture are built in init now and checked, and a failure says to update the driver and that the card may be below what ReSkate and skate. need. - None of this was diagnosable, because the renderer logged nothing at all. It now records the adapter, its memory and whether it is the software fallback; the window size, scale, system DPI and work area; the font atlas size and whether it built; and the first frame drawn. Those four lines are what turned "blank window, healthy log" into the two bugs above, and they separate a missing device from a missing pipeline from a window that draws and is still black. World layers (Engine/Vfs/world_layer_scan.cpp) - CoreGameAssets had a row like any other, and switching it off took the world away with no clue which row had done it. essential_layer keeps its node, so its children are still found and still switchable, and drops its row -- which removes it from the menu, from "world <key> off" and from layer sync at once, rather than hiding it in one place and leaving it reachable from another. - Choices are stored by row key, not by index, so no saved setting shifts, and an "off" already stored for it has no row to apply to: a profile that turned it off fixes itself on the next launch. - The cache stamp goes to 2, so a cache written before this is rebuilt instead of keeping the row alive on existing installs. - The scan test asserted one row per node, which this breaks on purpose. It now checks the counts differ, that no row's key holds "coregameassets", and that the node is still there. Against a real install: 262 rows to 261, six maps, passing.
This commit is contained in:
1 parent
abdda77946
commit
f815658bf1
4 files changed
+106
-7
No files matched your search
@@ -21,7 +21,8 @@ int main(int argc, char** argv) {
|
||||
const std::filesystem::path root = argv[1];
|
||||
const auto catalog = world_layer_scan::scan(root);
|
||||
check(catalog.anchors.size() == 6, "one anchor per map");
|
||||
check(catalog.nodes.size() >= 200 && catalog.layers.size() == catalog.nodes.size(), "one row per layer");
|
||||
check(catalog.nodes.size() >= 200 && catalog.layers.size() < catalog.nodes.size() &&
|
||||
catalog.layers.size() + 8 > catalog.nodes.size(), "a row per layer but the essential ones");
|
||||
for (std::size_t i = 0; i < catalog.nodes.size(); ++i) {
|
||||
const auto parent = catalog.nodes[i].parent;
|
||||
check(parent >= -1 && parent < static_cast<int>(i), "parents come first");
|
||||
@@ -31,6 +32,16 @@ int main(int argc, char** argv) {
|
||||
};
|
||||
for (const auto* key : {"bam_tod_1_morning", "bam_tod_7_weathernight", "grom_tod_5_night", "bam_debug_tests"})
|
||||
check(has(key), key);
|
||||
// No row for what every map is built out of: a player cannot switch off
|
||||
// the world and then wonder which row did it.
|
||||
check(std::none_of(catalog.layers.begin(), catalog.layers.end(), [](const WorldLayer& layer) {
|
||||
return layer.key.find("coregameassets") != std::string::npos;
|
||||
}), "CoreGameAssets has no row to switch");
|
||||
check(std::any_of(catalog.nodes.begin(), catalog.nodes.end(), [](const WorldLayerNode& node) {
|
||||
std::string bundle = node.bundle;
|
||||
std::transform(bundle.begin(), bundle.end(), bundle.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return bundle.find("coregameassets") != std::string::npos;
|
||||
}), "but it keeps its node, so its children are still reachable");
|
||||
check(std::none_of(catalog.nodes.begin(), catalog.nodes.end(), [](const WorldLayerNode& node) {
|
||||
std::string bundle = node.bundle;
|
||||
std::transform(bundle.begin(), bundle.end(), bundle.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
|
||||
@@ -78,6 +78,15 @@ std::vector<Reference> read_references(const fs::path& gameRoot, const char* toc
|
||||
return result;
|
||||
}
|
||||
|
||||
// Layers there is no sense in switching off. They stay in the node tree, so
|
||||
// their children are still found and still switchable, but they get no row of
|
||||
// their own: CoreGameAssets holds what every map is built out of, and a player
|
||||
// who turned it off lost the world with no clue which row had done it. A
|
||||
// choice already stored for one is keyed by row, so it simply stops applying.
|
||||
bool essential_layer(std::string_view bundle) {
|
||||
return contains(leaf_of(lower(bundle)), "coregameassets");
|
||||
}
|
||||
|
||||
std::string category(std::string_view bundle) {
|
||||
const auto path = lower(bundle);
|
||||
const auto leaf = leaf_of(path);
|
||||
@@ -143,6 +152,7 @@ void add_map(WorldLayerCatalog& catalog, WorldMap map, const std::vector<Referen
|
||||
const std::string prefix(world_map_key(map));
|
||||
for (auto slot = first; slot < catalog.nodes.size(); ++slot) {
|
||||
const auto& node = catalog.nodes[slot];
|
||||
if (essential_layer(node.bundle)) continue;
|
||||
const auto leaf = leaf_of(node.bundle);
|
||||
const auto key = prefix + "_" + (leaves[slug(leaf)] > 1 ? slug(node.bundle) : slug(leaf));
|
||||
auto label = leaf;
|
||||
@@ -154,7 +164,7 @@ void add_map(WorldLayerCatalog& catalog, WorldMap map, const std::vector<Referen
|
||||
|
||||
std::string stamp(const fs::path& gameRoot) {
|
||||
std::ostringstream out;
|
||||
out << "1";
|
||||
out << "2"; // 2: no row for the layers essential_layer keeps out
|
||||
for (const auto& source : maps) {
|
||||
const auto path = gameRoot / L"Data" / fs::path(source.toc);
|
||||
std::error_code error;
|
||||
|
||||
+19
-1
@@ -3,6 +3,8 @@
|
||||
#include "gui_internal.h"
|
||||
#include "gui_renderer.h"
|
||||
|
||||
#include "Engine/Core/Log/logging.h"
|
||||
|
||||
#include <dwmapi.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
@@ -221,6 +223,12 @@ Fonts load_fonts() {
|
||||
fonts.action = font(L"FONT_HEADING", L"seguibl.ttf", 50);
|
||||
fonts.title = font(L"FONT_BRUSH", L"seguibl.ttf", 86);
|
||||
io.FontDefault = fonts.body;
|
||||
// Built here rather than on the first frame, so the log says whether it
|
||||
// worked. A window with no atlas draws nothing but its clear colour.
|
||||
const bool built = io.Fonts->Build();
|
||||
logging::log(built ? logging::Level::info : logging::Level::error, logging::Channel::launcher,
|
||||
"Launcher fonts: atlas {}x{} {}.", io.Fonts->TexWidth, io.Fonts->TexHeight,
|
||||
built ? "built" : "COULD NOT BE BUILT, so the window will be empty");
|
||||
return fonts;
|
||||
}
|
||||
|
||||
@@ -313,6 +321,9 @@ int run(const launcher_app::Session& session, const std::vector<std::wstring>& a
|
||||
|
||||
const int width = static_cast<int>(S(design_width));
|
||||
const int height = static_cast<int>(S(design_height));
|
||||
logging::log(logging::Level::info, logging::Channel::launcher,
|
||||
"Launcher window: {}x{} at scale {:.2f} (system DPI {}), work area {}x{}.", width, height, g_scale,
|
||||
GetDpiForSystem(), work.right - work.left, work.bottom - work.top);
|
||||
const HWND window = CreateWindowExW(WS_EX_APPWINDOW, window_class.lpszClassName, L"ReSkate",
|
||||
WS_POPUP | WS_MINIMIZEBOX | WS_SYSMENU, work.left + (work.right - work.left - width) / 2,
|
||||
work.top + (work.bottom - work.top - height) / 2, width, height, nullptr, nullptr, instance, nullptr);
|
||||
@@ -334,7 +345,9 @@ int run(const launcher_app::Session& session, const std::vector<std::wstring>& a
|
||||
ImGui_ImplWin32_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
DestroyWindow(window);
|
||||
throw std::runtime_error("Cannot start Direct3D 12 for the launcher window");
|
||||
throw std::runtime_error("This PC's graphics driver cannot draw the launcher's window. Update your "
|
||||
"graphics driver and try again. If that does not help, the graphics card is below what ReSkate and "
|
||||
"skate. need. The log names the card it tried to use.");
|
||||
}
|
||||
g_renderer = &renderer;
|
||||
{
|
||||
@@ -376,6 +389,7 @@ int run(const launcher_app::Session& session, const std::vector<std::wstring>& a
|
||||
HANDLE game{};
|
||||
DWORD game_id{};
|
||||
bool hidden{};
|
||||
bool drew{};
|
||||
while (running) {
|
||||
MSG message;
|
||||
while (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE)) {
|
||||
@@ -423,6 +437,10 @@ int run(const launcher_app::Session& session, const std::vector<std::wstring>& a
|
||||
frame(launcher, fonts, window, ui, mods_panel);
|
||||
ImGui::Render();
|
||||
renderer.render();
|
||||
if (!drew) {
|
||||
drew = true;
|
||||
logging::write(logging::Level::info, logging::Channel::launcher, "Launcher drew its first frame.");
|
||||
}
|
||||
}
|
||||
ShowWindow(window, SW_HIDE);
|
||||
if (game) CloseHandle(game);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#include "gui_renderer.h"
|
||||
|
||||
#include "Engine/Core/Log/logging.h"
|
||||
|
||||
#include <dxgi1_6.h>
|
||||
|
||||
#include <backends/imgui_impl_dx12.h>
|
||||
#include <wincodec.h>
|
||||
|
||||
@@ -13,11 +17,52 @@ namespace dingosdk::launcher_gui::detail {
|
||||
bool Renderer::init(HWND window) {
|
||||
UINT factory_flags = 0;
|
||||
ComPtr<IDXGIFactory4> factory;
|
||||
if (FAILED(CreateDXGIFactory2(factory_flags, IID_PPV_ARGS(&factory)))) return false;
|
||||
if (FAILED(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device_)))) {
|
||||
if (FAILED(CreateDXGIFactory2(factory_flags, IID_PPV_ARGS(&factory)))) {
|
||||
logging::write(logging::Level::error, logging::Channel::launcher, "Launcher renderer: no DXGI factory.");
|
||||
return false;
|
||||
}
|
||||
bool software = false;
|
||||
// Ask for the best card rather than whichever DXGI lists first. Where
|
||||
// onboard graphics sit beside a real card, the default adapter is usually
|
||||
// the onboard one, and its Direct3D 12 driver can be too old to draw the
|
||||
// window at all while the card that would have worked goes unused.
|
||||
if (ComPtr<IDXGIFactory6> ranked; SUCCEEDED(factory.As(&ranked))) {
|
||||
for (UINT index = 0; !device_; ++index) {
|
||||
ComPtr<IDXGIAdapter1> candidate;
|
||||
if (FAILED(ranked->EnumAdapterByGpuPreference(index, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE,
|
||||
IID_PPV_ARGS(&candidate)))) break;
|
||||
DXGI_ADAPTER_DESC1 description{};
|
||||
// WARP is the last resort below, not a candidate here.
|
||||
if (FAILED(candidate->GetDesc1(&description)) || (description.Flags & DXGI_ADAPTER_FLAG_SOFTWARE))
|
||||
continue;
|
||||
D3D12CreateDevice(candidate.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device_));
|
||||
}
|
||||
}
|
||||
if (!device_ && FAILED(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device_)))) {
|
||||
ComPtr<IDXGIAdapter> warp;
|
||||
if (FAILED(factory->EnumWarpAdapter(IID_PPV_ARGS(&warp))) ||
|
||||
FAILED(D3D12CreateDevice(warp.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device_)))) return false;
|
||||
FAILED(D3D12CreateDevice(warp.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device_)))) {
|
||||
logging::write(logging::Level::error, logging::Channel::launcher,
|
||||
"Launcher renderer: no Direct3D 12 device, not even the software one.");
|
||||
return false;
|
||||
}
|
||||
software = true;
|
||||
}
|
||||
{
|
||||
// Which card drew the window, so a blank one can be told apart from a
|
||||
// driver problem, a software fallback or a remote session.
|
||||
DXGI_ADAPTER_DESC1 description{};
|
||||
ComPtr<IDXGIAdapter1> adapter;
|
||||
if (SUCCEEDED(factory->EnumAdapterByLuid(device_->GetAdapterLuid(), IID_PPV_ARGS(&adapter))) &&
|
||||
SUCCEEDED(adapter->GetDesc1(&description)))
|
||||
logging::write(logging::Level::info, logging::Channel::launcher,
|
||||
std::wstring(L"Launcher renderer: ") + description.Description +
|
||||
(software ? L" (software fallback)" : L"") + L", " +
|
||||
std::to_wstring(description.DedicatedVideoMemory / (1024 * 1024)) + L" MB dedicated");
|
||||
else
|
||||
logging::write(logging::Level::warning, logging::Channel::launcher,
|
||||
software ? "Launcher renderer: software fallback, adapter unidentified."
|
||||
: "Launcher renderer: adapter unidentified.");
|
||||
}
|
||||
D3D12_DESCRIPTOR_HEAP_DESC rtv_desc{D3D12_DESCRIPTOR_HEAP_TYPE_RTV, frame_count, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1};
|
||||
D3D12_DESCRIPTOR_HEAP_DESC srv_desc{D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, 1 + max_textures,
|
||||
@@ -67,7 +112,22 @@ bool Renderer::init(HWND window) {
|
||||
info.SrvDescriptorHeap = srv_heap_.Get();
|
||||
info.LegacySingleSrvCpuDescriptor = srv_heap_->GetCPUDescriptorHandleForHeapStart();
|
||||
info.LegacySingleSrvGpuDescriptor = srv_heap_->GetGPUDescriptorHandleForHeapStart();
|
||||
return ImGui_ImplDX12_Init(&info);
|
||||
if (!ImGui_ImplDX12_Init(&info)) {
|
||||
logging::write(logging::Level::error, logging::Channel::launcher,
|
||||
"Launcher renderer: the Direct3D 12 backend did not start.");
|
||||
return false;
|
||||
}
|
||||
// The backend builds its pipeline and font texture on the first frame and
|
||||
// ignores whether that worked, so a driver that cannot make one draws
|
||||
// nothing at all and the window shows only its clear colour -- a black
|
||||
// window, with a log that says everything succeeded. Build it here, where
|
||||
// the answer can be acted on.
|
||||
if (!ImGui_ImplDX12_CreateDeviceObjects()) {
|
||||
logging::write(logging::Level::error, logging::Channel::launcher,
|
||||
"Launcher renderer: this driver cannot create the Direct3D 12 pipeline the window draws with.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Renderer::render() {
|
||||
|
||||
Reference in new issue
Block a user