From 8b4a885ee8daaf792674e217000a4020164e6f88 Mon Sep 17 00:00:00 2001 From: Curtis Date: Tue, 25 Aug 2026 00:56:38 +1200 Subject: [PATCH 01/10] TEMP H1 add neutral backend provider contract --- render/RenderRhi/RenderRhiBackend.h | 41 +++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 render/RenderRhi/RenderRhiBackend.h diff --git a/render/RenderRhi/RenderRhiBackend.h b/render/RenderRhi/RenderRhiBackend.h new file mode 100644 index 0000000..f3be895 --- /dev/null +++ b/render/RenderRhi/RenderRhiBackend.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +namespace Upp { + +// Internal provider seam between application presentation and a compiled GPU backend. +// It contains no backend-native types and deliberately stops at the neutral GpuDevice. +class GpuPresentationBackendContext { +public: + virtual ~GpuPresentationBackendContext() {} +}; + +class GpuPresentationBackendSession { +public: + virtual ~GpuPresentationBackendSession() {} + + virtual bool Open(bool request_validation, const GpuNativeWindowDesc& native_window, + String& error) = 0; + virtual void Close() = 0; + virtual bool IsReady() const = 0; + virtual String GetError() const = 0; + virtual GpuDevice *GetDevice() = 0; +}; + +class GpuPresentationBackend { +public: + virtual ~GpuPresentationBackend() {} + + virtual One CreateContext(String& error) = 0; + virtual One CreateSession(GpuPresentationBackendContext& context, + String& error) = 0; +}; + +// Compiled backend packages register during process start. One provider owns each +// backend kind; duplicate registration and Unknown are rejected. +bool RegisterGpuPresentationBackend(GpuBackendKind kind, GpuPresentationBackend& backend); +GpuPresentationBackend *FindGpuPresentationBackend(GpuBackendKind kind); +bool IsGpuPresentationBackendRegistered(GpuBackendKind kind); + +} From 80d61121ff242fda43507c2b160e19898c3343da Mon Sep 17 00:00:00 2001 From: Curtis Date: Tue, 25 Aug 2026 00:56:51 +1200 Subject: [PATCH 02/10] TEMP H1 add neutral backend registry --- render/RenderRhi/RenderRhiBackend.cpp | 50 +++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 render/RenderRhi/RenderRhiBackend.cpp diff --git a/render/RenderRhi/RenderRhiBackend.cpp b/render/RenderRhi/RenderRhiBackend.cpp new file mode 100644 index 0000000..8dcceae --- /dev/null +++ b/render/RenderRhi/RenderRhiBackend.cpp @@ -0,0 +1,50 @@ +#include "RenderRhiBackend.h" + +namespace Upp { + +namespace { + +struct BackendRegistration : Moveable { + GpuBackendKind kind = GpuBackendKind::Unknown; + GpuPresentationBackend *backend = nullptr; +}; + +static Vector& BackendRegistry() +{ + static Vector registry; + return registry; +} + +static int FindBackendIndex(GpuBackendKind kind) +{ + const Vector& registry = BackendRegistry(); + for(int i = 0; i < registry.GetCount(); ++i) + if(registry[i].kind == kind) + return i; + return -1; +} + +} // namespace + +bool RegisterGpuPresentationBackend(GpuBackendKind kind, GpuPresentationBackend& backend) +{ + if(kind == GpuBackendKind::Unknown || FindBackendIndex(kind) >= 0) + return false; + BackendRegistration& registration = BackendRegistry().Add(); + registration.kind = kind; + registration.backend = &backend; + return true; +} + +GpuPresentationBackend *FindGpuPresentationBackend(GpuBackendKind kind) +{ + int index = FindBackendIndex(kind); + return index >= 0 ? BackendRegistry()[index].backend : nullptr; +} + +bool IsGpuPresentationBackendRegistered(GpuBackendKind kind) +{ + return FindGpuPresentationBackend(kind) != nullptr; +} + +} From 1ddea34ed79c52e23ef769fa5c121066a06d4dad Mon Sep 17 00:00:00 2001 From: Curtis Date: Tue, 25 Aug 2026 00:57:00 +1200 Subject: [PATCH 03/10] TEMP H1 package neutral backend registry --- render/RenderRhi/RenderRhi.upp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/render/RenderRhi/RenderRhi.upp b/render/RenderRhi/RenderRhi.upp index 129e72a..0d80806 100644 --- a/render/RenderRhi/RenderRhi.upp +++ b/render/RenderRhi/RenderRhi.upp @@ -3,4 +3,6 @@ uses file RenderRhi.h, - RenderRhi.cpp; + RenderRhiBackend.h, + RenderRhi.cpp, + RenderRhiBackend.cpp; From 8d54a85131e6625ac1f102165d3e7b4e1aac3932 Mon Sep 17 00:00:00 2001 From: Curtis Date: Tue, 25 Aug 2026 00:57:21 +1200 Subject: [PATCH 04/10] TEMP H1 move Vulkan provider into backend package --- .../RenderVulkan/RenderVulkanPresentation.cpp | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 render/RenderVulkan/RenderVulkanPresentation.cpp diff --git a/render/RenderVulkan/RenderVulkanPresentation.cpp b/render/RenderVulkan/RenderVulkanPresentation.cpp new file mode 100644 index 0000000..8884609 --- /dev/null +++ b/render/RenderVulkan/RenderVulkanPresentation.cpp @@ -0,0 +1,106 @@ +#include "RenderVulkanRhi.h" + +#include +#include + +namespace Upp { + +namespace { + +class VulkanPresentationContext : public GpuPresentationBackendContext { +public: + VulkanSurfaceSessionGroup group; +}; + +class VulkanPresentationSession : public GpuPresentationBackendSession { +public: + explicit VulkanPresentationSession(VulkanPresentationContext& context) + : session(context.group) + { + } + + ~VulkanPresentationSession() override + { + Close(); + } + + bool Open(bool request_validation, const GpuNativeWindowDesc& native_window, + String& out_error) override + { + Close(); + error.Clear(); + out_error.Clear(); + if(!session.Open(request_validation, native_window)) { + error = session.GetError(); + out_error = error; + return false; + } + + device.reset(new VulkanGpuDevice(session)); + if(!device->IsReady()) { + String failure = device->GetError(); + device.reset(); + session.Close(); + error = failure.IsEmpty() ? String("VulkanGpuDevice initialization failed") : failure; + out_error = error; + return false; + } + + return true; + } + + void Close() override + { + device.reset(); + session.Close(); + error.Clear(); + } + + bool IsReady() const override + { + return session.IsReady() && device && device->IsReady(); + } + + String GetError() const override + { + if(!error.IsEmpty()) + return error; + if(device && !device->GetError().IsEmpty()) + return device->GetError(); + return session.GetError(); + } + + GpuDevice *GetDevice() override + { + return IsReady() ? device.get() : nullptr; + } + +private: + VulkanSurfaceSession session; + std::unique_ptr device; + String error; +}; + +class VulkanPresentationBackend : public GpuPresentationBackend { +public: + One CreateContext(String& error) override + { + error.Clear(); + return new VulkanPresentationContext; + } + + One CreateSession(GpuPresentationBackendContext& context, + String& error) override + { + error.Clear(); + return new VulkanPresentationSession(static_cast(context)); + } +}; + +static VulkanPresentationBackend s_vulkan_backend; +static bool s_vulkan_backend_registered = + RegisterGpuPresentationBackend(GpuBackendKind::Vulkan, s_vulkan_backend); + +} // namespace + +} From ddb73169f66010ca950f28a551ac8d25f3d986a9 Mon Sep 17 00:00:00 2001 From: Curtis Date: Tue, 25 Aug 2026 00:57:30 +1200 Subject: [PATCH 05/10] TEMP H1 package Vulkan provider with backend --- render/RenderVulkan/RenderVulkan.upp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/render/RenderVulkan/RenderVulkan.upp b/render/RenderVulkan/RenderVulkan.upp index 6b96963..df0d33e 100644 --- a/render/RenderVulkan/RenderVulkan.upp +++ b/render/RenderVulkan/RenderVulkan.upp @@ -11,4 +11,5 @@ file RenderVulkanFrame.cpp, RenderVulkanClearFrame.cpp, RenderVulkanRhiBase.inc, - RenderVulkanRhi.cpp; + RenderVulkanRhi.cpp, + RenderVulkanPresentation.cpp; From aafacb6fdb5b21ef9981582c1ae1be5916fee2f4 Mon Sep 17 00:00:00 2001 From: Curtis Date: Tue, 25 Aug 2026 00:58:19 +1200 Subject: [PATCH 06/10] TEMP H1 make presentation orchestration backend neutral --- render/GpuRender/RenderPresentation.cpp | 222 +++++++++++++++++++----- 1 file changed, 177 insertions(+), 45 deletions(-) diff --git a/render/GpuRender/RenderPresentation.cpp b/render/GpuRender/RenderPresentation.cpp index 800ffcc..35cd417 100644 --- a/render/GpuRender/RenderPresentation.cpp +++ b/render/GpuRender/RenderPresentation.cpp @@ -1,53 +1,26 @@ #include "RenderPresentation.h" -#include "RenderPresentationBackend.h" + +#include +#include +#include namespace Upp { namespace { -struct BackendRegistration : Moveable { - GpuBackendKind kind = GpuBackendKind::Unknown; - GpuPresentationBackend *backend = nullptr; -}; - -static Vector& BackendRegistry() +static GpuClearColor ToClearColor(Rgba8 color) { - static Vector registry; - return registry; -} - -static int FindBackendIndex(GpuBackendKind kind) -{ - const Vector& registry = BackendRegistry(); - for(int i = 0; i < registry.GetCount(); ++i) - if(registry[i].kind == kind) - return i; - return -1; + const float scale = 1.0f / 255.0f; + GpuClearColor out; + out.red = color.r * scale; + out.green = color.g * scale; + out.blue = color.b * scale; + out.alpha = color.a * scale; + return out; } } // namespace -bool RegisterGpuPresentationBackend(GpuBackendKind kind, GpuPresentationBackend& backend) -{ - if(kind == GpuBackendKind::Unknown || FindBackendIndex(kind) >= 0) - return false; - BackendRegistration& registration = BackendRegistry().Add(); - registration.kind = kind; - registration.backend = &backend; - return true; -} - -GpuPresentationBackend *FindGpuPresentationBackend(GpuBackendKind kind) -{ - int index = FindBackendIndex(kind); - return index >= 0 ? BackendRegistry()[index].backend : nullptr; -} - -bool IsGpuPresentationBackendRegistered(GpuBackendKind kind) -{ - return FindGpuPresentationBackend(kind) != nullptr; -} - struct GpuContext::Impl { struct ContextEntry : Moveable { GpuBackendKind kind = GpuBackendKind::Unknown; @@ -99,9 +72,131 @@ GpuContext& GpuContext::Default() struct GpuDisplayPresenter::Impl { One session; + std::unique_ptr renderer; GpuContext *context = nullptr; GpuBackendKind backend = GpuBackendKind::Unknown; + GpuNativeWindowDesc native_window; + GpuSurfaceId surface; + GpuSwapchainId swapchain; + Size swapchain_request_size = Size(0, 0); String error; + + GpuDevice *GetDevice() const + { + return session ? session->GetDevice() : nullptr; + } + + void DestroyPresentationObjects() + { + renderer.reset(); + GpuDevice *device = GetDevice(); + if(device) { + if(swapchain.IsValid()) + device->DestroySwapchain(swapchain); + if(surface.IsValid()) + device->DestroySurface(surface); + } + swapchain = GpuSwapchainId(); + surface = GpuSurfaceId(); + swapchain_request_size = Size(0, 0); + native_window = GpuNativeWindowDesc(); + } + + bool EnsureSwapchain(Size requested_size, String& out_error) + { + GpuDevice *device = GetDevice(); + if(!device) { + error = "GPU presentation backend exposes no device"; + out_error = error; + return false; + } + + if(!surface.IsValid()) { + GpuSurfaceDesc desc; + desc.label = "GPU presentation surface"; + desc.size = requested_size; + desc.native_window = native_window; + GpuResult result = device->CreateSurface(desc, surface); + if(result != GpuResult::Ok) { + error = device->GetLastError(); + if(error.IsEmpty()) + error = "logical GPU surface creation failed"; + out_error = error; + return false; + } + } + + if(!swapchain.IsValid()) { + GpuSwapchainDesc desc; + desc.label = "GPU presentation swapchain"; + desc.surface = surface; + desc.size = requested_size; + desc.color_format = GpuFormat::RGBA8; + desc.image_count = 2; + GpuResult result = device->CreateSwapchain(desc, swapchain); + if(result != GpuResult::Ok) { + error = device->GetLastError(); + if(error.IsEmpty()) + error = "logical GPU swapchain creation failed"; + out_error = error; + return false; + } + swapchain_request_size = requested_size; + } + else if(requested_size != swapchain_request_size) { + GpuResult result = device->ResizeSwapchain(swapchain, requested_size); + if(result != GpuResult::Ok) { + error = device->GetLastError(); + if(error.IsEmpty()) + error = "logical GPU swapchain resize failed"; + out_error = error; + return false; + } + swapchain_request_size = requested_size; + } + return true; + } + + GpuResult PresentOnce(const UiDisplayList& list, Rgba8 background, String& out_error) + { + GpuDevice *device = GetDevice(); + if(!device || !renderer) { + error = "GPU presentation session is not ready"; + out_error = error; + return GpuResult::InvalidState; + } + + GpuFrameInfo frame; + GpuResult result = device->BeginFrame(swapchain, frame); + if(result != GpuResult::Ok) { + error = device->GetLastError(); + if(error.IsEmpty()) + error = "GPU BeginFrame failed"; + out_error = error; + return result; + } + + if(!renderer->RenderFrame(list, frame, ToClearColor(background))) { + String render_error = renderer->GetError(); + device->Present(frame.frame); // Release the acquired frame on the failure path. + error = render_error.IsEmpty() ? String("UiRenderer2D frame render failed") : render_error; + out_error = error; + return GpuResult::InvalidState; + } + + result = device->Present(frame.frame); + if(result != GpuResult::Ok) { + error = device->GetLastError(); + if(error.IsEmpty()) + error = "GPU Present failed"; + out_error = error; + return result; + } + + error.Clear(); + out_error.Clear(); + return GpuResult::Ok; + } }; GpuDisplayPresenter::GpuDisplayPresenter() @@ -130,6 +225,7 @@ bool GpuDisplayPresenter::Open(GpuContext& context, GpuBackendKind backend, out_error.Clear(); impl->context = &context; impl->backend = backend; + impl->native_window = native_window; GpuPresentationBackend *provider = FindGpuPresentationBackend(backend); if(!provider) { @@ -171,6 +267,27 @@ bool GpuDisplayPresenter::Open(GpuContext& context, GpuBackendKind backend, impl->session.Clear(); return false; } + + GpuDevice *device = impl->GetDevice(); + if(!device || device->GetBackendKind() != backend) { + impl->error = "GPU presentation backend returned an incompatible device"; + out_error = impl->error; + impl->session->Close(); + impl->session.Clear(); + return false; + } + + impl->renderer.reset(new UiRenderer2D(*device)); + if(!impl->renderer->IsReady()) { + String failure = impl->renderer->GetError(); + impl->renderer.reset(); + impl->session->Close(); + impl->session.Clear(); + impl->error = failure.IsEmpty() ? String("UiRenderer2D initialization failed") : failure; + out_error = impl->error; + return false; + } + impl->error.Clear(); out_error.Clear(); return true; @@ -180,6 +297,7 @@ void GpuDisplayPresenter::Close() { if(!impl) return; + impl->DestroyPresentationObjects(); if(impl->session) impl->session->Close(); impl->session.Clear(); @@ -190,7 +308,8 @@ void GpuDisplayPresenter::Close() bool GpuDisplayPresenter::IsReady() const { - return impl && impl->session && impl->session->IsReady(); + return impl && impl->session && impl->session->IsReady() && impl->GetDevice() && + impl->renderer && impl->renderer->IsReady(); } GpuBackendKind GpuDisplayPresenter::GetBackend() const @@ -211,6 +330,8 @@ bool GpuDisplayPresenter::Present(Size requested_size, const UiDisplayList& list Rgba8 background, String& out_error) { out_error.Clear(); + if(requested_size.cx <= 0 || requested_size.cy <= 0) + return true; if(!IsReady()) { impl->error = GetError(); if(impl->error.IsEmpty()) @@ -218,14 +339,25 @@ bool GpuDisplayPresenter::Present(Size requested_size, const UiDisplayList& list out_error = impl->error; return false; } - if(!impl->session->Present(requested_size, list, background, out_error)) { - impl->error = out_error.IsEmpty() ? impl->session->GetError() : out_error; + if(!impl->EnsureSwapchain(requested_size, out_error)) + return false; + + GpuResult result = impl->PresentOnce(list, background, out_error); + if(result == GpuResult::Ok) + return true; + if(result != GpuResult::OutOfDate) + return false; + + GpuDevice *device = impl->GetDevice(); + if(!device || device->ResizeSwapchain(impl->swapchain, requested_size) != GpuResult::Ok) { + impl->error = device ? device->GetLastError() : String(); + if(impl->error.IsEmpty()) + impl->error = "GPU swapchain recreation after out-of-date presentation failed"; out_error = impl->error; return false; } - impl->error.Clear(); - out_error.Clear(); - return true; + impl->swapchain_request_size = requested_size; + return impl->PresentOnce(list, background, out_error) == GpuResult::Ok; } } From 357ce8c94ee525b638af858c0250f4525fa045be Mon Sep 17 00:00:00 2001 From: Curtis Date: Tue, 25 Aug 2026 00:58:55 +1200 Subject: [PATCH 07/10] TEMP H1 finish provider boundary transformation --- .github/workflows/h1-provider-finish.yml | 98 ++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 .github/workflows/h1-provider-finish.yml diff --git a/.github/workflows/h1-provider-finish.yml b/.github/workflows/h1-provider-finish.yml new file mode 100644 index 0000000..27f8c07 --- /dev/null +++ b/.github/workflows/h1-provider-finish.yml @@ -0,0 +1,98 @@ +name: Temporary H1 provider boundary finish + +on: + pull_request: + branches: + - main + +permissions: + contents: write + +jobs: + patch: + if: github.head_ref == 'supervisor/h1-provider-boundary' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: supervisor/h1-provider-boundary + fetch-depth: 0 + - name: Apply guarded neutral-result and package cleanup + shell: bash + run: | + python3 - <<'PY' + from pathlib import Path + + def replace_once(path, old, new, label): + p = Path(path) + s = p.read_text(encoding='utf-8') + count = s.count(old) + if count != 1: + raise SystemExit(f'{label}: expected 1 match, got {count}') + p.write_text(s.replace(old, new, 1), encoding='utf-8', newline='\n') + + replace_once('render/RenderRhi/RenderRhi.h', + '''enum class GpuResult {\n\tOk,\n\tInvalidArgument,\n\tInvalidHandle,\n\tInvalidState,\n\tNotFound,\n\tUnsupported,\n};''', + '''enum class GpuResult {\n\tOk,\n\tInvalidArgument,\n\tInvalidHandle,\n\tInvalidState,\n\tOutOfDate,\n\tNotFound,\n\tUnsupported,\n};''', + 'GpuResult OutOfDate') + + replace_once('render/RenderRhi/RenderRhi.h', + '''\tvirtual GpuDeviceId GetDeviceId() const = 0;\n\tvirtual GpuBackendKind GetBackendKind() const = 0;\n\tvirtual GpuAdapterInfo GetAdapterInfo() const = 0;''', + '''\tvirtual GpuDeviceId GetDeviceId() const = 0;\n\tvirtual GpuBackendKind GetBackendKind() const = 0;\n\tvirtual GpuAdapterInfo GetAdapterInfo() const = 0;\n\tvirtual String GetLastError() const { return String(); }''', + 'GpuDevice last error') + + replace_once('render/RenderRhi/RenderRhi.cpp', + '''\tcase GpuResult::InvalidState: return "InvalidState";\n\tcase GpuResult::NotFound: return "NotFound";''', + '''\tcase GpuResult::InvalidState: return "InvalidState";\n\tcase GpuResult::OutOfDate: return "OutOfDate";\n\tcase GpuResult::NotFound: return "NotFound";''', + 'DumpGpuResult OutOfDate') + + replace_once('render/RenderVulkan/RenderVulkanRhi.h', + '''\tGpuDeviceId GetDeviceId() const override;\n\tGpuBackendKind GetBackendKind() const override;\n\tGpuAdapterInfo GetAdapterInfo() const override;''', + '''\tGpuDeviceId GetDeviceId() const override;\n\tGpuBackendKind GetBackendKind() const override;\n\tGpuAdapterInfo GetAdapterInfo() const override;\n\tString GetLastError() const override;''', + 'Vulkan neutral error override') + + replace_once('render/RenderVulkan/RenderVulkanRhiBase.inc', + '''GpuDeviceId VulkanGpuDevice::GetDeviceId() const { return impl->device_id; }\nGpuBackendKind VulkanGpuDevice::GetBackendKind() const { return GpuBackendKind::Vulkan; }\nGpuAdapterInfo VulkanGpuDevice::GetAdapterInfo() const { return impl->adapter_info; }''', + '''GpuDeviceId VulkanGpuDevice::GetDeviceId() const { return impl->device_id; }\nGpuBackendKind VulkanGpuDevice::GetBackendKind() const { return GpuBackendKind::Vulkan; }\nGpuAdapterInfo VulkanGpuDevice::GetAdapterInfo() const { return impl->adapter_info; }\nString VulkanGpuDevice::GetLastError() const { return impl ? impl->error : String(); }''', + 'Vulkan neutral error implementation') + + replace_once('render/RenderVulkan/RenderVulkanRhiBase.inc', + '''\tif(!impl->session->AcquireFrame()) {\n\t\timpl->error = impl->session->GetFrameReport().error;\n\t\tif(impl->error.IsEmpty()) impl->error = "VulkanSurfaceSession frame acquisition failed";\n\t\treturn GpuResult::InvalidState;\n\t}''', + '''\tif(!impl->session->AcquireFrame()) {\n\t\timpl->error = impl->session->GetFrameReport().error;\n\t\tif(impl->error.IsEmpty()) impl->error = "VulkanSurfaceSession frame acquisition failed";\n\t\treturn impl->session->GetFrameReport().out_of_date ? GpuResult::OutOfDate : GpuResult::InvalidState;\n\t}''', + 'BeginFrame OutOfDate') + + replace_once('render/RenderVulkan/RenderVulkanRhiBase.inc', + '''\t\treturn GpuResult::InvalidState;\n\t}\n\tImpl::TextureState *texture = impl->FindTexture(state->color_target);\n\tif(texture) impl->ClearBorrowedTexture(*texture);\n\tstate->active = false;\n\tstate->presented = true;''', + '''\t\treturn impl->session->GetFrameReport().out_of_date ? GpuResult::OutOfDate : GpuResult::InvalidState;\n\t}\n\tImpl::TextureState *texture = impl->FindTexture(state->color_target);\n\tif(texture) impl->ClearBorrowedTexture(*texture);\n\tstate->active = false;\n\tstate->presented = true;''', + 'Present OutOfDate') + + p = Path('render/GpuRender/GpuRender.upp') + s = p.read_text(encoding='utf-8') + for line in ('\tRenderPresentationBackend.h,\n', '\tRenderPresentationVulkan.cpp,\n'): + if s.count(line) != 1: + raise SystemExit(f'GpuRender.upp expected one {line.strip()}') + s = s.replace(line, '', 1) + p.write_text(s, encoding='utf-8', newline='\n') + + replace_once('tests/GpuBackendRegistryTest/main.cpp', + '''#include ''', + '''#include ''', + 'registry test include') + + for dead in ('render/GpuRender/RenderPresentationBackend.h', + 'render/GpuRender/RenderPresentationVulkan.cpp'): + p = Path(dead) + if not p.exists(): + raise SystemExit(f'missing expected legacy file {dead}') + p.unlink() + PY + - name: Commit product files only + shell: bash + run: | + rm .github/workflows/h1-provider-finish.yml + git config user.name "upp_render supervisor" + git config user.email "Trilec@users.noreply.github.com" + git add -A + git diff --cached --check + git commit -m "H1 finish neutral presentation provider boundary" + git push origin HEAD:supervisor/h1-provider-boundary From 95dd8a31311df20f3942e0de67bd26a0dae429bd Mon Sep 17 00:00:00 2001 From: upp_render supervisor Date: Mon, 24 Aug 2026 12:59:22 +0000 Subject: [PATCH 08/10] H1 finish neutral presentation provider boundary --- .github/workflows/h1-provider-finish.yml | 98 ------- render/GpuRender/GpuRender.upp | 2 - render/GpuRender/RenderPresentationBackend.h | 43 --- render/GpuRender/RenderPresentationVulkan.cpp | 253 ------------------ render/RenderRhi/RenderRhi.cpp | 1 + render/RenderRhi/RenderRhi.h | 2 + render/RenderVulkan/RenderVulkanRhi.h | 1 + render/RenderVulkan/RenderVulkanRhiBase.inc | 5 +- tests/GpuBackendRegistryTest/main.cpp | 2 +- 9 files changed, 8 insertions(+), 399 deletions(-) delete mode 100644 .github/workflows/h1-provider-finish.yml delete mode 100644 render/GpuRender/RenderPresentationBackend.h delete mode 100644 render/GpuRender/RenderPresentationVulkan.cpp diff --git a/.github/workflows/h1-provider-finish.yml b/.github/workflows/h1-provider-finish.yml deleted file mode 100644 index 27f8c07..0000000 --- a/.github/workflows/h1-provider-finish.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: Temporary H1 provider boundary finish - -on: - pull_request: - branches: - - main - -permissions: - contents: write - -jobs: - patch: - if: github.head_ref == 'supervisor/h1-provider-boundary' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: supervisor/h1-provider-boundary - fetch-depth: 0 - - name: Apply guarded neutral-result and package cleanup - shell: bash - run: | - python3 - <<'PY' - from pathlib import Path - - def replace_once(path, old, new, label): - p = Path(path) - s = p.read_text(encoding='utf-8') - count = s.count(old) - if count != 1: - raise SystemExit(f'{label}: expected 1 match, got {count}') - p.write_text(s.replace(old, new, 1), encoding='utf-8', newline='\n') - - replace_once('render/RenderRhi/RenderRhi.h', - '''enum class GpuResult {\n\tOk,\n\tInvalidArgument,\n\tInvalidHandle,\n\tInvalidState,\n\tNotFound,\n\tUnsupported,\n};''', - '''enum class GpuResult {\n\tOk,\n\tInvalidArgument,\n\tInvalidHandle,\n\tInvalidState,\n\tOutOfDate,\n\tNotFound,\n\tUnsupported,\n};''', - 'GpuResult OutOfDate') - - replace_once('render/RenderRhi/RenderRhi.h', - '''\tvirtual GpuDeviceId GetDeviceId() const = 0;\n\tvirtual GpuBackendKind GetBackendKind() const = 0;\n\tvirtual GpuAdapterInfo GetAdapterInfo() const = 0;''', - '''\tvirtual GpuDeviceId GetDeviceId() const = 0;\n\tvirtual GpuBackendKind GetBackendKind() const = 0;\n\tvirtual GpuAdapterInfo GetAdapterInfo() const = 0;\n\tvirtual String GetLastError() const { return String(); }''', - 'GpuDevice last error') - - replace_once('render/RenderRhi/RenderRhi.cpp', - '''\tcase GpuResult::InvalidState: return "InvalidState";\n\tcase GpuResult::NotFound: return "NotFound";''', - '''\tcase GpuResult::InvalidState: return "InvalidState";\n\tcase GpuResult::OutOfDate: return "OutOfDate";\n\tcase GpuResult::NotFound: return "NotFound";''', - 'DumpGpuResult OutOfDate') - - replace_once('render/RenderVulkan/RenderVulkanRhi.h', - '''\tGpuDeviceId GetDeviceId() const override;\n\tGpuBackendKind GetBackendKind() const override;\n\tGpuAdapterInfo GetAdapterInfo() const override;''', - '''\tGpuDeviceId GetDeviceId() const override;\n\tGpuBackendKind GetBackendKind() const override;\n\tGpuAdapterInfo GetAdapterInfo() const override;\n\tString GetLastError() const override;''', - 'Vulkan neutral error override') - - replace_once('render/RenderVulkan/RenderVulkanRhiBase.inc', - '''GpuDeviceId VulkanGpuDevice::GetDeviceId() const { return impl->device_id; }\nGpuBackendKind VulkanGpuDevice::GetBackendKind() const { return GpuBackendKind::Vulkan; }\nGpuAdapterInfo VulkanGpuDevice::GetAdapterInfo() const { return impl->adapter_info; }''', - '''GpuDeviceId VulkanGpuDevice::GetDeviceId() const { return impl->device_id; }\nGpuBackendKind VulkanGpuDevice::GetBackendKind() const { return GpuBackendKind::Vulkan; }\nGpuAdapterInfo VulkanGpuDevice::GetAdapterInfo() const { return impl->adapter_info; }\nString VulkanGpuDevice::GetLastError() const { return impl ? impl->error : String(); }''', - 'Vulkan neutral error implementation') - - replace_once('render/RenderVulkan/RenderVulkanRhiBase.inc', - '''\tif(!impl->session->AcquireFrame()) {\n\t\timpl->error = impl->session->GetFrameReport().error;\n\t\tif(impl->error.IsEmpty()) impl->error = "VulkanSurfaceSession frame acquisition failed";\n\t\treturn GpuResult::InvalidState;\n\t}''', - '''\tif(!impl->session->AcquireFrame()) {\n\t\timpl->error = impl->session->GetFrameReport().error;\n\t\tif(impl->error.IsEmpty()) impl->error = "VulkanSurfaceSession frame acquisition failed";\n\t\treturn impl->session->GetFrameReport().out_of_date ? GpuResult::OutOfDate : GpuResult::InvalidState;\n\t}''', - 'BeginFrame OutOfDate') - - replace_once('render/RenderVulkan/RenderVulkanRhiBase.inc', - '''\t\treturn GpuResult::InvalidState;\n\t}\n\tImpl::TextureState *texture = impl->FindTexture(state->color_target);\n\tif(texture) impl->ClearBorrowedTexture(*texture);\n\tstate->active = false;\n\tstate->presented = true;''', - '''\t\treturn impl->session->GetFrameReport().out_of_date ? GpuResult::OutOfDate : GpuResult::InvalidState;\n\t}\n\tImpl::TextureState *texture = impl->FindTexture(state->color_target);\n\tif(texture) impl->ClearBorrowedTexture(*texture);\n\tstate->active = false;\n\tstate->presented = true;''', - 'Present OutOfDate') - - p = Path('render/GpuRender/GpuRender.upp') - s = p.read_text(encoding='utf-8') - for line in ('\tRenderPresentationBackend.h,\n', '\tRenderPresentationVulkan.cpp,\n'): - if s.count(line) != 1: - raise SystemExit(f'GpuRender.upp expected one {line.strip()}') - s = s.replace(line, '', 1) - p.write_text(s, encoding='utf-8', newline='\n') - - replace_once('tests/GpuBackendRegistryTest/main.cpp', - '''#include ''', - '''#include ''', - 'registry test include') - - for dead in ('render/GpuRender/RenderPresentationBackend.h', - 'render/GpuRender/RenderPresentationVulkan.cpp'): - p = Path(dead) - if not p.exists(): - raise SystemExit(f'missing expected legacy file {dead}') - p.unlink() - PY - - name: Commit product files only - shell: bash - run: | - rm .github/workflows/h1-provider-finish.yml - git config user.name "upp_render supervisor" - git config user.email "Trilec@users.noreply.github.com" - git add -A - git diff --cached --check - git commit -m "H1 finish neutral presentation provider boundary" - git push origin HEAD:supervisor/h1-provider-boundary diff --git a/render/GpuRender/GpuRender.upp b/render/GpuRender/GpuRender.upp index 5f48979..168ee66 100644 --- a/render/GpuRender/GpuRender.upp +++ b/render/GpuRender/GpuRender.upp @@ -19,8 +19,6 @@ file GpuWindow.h, GpuWindow.cpp, RenderPresentation.h, - RenderPresentationBackend.h, RenderPresentation.cpp, - RenderPresentationVulkan.cpp, RenderCtrlBridge.h, RenderCtrlBridge.cpp; diff --git a/render/GpuRender/RenderPresentationBackend.h b/render/GpuRender/RenderPresentationBackend.h deleted file mode 100644 index 2c0804f..0000000 --- a/render/GpuRender/RenderPresentationBackend.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include "RenderPresentation.h" - -namespace Upp { - -// Internal backend plug-in seam for GpuRender presentation. -// Application-facing GpuCtrl/GpuWindow/GpuTopWindow code never depends on this -// interface and never sees backend-native types. -class GpuPresentationBackendContext { -public: - virtual ~GpuPresentationBackendContext() {} -}; - -class GpuPresentationBackendSession { -public: - virtual ~GpuPresentationBackendSession() {} - - virtual bool Open(bool request_validation, const GpuNativeWindowDesc& native_window, - String& error) = 0; - virtual void Close() = 0; - virtual bool IsReady() const = 0; - virtual String GetError() const = 0; - virtual bool Present(Size requested_size, const UiDisplayList& list, - Rgba8 background, String& error) = 0; -}; - -class GpuPresentationBackend { -public: - virtual ~GpuPresentationBackend() {} - - virtual One CreateContext(String& error) = 0; - virtual One CreateSession(GpuPresentationBackendContext& context, - String& error) = 0; -}; - -// Registration is intended for compiled backend packages during process start. -// One provider owns each backend kind; duplicate registration is rejected. -bool RegisterGpuPresentationBackend(GpuBackendKind kind, GpuPresentationBackend& backend); -GpuPresentationBackend *FindGpuPresentationBackend(GpuBackendKind kind); -bool IsGpuPresentationBackendRegistered(GpuBackendKind kind); - -} diff --git a/render/GpuRender/RenderPresentationVulkan.cpp b/render/GpuRender/RenderPresentationVulkan.cpp deleted file mode 100644 index 36ce193..0000000 --- a/render/GpuRender/RenderPresentationVulkan.cpp +++ /dev/null @@ -1,253 +0,0 @@ -#include "RenderPresentationBackend.h" - -#include -#include - -#include - -namespace Upp { - -namespace { - -static GpuClearColor ToClearColor(Rgba8 color) -{ - const float scale = 1.0f / 255.0f; - GpuClearColor out; - out.red = color.r * scale; - out.green = color.g * scale; - out.blue = color.b * scale; - out.alpha = color.a * scale; - return out; -} - -class VulkanPresentationContext : public GpuPresentationBackendContext { -public: - VulkanSurfaceSessionGroup group; -}; - -class VulkanPresentationSession : public GpuPresentationBackendSession { -public: - explicit VulkanPresentationSession(VulkanPresentationContext& context) - : session(context.group) - { - } - - ~VulkanPresentationSession() override - { - Close(); - } - - bool Open(bool request_validation, const GpuNativeWindowDesc& window, - String& out_error) override - { - Close(); - error.Clear(); - native_window = window; - if(!session.Open(request_validation, native_window)) { - error = session.GetError(); - out_error = error; - return false; - } - - device.reset(new VulkanGpuDevice(session)); - if(!device->IsReady()) { - String failure = device->GetError(); - Close(); - error = failure.IsEmpty() ? String("VulkanGpuDevice initialization failed") : failure; - out_error = error; - return false; - } - - renderer.reset(new UiRenderer2D(*device)); - if(!renderer->IsReady()) { - String failure = renderer->GetError(); - Close(); - error = failure.IsEmpty() ? String("UiRenderer2D initialization failed") : failure; - out_error = error; - return false; - } - - out_error.Clear(); - return true; - } - - void Close() override - { - renderer.reset(); - if(device) { - if(swapchain.IsValid()) - device->DestroySwapchain(swapchain); - swapchain = GpuSwapchainId(); - if(surface.IsValid()) - device->DestroySurface(surface); - surface = GpuSurfaceId(); - } - device.reset(); - session.Close(); - native_window = GpuNativeWindowDesc(); - swapchain_request_size = Size(0, 0); - error.Clear(); - } - - bool IsReady() const override - { - return session.IsReady() && device && device->IsReady() && - renderer && renderer->IsReady(); - } - - String GetError() const override - { - return error; - } - - bool Present(Size requested_size, const UiDisplayList& list, - Rgba8 background, String& out_error) override - { - out_error.Clear(); - if(requested_size.cx <= 0 || requested_size.cy <= 0) - return true; - if(!IsReady()) { - error = !device ? session.GetError() : device->GetError(); - if(error.IsEmpty()) - error = "GPU presentation session is not ready"; - out_error = error; - return false; - } - if(!EnsureSwapchain(requested_size, out_error)) - return false; - if(PresentOnce(list, background, out_error)) - return true; - - if(!session.GetFrameReport().out_of_date) - return false; - if(device->ResizeSwapchain(swapchain, requested_size) != GpuResult::Ok) { - error = device->GetError(); - if(error.IsEmpty()) - error = "GPU swapchain recreation after out-of-date presentation failed"; - out_error = error; - return false; - } - swapchain_request_size = requested_size; - if(PresentOnce(list, background, out_error)) { - out_error.Clear(); - return true; - } - return false; - } - -private: - bool EnsureSwapchain(Size requested_size, String& out_error) - { - if(!surface.IsValid()) { - GpuSurfaceDesc desc; - desc.label = "GPU presentation surface"; - desc.size = requested_size; - desc.native_window = native_window; - GpuResult result = device->CreateSurface(desc, surface); - if(result != GpuResult::Ok) { - error = device->GetError(); - if(error.IsEmpty()) - error = "logical GPU surface creation failed"; - out_error = error; - return false; - } - } - - if(!swapchain.IsValid()) { - GpuSwapchainDesc desc; - desc.label = "GPU presentation swapchain"; - desc.surface = surface; - desc.size = requested_size; - desc.color_format = GpuFormat::RGBA8; - desc.image_count = 2; - GpuResult result = device->CreateSwapchain(desc, swapchain); - if(result != GpuResult::Ok) { - error = device->GetError(); - if(error.IsEmpty()) - error = "logical GPU swapchain creation failed"; - out_error = error; - return false; - } - swapchain_request_size = requested_size; - } - else if(requested_size != swapchain_request_size) { - GpuResult result = device->ResizeSwapchain(swapchain, requested_size); - if(result != GpuResult::Ok) { - error = device->GetError(); - if(error.IsEmpty()) - error = "logical GPU swapchain resize failed"; - out_error = error; - return false; - } - swapchain_request_size = requested_size; - } - return true; - } - - bool PresentOnce(const UiDisplayList& list, Rgba8 background, String& out_error) - { - GpuFrameInfo frame; - GpuResult result = device->BeginFrame(swapchain, frame); - if(result != GpuResult::Ok) { - error = device->GetError(); - if(error.IsEmpty()) - error = "GPU BeginFrame failed"; - out_error = error; - return false; - } - - if(!renderer->RenderFrame(list, frame, ToClearColor(background))) { - String render_error = renderer->GetError(); - device->Present(frame.frame); - error = render_error.IsEmpty() ? String("UiRenderer2D frame render failed") : render_error; - out_error = error; - return false; - } - - result = device->Present(frame.frame); - if(result != GpuResult::Ok) { - error = device->GetError(); - if(error.IsEmpty()) - error = "GPU Present failed"; - out_error = error; - return false; - } - - error.Clear(); - out_error.Clear(); - return true; - } - - VulkanSurfaceSession session; - std::unique_ptr device; - std::unique_ptr renderer; - GpuNativeWindowDesc native_window; - GpuSurfaceId surface; - GpuSwapchainId swapchain; - Size swapchain_request_size = Size(0, 0); - String error; -}; - -class VulkanPresentationBackend : public GpuPresentationBackend { -public: - One CreateContext(String& error) override - { - error.Clear(); - return new VulkanPresentationContext; - } - - One CreateSession(GpuPresentationBackendContext& context, - String& error) override - { - error.Clear(); - return new VulkanPresentationSession(static_cast(context)); - } -}; - -static VulkanPresentationBackend s_vulkan_backend; -static bool s_vulkan_backend_registered = - RegisterGpuPresentationBackend(GpuBackendKind::Vulkan, s_vulkan_backend); - -} // namespace - -} diff --git a/render/RenderRhi/RenderRhi.cpp b/render/RenderRhi/RenderRhi.cpp index b97a198..8be9518 100644 --- a/render/RenderRhi/RenderRhi.cpp +++ b/render/RenderRhi/RenderRhi.cpp @@ -196,6 +196,7 @@ String DumpGpuResult(GpuResult result) case GpuResult::InvalidArgument: return "InvalidArgument"; case GpuResult::InvalidHandle: return "InvalidHandle"; case GpuResult::InvalidState: return "InvalidState"; + case GpuResult::OutOfDate: return "OutOfDate"; case GpuResult::NotFound: return "NotFound"; case GpuResult::Unsupported: return "Unsupported"; } diff --git a/render/RenderRhi/RenderRhi.h b/render/RenderRhi/RenderRhi.h index b0d4e1b..6a32baf 100644 --- a/render/RenderRhi/RenderRhi.h +++ b/render/RenderRhi/RenderRhi.h @@ -108,6 +108,7 @@ enum class GpuResult { InvalidArgument, InvalidHandle, InvalidState, + OutOfDate, NotFound, Unsupported, }; @@ -253,6 +254,7 @@ class GpuDevice { virtual GpuDeviceId GetDeviceId() const = 0; virtual GpuBackendKind GetBackendKind() const = 0; virtual GpuAdapterInfo GetAdapterInfo() const = 0; + virtual String GetLastError() const { return String(); } virtual GpuResult CreateBuffer(const GpuBufferDesc& desc, GpuBufferId& out) = 0; virtual GpuResult WriteBuffer(GpuBufferId id, int64 offset, const void *data, int64 size) = 0; diff --git a/render/RenderVulkan/RenderVulkanRhi.h b/render/RenderVulkan/RenderVulkanRhi.h index 014d668..e0ea213 100644 --- a/render/RenderVulkan/RenderVulkanRhi.h +++ b/render/RenderVulkan/RenderVulkanRhi.h @@ -31,6 +31,7 @@ class VulkanGpuDevice final : public GpuDevice { GpuDeviceId GetDeviceId() const override; GpuBackendKind GetBackendKind() const override; GpuAdapterInfo GetAdapterInfo() const override; + String GetLastError() const override; GpuResult CreateBuffer(const GpuBufferDesc& desc, GpuBufferId& out) override; GpuResult WriteBuffer(GpuBufferId id, int64 offset, const void *data, int64 size) override; diff --git a/render/RenderVulkan/RenderVulkanRhiBase.inc b/render/RenderVulkan/RenderVulkanRhiBase.inc index ad6b1b9..b770273 100644 --- a/render/RenderVulkan/RenderVulkanRhiBase.inc +++ b/render/RenderVulkan/RenderVulkanRhiBase.inc @@ -893,6 +893,7 @@ int VulkanGpuDevice::GetLiveFrameCount() const GpuDeviceId VulkanGpuDevice::GetDeviceId() const { return impl->device_id; } GpuBackendKind VulkanGpuDevice::GetBackendKind() const { return GpuBackendKind::Vulkan; } GpuAdapterInfo VulkanGpuDevice::GetAdapterInfo() const { return impl->adapter_info; } +String VulkanGpuDevice::GetLastError() const { return impl ? impl->error : String(); } GpuResult VulkanGpuDevice::CreateBuffer(const GpuBufferDesc& desc, GpuBufferId& out) { @@ -1150,7 +1151,7 @@ GpuResult VulkanGpuDevice::BeginFrame(GpuSwapchainId swapchain, GpuFrameInfo& ou if(!impl->session->AcquireFrame()) { impl->error = impl->session->GetFrameReport().error; if(impl->error.IsEmpty()) impl->error = "VulkanSurfaceSession frame acquisition failed"; - return GpuResult::InvalidState; + return impl->session->GetFrameReport().out_of_date ? GpuResult::OutOfDate : GpuResult::InvalidState; } VulkanSurfaceSession::FrameInterop interop; uint32_t image_index = UINT32_MAX; @@ -1205,7 +1206,7 @@ GpuResult VulkanGpuDevice::Present(GpuFrameId frame) state->active = false; swapchain->active_frame = GpuFrameId(); } - return GpuResult::InvalidState; + return impl->session->GetFrameReport().out_of_date ? GpuResult::OutOfDate : GpuResult::InvalidState; } Impl::TextureState *texture = impl->FindTexture(state->color_target); if(texture) impl->ClearBorrowedTexture(*texture); diff --git a/tests/GpuBackendRegistryTest/main.cpp b/tests/GpuBackendRegistryTest/main.cpp index bdb0d9c..e122a33 100644 --- a/tests/GpuBackendRegistryTest/main.cpp +++ b/tests/GpuBackendRegistryTest/main.cpp @@ -1,4 +1,4 @@ -#include +#include using namespace Upp; From 7cee7588e6638d2939dca144af00bf9b6c170f1f Mon Sep 17 00:00:00 2001 From: Curtis Date: Tue, 25 Aug 2026 01:00:26 +1200 Subject: [PATCH 09/10] TEMP H1 make backend device access const --- render/RenderRhi/RenderRhiBackend.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render/RenderRhi/RenderRhiBackend.h b/render/RenderRhi/RenderRhiBackend.h index f3be895..2fd8839 100644 --- a/render/RenderRhi/RenderRhiBackend.h +++ b/render/RenderRhi/RenderRhiBackend.h @@ -20,7 +20,7 @@ class GpuPresentationBackendSession { virtual void Close() = 0; virtual bool IsReady() const = 0; virtual String GetError() const = 0; - virtual GpuDevice *GetDevice() = 0; + virtual GpuDevice *GetDevice() const = 0; }; class GpuPresentationBackend { From 933d53361fb5edc3345a4bc2736e43af45a71ac1 Mon Sep 17 00:00:00 2001 From: Curtis Date: Tue, 25 Aug 2026 01:00:52 +1200 Subject: [PATCH 10/10] TEMP H1 align Vulkan provider device accessor --- render/RenderVulkan/RenderVulkanPresentation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render/RenderVulkan/RenderVulkanPresentation.cpp b/render/RenderVulkan/RenderVulkanPresentation.cpp index 8884609..0222c7a 100644 --- a/render/RenderVulkan/RenderVulkanPresentation.cpp +++ b/render/RenderVulkan/RenderVulkanPresentation.cpp @@ -70,7 +70,7 @@ class VulkanPresentationSession : public GpuPresentationBackendSession { return session.GetError(); } - GpuDevice *GetDevice() override + GpuDevice *GetDevice() const override { return IsReady() ? device.get() : nullptr; }