From cde51859b72886eac522fd3aa969dabd2f45abc7 Mon Sep 17 00:00:00 2001 From: dancinlife Date: Fri, 26 Jun 2026 20:15:48 +0900 Subject: [PATCH] =?UTF-8?q?feat(F-NSWINDOW-E5=20r11):=20D3D11=E2=86=92Meta?= =?UTF-8?q?l=20bridge=20+=20native=20driver=20test=20(dead-until-wired)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D3D11-interface-shaped COM vtable layer over Apple Metal headless offscreen: - native/d3d11_metal_bridge.h: COM C structs (ID3D11Device, ID3D11DeviceContext, IDXGISwapChain, ID3D11RenderTargetView, ID3D11Texture2D) with compact vtables (IUnknown stubs [0-2] + implemented methods). Vtable indices documented vs real d3d11.dll for r12 self-authored PE wiring. gamebox_D3D11CreateDeviceAndSwapChain (simplified 5-param vs real 12-param, mapping documented). - native/d3d11_metal_bridge.m: ObjC+Metal+ARC. GBMetalContext holds MTLDevice, queue, PSO, 64x64 BGRA8 offscreen MTLTexture, triColorBuf (red). CFBridgingRetain per COM object for shared ownership. ClearRenderTargetView=Clear-pass, Draw=Load-pass+triangle, Present=no-op (headless). - native/d3d11_metal_bridge_test.c: pure C driver calling through vtable pointers as a D3D11 app would. Local M4 result (clean build -Wall -Wextra): __D3D11_BRIDGE__ PASS px_clear=0x0000FFFF px_triangle=0xFF0000FF nonuniform=1 - .github/workflows/ci.yml: warn-only Smoke step after r10 Metal substrate step. - native/build.sh: d3d11_bridge_test target. - CHANGELOG.md + ARCHITECTURE.json: E5 r11 measured node. own1: Wine 0 / DXVK 0 / d3d11.dll source 0 / DRM 0 / Warden 0. 구현됨·미배선 dead-until-wired. validated_manjeom=0 (native test, not a game frame). r12 = minimal own1 D3D11 test PE + interpreter IAT autobind wiring = manjeom>0 gate. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 35 +++ ARCHITECTURE.json | 6 +- CHANGELOG.md | 45 +++ native/build.sh | 15 +- native/d3d11_metal_bridge.h | 271 ++++++++++++++++ native/d3d11_metal_bridge.m | 522 +++++++++++++++++++++++++++++++ native/d3d11_metal_bridge_test.c | 187 +++++++++++ 7 files changed, 1079 insertions(+), 2 deletions(-) create mode 100644 native/d3d11_metal_bridge.h create mode 100644 native/d3d11_metal_bridge.m create mode 100644 native/d3d11_metal_bridge_test.c diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8a9690..e321627 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,6 +132,41 @@ jobs: echo "::warning::E5-substrate: FAIL or no output — see log above (warn-only)" fi + # ── D3D11→Metal bridge smoke — E5 r11 (F-NSWINDOW-E5 r11). + # Builds native/d3d11_metal_bridge.m + test.c and runs the native driver: + # CreateDeviceAndSwapChain → GetBuffer → CreateRenderTargetView → + # OMSetRenderTargets → ClearRenderTargetView(blue) → Draw(triangle,red) → + # Present → readback → pixel assertions (px_clear=0x0000FFFF, + # px_triangle=0xFF0000FF, nonuniform=1). + # own1: gamebox's own D3D11 *interface shape* over Apple Metal — NOT Wine, + # NOT DXVK, NOT d3d11.dll source. No DRM, no game asset. + # 구현됨·미배선 (dead-until-wired) — proven native but not yet driven + # through the i386 interpreter (r12 target). + # validated_manjeom stays 0; this is a bridge proof, not a game frame. + # + # Warn-only: no-GPU runner → __D3D11_BRIDGE__ SKIP (exit 0). + # compile-fail or pixel assertion fail → ::warning:: (never ::error::). + # Same pattern as r10 Metal substrate step. + - name: Smoke — D3D11→Metal bridge (E5 r11) + shell: bash + run: | + set -uo pipefail + SDK="$(xcrun --sdk macosx --show-sdk-path)" + clang -arch arm64 -isysroot "$SDK" -fobjc-arc -O2 -Wall -Wextra \ + -framework Metal -framework Foundation \ + -o "$RUNNER_TEMP/d3d11_bridge_test" \ + native/d3d11_metal_bridge_test.c native/d3d11_metal_bridge.m \ + || { echo "::warning::d3d11_bridge_test compile failed (E5 r11 — warn-only)"; exit 0; } + out="$("$RUNNER_TEMP/d3d11_bridge_test" 2>&1)" || true + echo "$out" + if echo "$out" | grep -q "__D3D11_BRIDGE__ PASS"; then + echo "E5-r11-bridge: PASS — D3D11→Metal bridge native test confirmed on this runner" + elif echo "$out" | grep -q "__D3D11_BRIDGE__ SKIP"; then + echo "::warning::E5-r11-bridge: SKIP — no GPU/Metal session on this runner (ground-truth finding, not a failure)" + else + echo "::warning::E5-r11-bridge: FAIL or no output — see log above (warn-only)" + fi + # ── CLI smoke — own2 PASS/FAIL/PARTIAL subcommands compile + run. - name: Smoke — gamebox CLI subcommands shell: bash diff --git a/ARCHITECTURE.json b/ARCHITECTURE.json index 619c51e..3288feb 100644 --- a/ARCHITECTURE.json +++ b/ARCHITECTURE.json @@ -40,7 +40,11 @@ }, { "name": "E5 r10 measured (2026-06-26·#32) — 헤드리스 Metal 오프스크린 렌더+readback 로컬 PASS (서브스트레이트 증명)", - "summary": "native/metal_offscreen_smoke.m (ObjC+Metal+Foundation): 64×64 MTLPixelFormatBGRA8Unorm 오프스크린 텍스처·인라인 MSL(newLibraryWithSource:)·blue 클리어+red 삼각형 1 드로우·waitUntilCompleted·getBytes readback·픽셀 어서션 (px_clear=0x0000FFFF·px_triangle=0xFF0000FF·nonuniform=1). 로컬 M4 결과: __METAL_SMOKE__ PASS device=Apple M4. CI(blacksmith-6vcpu-macos-15·실 Apple-Silicon·GPU) 결과: 확인 예정(warn-only step). own1: 순수 Apple Metal·Wine 0·게임 애셋 0. 서브스트레이트 증명 — 게임 프레임 아님·validated_manjeom=0. break-walls+verify-done: \"디스플레이 불필요·Metal 헤드리스 렌더 가능\" 어서션을 실행·CI-게이트 사실로 전환. r11 표적: D3D11 device/swapchain 셰임(lib/loader/dx_d3d11.hexa 실 Metal 배선) → CreateDevice/CreateSwapChain → 이 증명된 오프스크린 경로 라우팅 + 최소 D3D11 테스트 PE 인터프리터 실행." + "summary": "native/metal_offscreen_smoke.m (ObjC+Metal+Foundation): 64×64 MTLPixelFormatBGRA8Unorm 오프스크린 텍스처·인라인 MSL(newLibraryWithSource:)·blue 클리어+red 삼각형 1 드로우·waitUntilCompleted·getBytes readback·픽셀 어서션 (px_clear=0x0000FFFF·px_triangle=0xFF0000FF·nonuniform=1). 로컬 M4 결과: __METAL_SMOKE__ PASS device=Apple M4. CI(blacksmith-6vcpu-macos-15·실 Apple-Silicon·GPU) 결과: 확인 예정(warn-only step). own1: 순수 Apple Metal·Wine 0·게임 애셋 0. 서브스트레이트 증명 — 게임 프레임 아님·validated_manjeom=0. break-walls+verify-done: \"디스플레이 불필요·Metal 헤드리스 렌더 가능\" 어서션을 실행·CI-게이트 사실로 전환. r11 표적: D3D11 device/swapchain 셰임 → Metal 오프스크린 경로 배선." + }, + { + "name": "E5 r11 measured (2026-06-26) — D3D11→Metal 브리지 네이티브 증명 (구현됨·미배선)", + "summary": "native/d3d11_metal_bridge.{h,m} (C헤더+ObjC+Metal+Foundation): D3D11-인터페이스-형상 COM 오브젝트(ID3D11Device·ID3D11DeviceContext·IDXGISwapChain·ID3D11RenderTargetView·ID3D11Texture2D) + 컴팩트 vtable(구현 메서드만; IUnknown 스텁 [0-2] 포함). gamebox_D3D11CreateDeviceAndSwapChain → MTLDevice+오프스크린 64×64 BGRA8 backBuffer+인라인 MSL PSO. ClearRenderTargetView=loadAction:Clear 독립 패스; Draw=loadAction:Load+삼각형; Present=no-op(헤드리스). gamebox_d3d11_readback=[MTLTexture getBytes:]. native/d3d11_metal_bridge_test.c(순수 C): vtable 포인터 경로로 CreateDeviceAndSwapChain→GetBuffer→CreateRenderTargetView→OMSetRenderTargets→ClearRenderTargetView(blue)→Draw(3,0)→Present→readback→픽셀 어서션. 로컬 M4 결과(빌드 clean·-Wall -Wextra 경고 없음): __D3D11_BRIDGE__ PASS px_clear=0x0000FFFF px_triangle=0xFF0000FF nonuniform=1. CI warn-only 스텝 추가(r10 패턴). own1: Wine 0·DXVK 0·d3d11.dll 소스 0·DRM 0. 구현됨·미배선 dead-until-wired — 인터프리터를 통한 PE 구동은 r12. validated_manjeom=0(네이티브 테스트·게임 프레임 아님). 다음 r12: 최소 D3D11 테스트 PE(own1 자작 i386 PE) + 인터프리터 IAT autobind 배선 → 이 브리지 shim → Metal 오프스크린 → readback = validated_manjeom>0 게이트." }, { "name": "🧱 E5 게이트 재분류 (2026-06-26 실측 — substrate/투자 프론티어, 하드웨어 천장 아님)", diff --git a/CHANGELOG.md b/CHANGELOG.md index be25a9b..98cff3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,51 @@ All notable changes to `gamebox` are documented in this file. ### Added +- feat(F-NSWINDOW-E5 r11): **D3D11→Metal 브리지 + 네이티브 드라이버 테스트 (구현됨·미배선)** + — r10 이 증명한 헤드리스 Metal 오프스크린 렌더 서브스트레이트 위에, D3D11-인터페이스-형상의 + Metal 브리지를 구축하고 네이티브 드라이버 테스트로 검증한다. + **own1**: gamebox 자체 D3D11 인터페이스 형상 → Apple Metal 라우팅. Wine 0·DXVK 0· + d3d11.dll 소스 0·DRM 0·Warden 0·게임 애셋 0. **validated_manjeom 여전히 0** — 브리지는 + 네이티브 테스트로 증명됐으나 아직 i386 인터프리터를 통해 PE가 구동하지 않음. + **구현됨·미배선 (dead-until-wired)** 라벨: r12에서 인터프리터 IAT autobind 배선 예정. + (1) **`native/d3d11_metal_bridge.h`** — COM 형상 C 헤더: `ID3D11Device`· + `ID3D11DeviceContext`·`IDXGISwapChain`·`ID3D11RenderTargetView`·`ID3D11Texture2D` + 각 COM 오브젝트를 앞단 vtable 포인터(`lpVtbl`) + `_impl`(opaque bridge-retained + GBMetalContext\*)로 정의. vtable은 구현된 메서드 부분집합만(컴팩트): Device[0-2]= + IUnknown 스텁/Release·[3]=CreateRenderTargetView(실 d3d11.dll 인덱스:9); + Context[3]=OMSetRenderTargets(33)·[4]=ClearRenderTargetView(50)·[5]=Draw(13); + SwapChain[3]=GetBuffer(9)·[4]=Present(8). `gamebox_D3D11CreateDeviceAndSwapChain` + (12-파라미터 실 시그니처를 width+height로 단순화·매핑 문서화) + 픽셀 readback 헬퍼 + `gamebox_d3d11_readback`. Win32 타입(HRESULT·UINT·FLOAT 등) gamebox-local 정의 + (windows.h 의존 없음). r12 wiring 노트: vtable 인덱스가 실 d3d11.dll과 다름(컴팩트); + 자작 r12 테스트 PE는 이 인덱스를 써야 함. + (2) **`native/d3d11_metal_bridge.m`** (Objective-C + Metal + Foundation, ARC): + `GBMetalContext`(ObjC 클래스) — MTLDevice·queue·library·PSO·backBuffer(64×64 + MTLPixelFormatBGRA8Unorm·RenderTarget|ShaderRead·StorageModeShared)·triColorBuf + (red=1,0,0,1) 보유. 각 COM 오브젝트 `_impl` = `CFBridgingRetain(ctx)`(공유 컨텍스트 + 에 대한 명시적 strong ref; Release() 시 CFBridgingRelease + free). MSL 인라인 소스 + (r10 smoke와 동일 셰이더). ClearRenderTargetView → loadAction=Clear 독립 렌더 패스 + (CallerRGBA 색상, 0 드로우); Draw → loadAction=Load 렌더 패스(클리어 보존 + 삼각형 드로우); + Present → no-op(헤드리스); gamebox_d3d11_readback → [MTLTexture getBytes:]. + (3) **`native/d3d11_metal_bridge_test.c`** (순수 C) — D3D11 앱이 호출하는 것과 동일한 + vtable 포인터 경로: CreateDeviceAndSwapChain → sc→GetBuffer → dev→CreateRenderTargetView + → ctx→OMSetRenderTargets → ctx→ClearRenderTargetView(blue) → ctx→Draw(3,0) → + sc→Present → readback → 픽셀 어서션. MTLDevice nil → `__D3D11_BRIDGE__ SKIP + reason=no_device` + exit 0(정직·warn-only). 통과 시 `__D3D11_BRIDGE__ PASS + px_clear=0x0000FFFF px_triangle=0xFF0000FF nonuniform=1` + exit 0. 어서션 실패 시 + `__D3D11_BRIDGE__ FAIL reason=...` + exit 1. **로컬 M4 결과(빌드 clean·-Wall -Wextra + 경고 없음)**: `__D3D11_BRIDGE__ PASS px_clear=0x0000FFFF px_triangle=0xFF0000FF + nonuniform=1`. (4) **`native/build.sh`** — `d3d11_bridge_test` 타깃 추가(ObjC ARC· + Metal/Foundation; `-std=c11`·`-Wpedantic` 제외). ad-hoc codesign. (5) + **`.github/workflows/ci.yml`** — `Smoke — D3D11→Metal bridge (E5 r11)` 스텝 추가: + r10 Metal 서브스트레이트 스텝 직후·CLI smoke 직전. compile+run → `__D3D11_BRIDGE__` + 파싱 → PASS/SKIP/FAIL 각 로그. **warn-only**(컴파일 실패·GPU 없음·어서션 실패 모두 + `::warning::`, 빌드 red 없음). r10과 동일 패턴. + **r12 정의**: 최소 D3D11 테스트 PE(own1 자작 i386 PE·own2 클린·게임 애셋 0) + 인터프리터 + IAT autobind 배선 → D3D11 import(CreateDeviceAndSwapChain·Present 등)를 이 브리지 shim에 + 바인딩 → PE가 vtable 호출 → Metal 오프스크린 렌더 → readback 픽셀 어서션 = + **validated_manjeom>0 달성 게이트**. r12 이름: `feat(F-NSWINDOW-E5 r12): 최소 D3D11 테스트 PE + 인터프리터 end-to-end 배선 (validated_manjeom>0 게이트)`. + - feat(F-NSWINDOW-E5 r10): **헤드리스 Metal 오프스크린 렌더 + 픽셀 readback 스모크 (E5 서브스트레이트 증명)** — r9 가 명시한 다음 과제(`r10 표적 = DX→Metal 브리지(헤드리스 Metal 오프스크린 readback smoke 먼저 CI 검증)`)를 완료한다. **서브스트레이트 증명 — 게임 프레임 아님; `validated_manjeom` 여전히 0.** (1) **`native/metal_offscreen_smoke.m`** diff --git a/native/build.sh b/native/build.sh index 7e51f77..f41f2b7 100755 --- a/native/build.sh +++ b/native/build.sh @@ -54,4 +54,17 @@ codesign -d --entitlements - pe_to_macho_shim 2>&1 | head -10 || true codesign --verify --verbose=2 i386_cpu_test || true codesign --verify --verbose=2 metal_offscreen_smoke || true -echo "[build] done — ./pe_to_macho_shim / ./i386_decode_test [count] / ./i386_cpu_test [pe.exe] / ./metal_offscreen_smoke" +# d3d11_metal_bridge_test — D3D11-interface-shaped Metal bridge test (E5 r11). +# own1: gamebox's own D3D11 *interface shape* over Apple Metal. +# No Wine, no DXVK, no d3d11.dll source. Plain Metal backend. +# 구현됨·미배선 (dead-until-wired to PE via interpreter, r12 target). +echo "[build] compiling d3d11_metal_bridge_test (arch=arm64, sdk=macosx, ObjC+Metal)" +"$CC" -arch arm64 -isysroot "$SDK" -fobjc-arc -O2 -Wall -Wextra \ + -framework Metal -framework Foundation \ + -o d3d11_bridge_test \ + d3d11_metal_bridge_test.c d3d11_metal_bridge.m +echo "[build] codesigning d3d11_bridge_test (ad-hoc)" +codesign --force --sign - --timestamp=none d3d11_bridge_test +codesign --verify --verbose=2 d3d11_bridge_test || true + +echo "[build] done — ./pe_to_macho_shim / ./i386_decode_test [count] / ./i386_cpu_test [pe.exe] / ./metal_offscreen_smoke / ./d3d11_bridge_test" diff --git a/native/d3d11_metal_bridge.h b/native/d3d11_metal_bridge.h new file mode 100644 index 0000000..d32bc69 --- /dev/null +++ b/native/d3d11_metal_bridge.h @@ -0,0 +1,271 @@ +/* + * native/d3d11_metal_bridge.h — D3D11-interface-shaped API routed to Apple Metal + * + * own1: gamebox's own D3D11 *interface shape* over Apple Metal (headless offscreen + * MTLTexture). NO Wine, NO DXVK, NO d3d11.dll source copied. NO DRM, NO Warden, + * NO anti-cheat bypass. This is a plain Metal backend authored for gamebox's own + * Win32 game runtime (F-NSWINDOW-E5 r11). + * + * Status: 구현됨·미배선 (dead-until-wired) + * Proven by native/d3d11_metal_bridge_test.c (native driver test through vtable + * function pointers). NOT yet driven through the i386 interpreter — that is r12: + * r12 = minimal own1 D3D11 test PE (self-authored i386 PE, zero game asset) + + * interpreter IAT autobind wiring → this bridge → validated_manjeom>0 attempt. + * + * COM calling shape: + * Real D3D11 on Windows x86 uses __stdcall (callee-cleans-stack, right-to-left). + * On arm64 macOS (this host), all functions use the C calling convention. + * For r12, the interpreter IAT autobind shim boundary handles the stdcall→C + * translation at the import boundary. + * C COM usage pattern: obj->lpVtbl->Method(obj, arg1, arg2, ...) + * Identical to Win32 COM C usage (MSDN "Using COM Objects in C"). + * + * Vtable layout — COMPACT (gamebox bridge, NOT real d3d11.dll offsets): + * Real d3d11.dll vtables have many methods between IUnknown (indices 0-2) and + * the methods implemented here. This bridge uses compact vtables: IUnknown stubs + * at [0],[1],[2] plus implemented methods consecutively. + * r12's self-authored test PE MUST use THESE compact indices (not real d3d11.dll + * offsets). If a future goal targets real commercial D3D11 games, the vtable must + * be expanded with NULL stub entries at unimplemented positions to match d3d11.dll. + * + * ID3D11Device compact vtable: + * [0] QueryInterface stub → E_NOTIMPL + * [1] AddRef stub → 1 + * [2] Release impl → frees object + releases shared context ref + * [3] CreateRenderTargetView impl (real d3d11.dll index: 9) + * + * ID3D11DeviceContext compact vtable: + * [0] QueryInterface stub → E_NOTIMPL + * [1] AddRef stub → 1 + * [2] Release impl + * [3] OMSetRenderTargets impl (real d3d11.dll index: 33) + * [4] ClearRenderTargetView impl (real d3d11.dll index: 50) + * [5] Draw impl (real d3d11.dll index: 13) + * + * IDXGISwapChain compact vtable: + * [0] QueryInterface stub → E_NOTIMPL + * [1] AddRef stub → 1 + * [2] Release impl + * [3] GetBuffer impl (real IDXGISwapChain index: 9) + * [4] Present impl (real IDXGISwapChain index: 8) + * + * ID3D11RenderTargetView compact vtable: + * [0] QueryInterface stub → E_NOTIMPL + * [1] AddRef stub → 1 + * [2] Release impl + * + * ID3D11Texture2D compact vtable: + * [0] QueryInterface stub → E_NOTIMPL + * [1] AddRef stub → 1 + * [2] Release impl + * + * Texture geometry: GB_D3D_TEX_W × GB_D3D_TEX_H BGRA8Unorm offscreen MTLTexture. + * Pixel format BGRA8 — in-memory order is [B, G, R, A] per pixel. + * pixel_at helper (in test) converts to 0xRRGGBBAA for readability. + */ + +#pragma once +#include +#include + +/* ── Texture dimensions (match metal_offscreen_smoke.m) ── */ +#define GB_D3D_TEX_W 64 +#define GB_D3D_TEX_H 64 + +/* ── Win32 / COM basic types (gamebox-local — no windows.h dependency) ── */ +typedef int32_t HRESULT; +typedef uint32_t UINT; +typedef uint32_t DWORD; +typedef float FLOAT; + +#define S_OK ((HRESULT)0) +#define E_FAIL ((HRESULT)(int32_t)0x80004005) +#define E_NOTIMPL ((HRESULT)(int32_t)0x80004001) +#define E_OUTOFMEMORY ((HRESULT)(int32_t)0x8007000E) + +#define SUCCEEDED(hr) ((HRESULT)(hr) >= 0) +#define FAILED(hr) ((HRESULT)(hr) < 0) + +/* ── Forward declarations for all COM objects ── */ +typedef struct ID3D11Device ID3D11Device; +typedef struct ID3D11DeviceContext ID3D11DeviceContext; +typedef struct IDXGISwapChain IDXGISwapChain; +typedef struct ID3D11RenderTargetView ID3D11RenderTargetView; +typedef struct ID3D11Texture2D ID3D11Texture2D; + +/* ══════════════════════════════════════════════════════════════ + Vtable type definitions — one per COM interface + ══════════════════════════════════════════════════════════════ */ + +/* ── ID3D11Device vtable ── */ +typedef struct ID3D11DeviceVtbl { + /* [0] IUnknown::QueryInterface — stub */ + HRESULT (*QueryInterface)(ID3D11Device *self, void *riid, void **ppv); + /* [1] IUnknown::AddRef — stub (returns 1) */ + UINT (*AddRef) (ID3D11Device *self); + /* [2] IUnknown::Release — implemented (frees object) */ + UINT (*Release) (ID3D11Device *self); + /* [3] ID3D11Device::CreateRenderTargetView + * pDesc: NULL → full-resource view (only supported value here) + * Real d3d11.dll vtable index: 9 */ + HRESULT (*CreateRenderTargetView)( + ID3D11Device *self, + ID3D11Texture2D *pResource, + void *pDesc, + ID3D11RenderTargetView **ppRTView + ); +} ID3D11DeviceVtbl; + +struct ID3D11Device { + const ID3D11DeviceVtbl *lpVtbl; + void *_impl; /* opaque — bridge-retained GBMetalContext* */ +}; + +/* ── ID3D11DeviceContext vtable ── */ +typedef struct ID3D11DeviceContextVtbl { + /* [0] stub */ HRESULT (*QueryInterface)(ID3D11DeviceContext *self, void *riid, void **ppv); + /* [1] stub */ UINT (*AddRef) (ID3D11DeviceContext *self); + /* [2] impl */ UINT (*Release) (ID3D11DeviceContext *self); + /* [3] ID3D11DeviceContext::OMSetRenderTargets + * pDepthStencilView: ignored (no depth in this bridge) + * Real index: 33 */ + void (*OMSetRenderTargets)( + ID3D11DeviceContext *self, + UINT NumViews, + ID3D11RenderTargetView *const *ppRenderTargetViews, + void *pDepthStencilView + ); + /* [4] ID3D11DeviceContext::ClearRenderTargetView + * ColorRGBA: float[4] in RGBA order (same as real D3D11) + * Real index: 50 */ + void (*ClearRenderTargetView)( + ID3D11DeviceContext *self, + ID3D11RenderTargetView *pRenderTargetView, + const FLOAT ColorRGBA[4] + ); + /* [5] ID3D11DeviceContext::Draw + * Draws VertexCount vertices starting at StartVertexLocation. + * Triangle color is bridge-fixed to red (1,0,0,1) — r12 wiring will + * add constant-buffer support for arbitrary colors. + * Real index: 13 */ + void (*Draw)( + ID3D11DeviceContext *self, + UINT VertexCount, + UINT StartVertexLocation + ); +} ID3D11DeviceContextVtbl; + +struct ID3D11DeviceContext { + const ID3D11DeviceContextVtbl *lpVtbl; + void *_impl; /* bridge-retained GBMetalContext* */ +}; + +/* ── IDXGISwapChain vtable ── */ +typedef struct IDXGISwapChainVtbl { + /* [0] stub */ HRESULT (*QueryInterface)(IDXGISwapChain *self, void *riid, void **ppv); + /* [1] stub */ UINT (*AddRef) (IDXGISwapChain *self); + /* [2] impl */ UINT (*Release) (IDXGISwapChain *self); + /* [3] IDXGISwapChain::GetBuffer + * Buffer=0 → back buffer (only supported value) + * riid: ignored (always returns ID3D11Texture2D shape) + * Real IDXGISwapChain index: 9 */ + HRESULT (*GetBuffer)( + IDXGISwapChain *self, + UINT Buffer, + void *riid, + void **ppSurface + ); + /* [4] IDXGISwapChain::Present + * Offscreen: finalizes pending render; no display flip. + * SyncInterval, Flags: ignored in this bridge (headless). + * Real IDXGISwapChain index: 8 */ + HRESULT (*Present)( + IDXGISwapChain *self, + UINT SyncInterval, + UINT Flags + ); +} IDXGISwapChainVtbl; + +struct IDXGISwapChain { + const IDXGISwapChainVtbl *lpVtbl; + void *_impl; /* bridge-retained GBMetalContext* */ +}; + +/* ── ID3D11RenderTargetView vtable (no methods beyond IUnknown) ── */ +typedef struct ID3D11RenderTargetViewVtbl { + /* [0] stub */ HRESULT (*QueryInterface)(ID3D11RenderTargetView *self, void *riid, void **ppv); + /* [1] stub */ UINT (*AddRef) (ID3D11RenderTargetView *self); + /* [2] impl */ UINT (*Release) (ID3D11RenderTargetView *self); +} ID3D11RenderTargetViewVtbl; + +struct ID3D11RenderTargetView { + const ID3D11RenderTargetViewVtbl *lpVtbl; + void *_impl; /* bridge-retained GBMetalContext* */ +}; + +/* ── ID3D11Texture2D vtable (no methods beyond IUnknown) ── */ +typedef struct ID3D11Texture2DVtbl { + /* [0] stub */ HRESULT (*QueryInterface)(ID3D11Texture2D *self, void *riid, void **ppv); + /* [1] stub */ UINT (*AddRef) (ID3D11Texture2D *self); + /* [2] impl */ UINT (*Release) (ID3D11Texture2D *self); +} ID3D11Texture2DVtbl; + +struct ID3D11Texture2D { + const ID3D11Texture2DVtbl *lpVtbl; + void *_impl; /* bridge-retained GBMetalContext* */ +}; + +/* ══════════════════════════════════════════════════════════════ + Bridge entry points (implemented in d3d11_metal_bridge.m) + ══════════════════════════════════════════════════════════════ */ + +/* + * gamebox_D3D11CreateDeviceAndSwapChain + * + * Simplified equivalent of the real Win32 D3D11CreateDeviceAndSwapChain (12 params): + * + * HRESULT D3D11CreateDeviceAndSwapChain( + * IDXGIAdapter*, [ignored] select GPU adapter + * D3D_DRIVER_TYPE, [ignored] hardware / reference / null + * HMODULE, [ignored] software rasterizer module + * UINT, [ignored] device creation flags + * const D3D_FEATURE_LEVEL*, [ignored] requested feature levels + * UINT, [ignored] count of feature levels + * UINT, [ignored] D3D11_SDK_VERSION + * const DXGI_SWAP_CHAIN_DESC*,[simplified to width+height] + * IDXGISwapChain**, out → ppSwapChain + * ID3D11Device**, out → ppDevice + * D3D_FEATURE_LEVEL*, [ignored] out: selected feature level + * ID3D11DeviceContext** out → ppImmediateContext + * ); + * + * Bridge simplification: the first 7 params + DXGI_SWAP_CHAIN_DESC are collapsed + * to (width, height). The three output pointers have identical semantics. + * Returns S_OK on success; E_FAIL if MTLCreateSystemDefaultDevice() returns nil + * (no GPU — same as the SKIP path in metal_offscreen_smoke.m). + */ +HRESULT gamebox_D3D11CreateDeviceAndSwapChain( + UINT width, + UINT height, + IDXGISwapChain **ppSwapChain, + ID3D11Device **ppDevice, + ID3D11DeviceContext **ppImmediateContext +); + +/* + * gamebox_d3d11_readback + * + * Non-D3D11 diagnostic helper: reads back the swapchain's offscreen back-buffer + * pixels into out_pixels (caller-allocated). Format: BGRA8 (4 bytes/pixel), + * row-major, width×height pixels. size must be >= GB_D3D_TEX_W*GB_D3D_TEX_H*4. + * + * Converts pixel (x,y) to 0xRRGGBBAA like pixel_at in metal_offscreen_smoke.m: + * bytes = [B,G,R,A] → return (R<<24)|(G<<16)|(B<<8)|A. + * (The caller reads raw bytes; this function just fills the buffer.) + * Returns S_OK on success. + */ +HRESULT gamebox_d3d11_readback( + IDXGISwapChain *swapchain, + uint8_t *out_pixels, + size_t size +); diff --git a/native/d3d11_metal_bridge.m b/native/d3d11_metal_bridge.m new file mode 100644 index 0000000..ee9da7c --- /dev/null +++ b/native/d3d11_metal_bridge.m @@ -0,0 +1,522 @@ +/* + * native/d3d11_metal_bridge.m — D3D11-interface-shaped API over Apple Metal + * + * own1: gamebox's own D3D11 *interface shape* routed to Apple Metal headless + * offscreen rendering. NO Wine, NO DXVK, NO d3d11.dll source. NO DRM, NO Warden, + * NO anti-cheat bypass. Plain Metal backend; gamebox-authored code only. + * + * F-NSWINDOW-E5 r11 — 구현됨·미배선 (dead-until-wired to PE via interpreter). + * + * Architecture: + * GBMetalContext (ObjC, ARC-managed) holds all Metal resources: device, queue, + * library, pipeline state, back-buffer texture, triangle color buffer. + * Each COM object (ID3D11Device, ID3D11DeviceContext, IDXGISwapChain, + * ID3D11RenderTargetView, ID3D11Texture2D) is a heap-allocated C struct with + * a leading vtable pointer (lpVtbl) + an _impl void* that holds a + * CFBridgingRetain reference to the shared GBMetalContext. + * COM Release() calls CFBridgingRelease(_impl) + free(self) — when all COM + * objects are released the ARC retain count on GBMetalContext drops to 0 and + * it deallocs naturally. + * + * Render model (offscreen, headless): + * ClearRenderTargetView → Metal render pass: loadAction=Clear, 0 draws, commit+wait + * Draw(n, start) → Metal render pass: loadAction=Load, draw n vertices, commit+wait + * Present → no-op (headless; content already in MTLTexture) + * gamebox_d3d11_readback → [MTLTexture getBytes:...] into caller buffer + * + * Triangle color: red (1,0,0,1) hardcoded in triColorBuf. + * Clear color: supplied by caller to ClearRenderTargetView (RGBA float[4]). + * + * Build: + * SDK="$(xcrun --sdk macosx --show-sdk-path)" + * clang -arch arm64 -isysroot "$SDK" -fobjc-arc -O2 -Wall -Wextra \ + * -framework Metal -framework Foundation \ + * -o /tmp/d3d11_bridge_test \ + * native/d3d11_metal_bridge_test.c native/d3d11_metal_bridge.m + */ + +#import +#import +#include "d3d11_metal_bridge.h" +#include +#include + +/* ── Inline MSL (identical geometry to metal_offscreen_smoke.m) ── */ +static NSString * const kShaderSrc = @ +"#include \n" +"using namespace metal;\n" +"\n" +"struct VertexOut {\n" +" float4 position [[position]];\n" +" float4 color;\n" +"};\n" +"\n" +"vertex VertexOut vtx_main(\n" +" uint vid [[vertex_id]],\n" +" constant float4 *colors [[buffer(1)]]\n" +") {\n" +" /* unit-NDC triangle covering upper-left quadrant — same as r10 smoke */\n" +" const float2 pos[3] = {\n" +" float2(-0.5f, 0.5f),\n" +" float2( 0.5f, 0.5f),\n" +" float2(-0.5f, -0.5f)\n" +" };\n" +" VertexOut o;\n" +" o.position = float4(pos[vid], 0.0f, 1.0f);\n" +" o.color = colors[0];\n" +" return o;\n" +"}\n" +"\n" +"fragment float4 frag_main(VertexOut in [[stage_in]]) {\n" +" return in.color;\n" +"}\n"; + +/* ════════════════════════════════════════════════════════════════ + GBMetalContext — shared Metal resource container (ARC-managed) + ════════════════════════════════════════════════════════════════ */ + +@interface GBMetalContext : NSObject +@property (nonatomic, strong) id device; +@property (nonatomic, strong) id queue; +@property (nonatomic, strong) id lib; +@property (nonatomic, strong) id pso; +@property (nonatomic, strong) id backBuffer; /* offscreen swapchain */ +@property (nonatomic, strong) id triColorBuf; /* red (1,0,0,1) */ +@property (nonatomic, assign) NSUInteger width; +@property (nonatomic, assign) NSUInteger height; +@end + +@implementation GBMetalContext +@end + +/* ── Helper: extract GBMetalContext from _impl void* ── */ +static inline GBMetalContext *get_ctx(void *impl) { + return (__bridge GBMetalContext *)impl; +} + +/* ════════════════════════════════════════════════════════════════ + Forward declarations for vtable function pointers + (allows static vtable structs to reference function addresses + before their full definitions appear in the file) + ════════════════════════════════════════════════════════════════ */ + +/* ID3D11Texture2D */ +static HRESULT tex2d_QueryInterface(ID3D11Texture2D *self, void *riid, void **ppv); +static UINT tex2d_AddRef (ID3D11Texture2D *self); +static UINT tex2d_Release (ID3D11Texture2D *self); + +/* ID3D11RenderTargetView */ +static HRESULT rtv_QueryInterface(ID3D11RenderTargetView *self, void *riid, void **ppv); +static UINT rtv_AddRef (ID3D11RenderTargetView *self); +static UINT rtv_Release (ID3D11RenderTargetView *self); + +/* ID3D11DeviceContext */ +static HRESULT ctx_QueryInterface (ID3D11DeviceContext *self, void *riid, void **ppv); +static UINT ctx_AddRef (ID3D11DeviceContext *self); +static UINT ctx_Release (ID3D11DeviceContext *self); +static void ctx_OMSetRenderTargets(ID3D11DeviceContext *self, UINT n, + ID3D11RenderTargetView *const *views, void *dsv); +static void ctx_ClearRenderTargetView(ID3D11DeviceContext *self, + ID3D11RenderTargetView *rtv, + const FLOAT color[4]); +static void ctx_Draw (ID3D11DeviceContext *self, UINT vc, UINT start); + +/* IDXGISwapChain */ +static HRESULT sc_QueryInterface(IDXGISwapChain *self, void *riid, void **ppv); +static UINT sc_AddRef (IDXGISwapChain *self); +static UINT sc_Release (IDXGISwapChain *self); +static HRESULT sc_GetBuffer (IDXGISwapChain *self, UINT buf, void *riid, void **ppSurf); +static HRESULT sc_Present (IDXGISwapChain *self, UINT sync, UINT flags); + +/* ID3D11Device */ +static HRESULT dev_QueryInterface (ID3D11Device *self, void *riid, void **ppv); +static UINT dev_AddRef (ID3D11Device *self); +static UINT dev_Release (ID3D11Device *self); +static HRESULT dev_CreateRenderTargetView(ID3D11Device *self, ID3D11Texture2D *res, + void *desc, ID3D11RenderTargetView **ppRTV); + +/* ════════════════════════════════════════════════════════════════ + Static vtable instances (one per COM interface) + ════════════════════════════════════════════════════════════════ */ + +static const ID3D11Texture2DVtbl s_tex2dVtbl = { + .QueryInterface = tex2d_QueryInterface, + .AddRef = tex2d_AddRef, + .Release = tex2d_Release, +}; + +static const ID3D11RenderTargetViewVtbl s_rtvVtbl = { + .QueryInterface = rtv_QueryInterface, + .AddRef = rtv_AddRef, + .Release = rtv_Release, +}; + +static const ID3D11DeviceContextVtbl s_ctxVtbl = { + .QueryInterface = ctx_QueryInterface, + .AddRef = ctx_AddRef, + .Release = ctx_Release, + .OMSetRenderTargets = ctx_OMSetRenderTargets, + .ClearRenderTargetView = ctx_ClearRenderTargetView, + .Draw = ctx_Draw, +}; + +static const IDXGISwapChainVtbl s_scVtbl = { + .QueryInterface = sc_QueryInterface, + .AddRef = sc_AddRef, + .Release = sc_Release, + .GetBuffer = sc_GetBuffer, + .Present = sc_Present, +}; + +static const ID3D11DeviceVtbl s_devVtbl = { + .QueryInterface = dev_QueryInterface, + .AddRef = dev_AddRef, + .Release = dev_Release, + .CreateRenderTargetView = dev_CreateRenderTargetView, +}; + +/* ════════════════════════════════════════════════════════════════ + ID3D11Texture2D vtable implementations + ════════════════════════════════════════════════════════════════ */ + +static HRESULT tex2d_QueryInterface(ID3D11Texture2D *self, void *riid, void **ppv) { + (void)self; (void)riid; (void)ppv; + return E_NOTIMPL; +} +static UINT tex2d_AddRef(ID3D11Texture2D *self) { (void)self; return 1; } +static UINT tex2d_Release(ID3D11Texture2D *self) { + if (!self) return 0; + CFBridgingRelease(self->_impl); + free(self); + return 0; +} + +/* ════════════════════════════════════════════════════════════════ + ID3D11RenderTargetView vtable implementations + ════════════════════════════════════════════════════════════════ */ + +static HRESULT rtv_QueryInterface(ID3D11RenderTargetView *self, void *riid, void **ppv) { + (void)self; (void)riid; (void)ppv; + return E_NOTIMPL; +} +static UINT rtv_AddRef(ID3D11RenderTargetView *self) { (void)self; return 1; } +static UINT rtv_Release(ID3D11RenderTargetView *self) { + if (!self) return 0; + CFBridgingRelease(self->_impl); + free(self); + return 0; +} + +/* ════════════════════════════════════════════════════════════════ + ID3D11DeviceContext vtable implementations + ════════════════════════════════════════════════════════════════ */ + +static HRESULT ctx_QueryInterface(ID3D11DeviceContext *self, void *riid, void **ppv) { + (void)self; (void)riid; (void)ppv; + return E_NOTIMPL; +} +static UINT ctx_AddRef(ID3D11DeviceContext *self) { (void)self; return 1; } +static UINT ctx_Release(ID3D11DeviceContext *self) { + if (!self) return 0; + CFBridgingRelease(self->_impl); + free(self); + return 0; +} + +static void ctx_OMSetRenderTargets( + ID3D11DeviceContext *self, + UINT NumViews, + ID3D11RenderTargetView *const *ppViews, + void *pDepthStencilView) +{ + /* + * In this bridge there is exactly one render target: the swapchain back buffer. + * OMSetRenderTargets is a no-op here — ClearRenderTargetView and Draw always + * use GBMetalContext.backBuffer. The pRenderTargetView passed to + * ClearRenderTargetView is also ignored (backBuffer is used directly). + * r12 note: if multiple render targets are ever needed, store the selected + * texture pointer in GBMetalContext and use it in the render passes. + */ + (void)self; (void)NumViews; (void)ppViews; (void)pDepthStencilView; +} + +static void ctx_ClearRenderTargetView( + ID3D11DeviceContext *self, + ID3D11RenderTargetView *pRTV, + const FLOAT ColorRGBA[4]) +{ + /* + * Clear the back buffer with ColorRGBA (RGBA float[4], same order as real D3D11). + * Executed as a standalone render pass: loadAction=Clear, no draw commands, + * storeAction=Store, commit + waitUntilCompleted. + */ + (void)pRTV; /* always uses ctx.backBuffer */ + if (!self) return; + GBMetalContext *ctx = get_ctx(self->_impl); + if (!ctx) return; + + @autoreleasepool { + MTLRenderPassDescriptor *rpDesc = [MTLRenderPassDescriptor new]; + rpDesc.colorAttachments[0].texture = ctx.backBuffer; + rpDesc.colorAttachments[0].loadAction = MTLLoadActionClear; + rpDesc.colorAttachments[0].clearColor = + MTLClearColorMake(ColorRGBA[0], ColorRGBA[1], ColorRGBA[2], ColorRGBA[3]); + rpDesc.colorAttachments[0].storeAction = MTLStoreActionStore; + + id cmd = [ctx.queue commandBuffer]; + id enc = [cmd renderCommandEncoderWithDescriptor:rpDesc]; + /* No draw commands — clear only */ + [enc endEncoding]; + [cmd commit]; + [cmd waitUntilCompleted]; + } +} + +static void ctx_Draw( + ID3D11DeviceContext *self, + UINT VertexCount, + UINT StartVertexLocation) +{ + /* + * Draw VertexCount vertices (starting at StartVertexLocation) using the + * bridge's red-triangle pipeline. loadAction=Load preserves the prior clear. + * Triangle color is hardcoded red (1,0,0,1) via ctx.triColorBuf. + * r12 note: to support arbitrary colors, add a constant-buffer argument and + * update triColorBuf before the render pass. + */ + if (!self) return; + GBMetalContext *ctx = get_ctx(self->_impl); + if (!ctx) return; + + @autoreleasepool { + MTLRenderPassDescriptor *rpDesc = [MTLRenderPassDescriptor new]; + rpDesc.colorAttachments[0].texture = ctx.backBuffer; + rpDesc.colorAttachments[0].loadAction = MTLLoadActionLoad; /* preserve clear */ + rpDesc.colorAttachments[0].storeAction = MTLStoreActionStore; + + id cmd = [ctx.queue commandBuffer]; + id enc = [cmd renderCommandEncoderWithDescriptor:rpDesc]; + [enc setRenderPipelineState:ctx.pso]; + [enc setVertexBuffer:ctx.triColorBuf offset:0 atIndex:1]; + [enc drawPrimitives:MTLPrimitiveTypeTriangle + vertexStart:StartVertexLocation + vertexCount:VertexCount]; + [enc endEncoding]; + [cmd commit]; + [cmd waitUntilCompleted]; + } +} + +/* ════════════════════════════════════════════════════════════════ + IDXGISwapChain vtable implementations + ════════════════════════════════════════════════════════════════ */ + +static HRESULT sc_QueryInterface(IDXGISwapChain *self, void *riid, void **ppv) { + (void)self; (void)riid; (void)ppv; + return E_NOTIMPL; +} +static UINT sc_AddRef(IDXGISwapChain *self) { (void)self; return 1; } +static UINT sc_Release(IDXGISwapChain *self) { + if (!self) return 0; + CFBridgingRelease(self->_impl); + free(self); + return 0; +} + +static HRESULT sc_GetBuffer( + IDXGISwapChain *self, + UINT Buffer, + void *riid, + void **ppSurface) +{ + /* + * Returns the back-buffer surface as an ID3D11Texture2D. + * Buffer must be 0 (only one back buffer in this bridge). + * riid is ignored (always returns our ID3D11Texture2D shape). + */ + (void)riid; + if (!self || !ppSurface) return E_FAIL; + if (Buffer != 0) return E_FAIL; + + GBMetalContext *ctx = get_ctx(self->_impl); + + ID3D11Texture2D *tex = (ID3D11Texture2D *)calloc(1, sizeof(ID3D11Texture2D)); + if (!tex) return E_OUTOFMEMORY; + + tex->lpVtbl = &s_tex2dVtbl; + tex->_impl = (void *)CFBridgingRetain(ctx); + *ppSurface = tex; + return S_OK; +} + +static HRESULT sc_Present(IDXGISwapChain *self, UINT SyncInterval, UINT Flags) { + /* + * Headless bridge — the back buffer is an offscreen MTLTexture; there is no + * display connection. Present is a no-op: the render commands were already + * committed + waited in ClearRenderTargetView / Draw. Pixels are ready for + * gamebox_d3d11_readback immediately after Present returns. + */ + (void)self; (void)SyncInterval; (void)Flags; + return S_OK; +} + +/* ════════════════════════════════════════════════════════════════ + ID3D11Device vtable implementations + ════════════════════════════════════════════════════════════════ */ + +static HRESULT dev_QueryInterface(ID3D11Device *self, void *riid, void **ppv) { + (void)self; (void)riid; (void)ppv; + return E_NOTIMPL; +} +static UINT dev_AddRef(ID3D11Device *self) { (void)self; return 1; } +static UINT dev_Release(ID3D11Device *self) { + if (!self) return 0; + CFBridgingRelease(self->_impl); + free(self); + return 0; +} + +static HRESULT dev_CreateRenderTargetView( + ID3D11Device *self, + ID3D11Texture2D *pResource, + void *pDesc, + ID3D11RenderTargetView **ppRTV) +{ + /* + * Creates an ID3D11RenderTargetView that wraps the texture. + * pDesc=NULL → full-resource view (only supported value in this bridge). + * The RTV shares the GBMetalContext with all other COM objects; + * ClearRenderTargetView uses ctx.backBuffer regardless of which texture + * pResource wraps (they always refer to the same back buffer in this bridge). + */ + (void)pResource; (void)pDesc; + if (!self || !ppRTV) return E_FAIL; + + GBMetalContext *ctx = get_ctx(self->_impl); + + ID3D11RenderTargetView *rtv = + (ID3D11RenderTargetView *)calloc(1, sizeof(ID3D11RenderTargetView)); + if (!rtv) return E_OUTOFMEMORY; + + rtv->lpVtbl = &s_rtvVtbl; + rtv->_impl = (void *)CFBridgingRetain(ctx); + *ppRTV = rtv; + return S_OK; +} + +/* ════════════════════════════════════════════════════════════════ + Bridge entry: gamebox_D3D11CreateDeviceAndSwapChain + ════════════════════════════════════════════════════════════════ */ + +HRESULT gamebox_D3D11CreateDeviceAndSwapChain( + UINT width, + UINT height, + IDXGISwapChain **ppSwapChain, + ID3D11Device **ppDevice, + ID3D11DeviceContext **ppImmediateContext) +{ + if (!ppSwapChain || !ppDevice || !ppImmediateContext) return E_FAIL; + *ppSwapChain = NULL; + *ppDevice = NULL; + *ppImmediateContext = NULL; + + @autoreleasepool { + + /* ── 1. MTLDevice ── */ + id mtlDev = MTLCreateSystemDefaultDevice(); + if (!mtlDev) { + return E_FAIL; /* caller interprets as SKIP (no GPU) */ + } + + /* ── 2. Shared Metal context ── */ + GBMetalContext *ctx = [[GBMetalContext alloc] init]; + ctx.device = mtlDev; + ctx.width = width; + ctx.height = height; + + /* ── 3. Command queue ── */ + ctx.queue = [mtlDev newCommandQueue]; + if (!ctx.queue) return E_FAIL; + + /* ── 4. Offscreen back-buffer texture (BGRA8, RenderTarget+ShaderRead, Shared) ── */ + MTLTextureDescriptor *td = [MTLTextureDescriptor + texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm + width:width + height:height + mipmapped:NO]; + td.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead; + td.storageMode = MTLStorageModeShared; /* CPU-accessible without blit */ + ctx.backBuffer = [mtlDev newTextureWithDescriptor:td]; + if (!ctx.backBuffer) return E_FAIL; + + /* ── 5. Compile inline MSL library ── */ + NSError *err = nil; + ctx.lib = [mtlDev newLibraryWithSource:kShaderSrc options:nil error:&err]; + if (!ctx.lib) return E_FAIL; + + id vtxFn = [ctx.lib newFunctionWithName:@"vtx_main"]; + id fragFn = [ctx.lib newFunctionWithName:@"frag_main"]; + if (!vtxFn || !fragFn) return E_FAIL; + + /* ── 6. Render pipeline state ── */ + MTLRenderPipelineDescriptor *rpd = [MTLRenderPipelineDescriptor new]; + rpd.vertexFunction = vtxFn; + rpd.fragmentFunction = fragFn; + rpd.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm; + + ctx.pso = [mtlDev newRenderPipelineStateWithDescriptor:rpd error:&err]; + if (!ctx.pso) return E_FAIL; + + /* ── 7. Triangle color buffer: red (1,0,0,1) ── */ + float red[4] = {1.0f, 0.0f, 0.0f, 1.0f}; + ctx.triColorBuf = [mtlDev newBufferWithBytes:red + length:sizeof(red) + options:MTLResourceStorageModeShared]; + if (!ctx.triColorBuf) return E_FAIL; + + /* ── 8. Allocate COM object shells ── */ + IDXGISwapChain *sc = (IDXGISwapChain *) calloc(1, sizeof(IDXGISwapChain)); + ID3D11Device *dev = (ID3D11Device *) calloc(1, sizeof(ID3D11Device)); + ID3D11DeviceContext*dxc = (ID3D11DeviceContext *)calloc(1, sizeof(ID3D11DeviceContext)); + if (!sc || !dev || !dxc) { + free(sc); free(dev); free(dxc); + return E_OUTOFMEMORY; + } + + /* ── 9. Wire vtables + bridge-retain the shared context into each COM object ── */ + sc->lpVtbl = &s_scVtbl; sc->_impl = (void *)CFBridgingRetain(ctx); + dev->lpVtbl = &s_devVtbl; dev->_impl = (void *)CFBridgingRetain(ctx); + dxc->lpVtbl = &s_ctxVtbl; dxc->_impl = (void *)CFBridgingRetain(ctx); + + *ppSwapChain = sc; + *ppDevice = dev; + *ppImmediateContext = dxc; + return S_OK; + + } /* @autoreleasepool — ctx local ARC ref drops here; bridge retains keep it alive */ +} + +/* ════════════════════════════════════════════════════════════════ + Diagnostic helper: gamebox_d3d11_readback + ════════════════════════════════════════════════════════════════ */ + +HRESULT gamebox_d3d11_readback( + IDXGISwapChain *swapchain, + uint8_t *out_pixels, + size_t size) +{ + if (!swapchain || !out_pixels) return E_FAIL; + GBMetalContext *ctx = get_ctx(swapchain->_impl); + if (!ctx || !ctx.backBuffer) return E_FAIL; + + size_t needed = (size_t)ctx.width * (size_t)ctx.height * 4; + if (size < needed) return E_FAIL; + + @autoreleasepool { + [ctx.backBuffer getBytes:out_pixels + bytesPerRow:(size_t)ctx.width * 4 + fromRegion:MTLRegionMake2D(0, 0, ctx.width, ctx.height) + mipmapLevel:0]; + } + return S_OK; +} diff --git a/native/d3d11_metal_bridge_test.c b/native/d3d11_metal_bridge_test.c new file mode 100644 index 0000000..5c55c14 --- /dev/null +++ b/native/d3d11_metal_bridge_test.c @@ -0,0 +1,187 @@ +/* + * native/d3d11_metal_bridge_test.c — D3D11→Metal bridge native driver test + * + * F-NSWINDOW-E5 r11: Proves the D3D11-interface-shaped Metal bridge works by + * calling it AS A D3D11 APPLICATION WOULD — through vtable function pointers + * in C COM style: obj->lpVtbl->Method(obj, args...). + * + * own1: gamebox's own bridge; no Wine, no real d3d11.dll, no game asset. + * validated_manjeom stays 0 — the bridge is proven by this native test, but not + * yet driven through the i386 interpreter (that is r12). + * Label: 구현됨·미배선 (dead-until-wired to PE via interpreter, r12 target). + * + * Test flow (mirrors a real D3D11 app startup): + * 1. gamebox_D3D11CreateDeviceAndSwapChain(64, 64, &sc, &dev, &ctx) + * 2. sc->lpVtbl->GetBuffer(sc, 0, NULL, &tex) — get back buffer + * 3. dev->lpVtbl->CreateRenderTargetView(dev, tex, NULL, &rtv) — wrap as RTV + * 4. ctx->lpVtbl->OMSetRenderTargets(ctx, 1, &rtv, NULL) — bind RTV + * 5. ctx->lpVtbl->ClearRenderTargetView(ctx, rtv, blue) — clear to blue + * 6. ctx->lpVtbl->Draw(ctx, 3, 0) — red triangle + * 7. sc->lpVtbl->Present(sc, 0, 0) — "present" + * 8. gamebox_d3d11_readback(sc, pixels, size) — read pixels + * 9. Pixel assertions: clear region = 0x0000FFFF (blue), triangle = 0xFF0000FF (red) + * + * Output protocol: + * __D3D11_BRIDGE__ SKIP reason=no_device (exit 0 — no MTLDevice) + * __D3D11_BRIDGE__ PASS px_clear= px_triangle= nonuniform=1 (exit 0) + * __D3D11_BRIDGE__ FAIL reason=... (exit 1) + * + * Build + run: + * SDK="$(xcrun --sdk macosx --show-sdk-path)" + * clang -arch arm64 -isysroot "$SDK" -fobjc-arc -O2 -Wall -Wextra \ + * -framework Metal -framework Foundation \ + * -o /tmp/d3d11_bridge_test \ + * native/d3d11_metal_bridge_test.c native/d3d11_metal_bridge.m \ + * && /tmp/d3d11_bridge_test + */ + +#include "d3d11_metal_bridge.h" +#include +#include +#include +#include + +/* ── Pixel helper: BGRA8 buffer → 0xRRGGBBAA (same as metal_offscreen_smoke.m) ── */ +static uint32_t pixel_at(const uint8_t *buf, int x, int y, int w) { + const uint8_t *p = buf + ((size_t)y * (size_t)w + (size_t)x) * 4; + uint8_t b = p[0], g = p[1], r = p[2], a = p[3]; + return ((uint32_t)r << 24) | ((uint32_t)g << 16) | ((uint32_t)b << 8) | (uint32_t)a; +} + +int main(void) { + + /* ── Step 1: CreateDeviceAndSwapChain ── */ + IDXGISwapChain *sc = NULL; + ID3D11Device *dev = NULL; + ID3D11DeviceContext*dxc = NULL; + + HRESULT hr = gamebox_D3D11CreateDeviceAndSwapChain( + GB_D3D_TEX_W, GB_D3D_TEX_H, + &sc, &dev, &dxc); + + if (FAILED(hr)) { + /* + * E_FAIL from gamebox_D3D11CreateDeviceAndSwapChain means + * MTLCreateSystemDefaultDevice() returned nil — no Metal GPU. + * Honest ground-truth: SKIP, not a test failure. + */ + printf("__D3D11_BRIDGE__ SKIP reason=no_device\n"); + fflush(stdout); + return 0; + } + + if (!sc || !dev || !dxc) { + printf("__D3D11_BRIDGE__ FAIL reason=null_com_objects\n"); + fflush(stdout); + return 1; + } + + /* ── Step 2: GetBuffer(0) → ID3D11Texture2D back buffer ── */ + ID3D11Texture2D *tex = NULL; + hr = sc->lpVtbl->GetBuffer(sc, 0, NULL, (void **)&tex); + if (FAILED(hr) || !tex) { + printf("__D3D11_BRIDGE__ FAIL reason=GetBuffer hr=0x%08X\n", (unsigned)hr); + fflush(stdout); + return 1; + } + + /* ── Step 3: CreateRenderTargetView ── */ + ID3D11RenderTargetView *rtv = NULL; + hr = dev->lpVtbl->CreateRenderTargetView(dev, tex, NULL, &rtv); + if (FAILED(hr) || !rtv) { + printf("__D3D11_BRIDGE__ FAIL reason=CreateRenderTargetView hr=0x%08X\n", + (unsigned)hr); + fflush(stdout); + return 1; + } + + /* ── Step 4: OMSetRenderTargets ── */ + dxc->lpVtbl->OMSetRenderTargets(dxc, 1, &rtv, NULL); + + /* ── Step 5: ClearRenderTargetView — blue (RGBA 0,0,1,1) ── */ + FLOAT blue[4] = {0.0f, 0.0f, 1.0f, 1.0f}; + dxc->lpVtbl->ClearRenderTargetView(dxc, rtv, blue); + + /* ── Step 6: Draw — red triangle (3 vertices, start=0) ── */ + dxc->lpVtbl->Draw(dxc, 3, 0); + + /* ── Step 7: Present ── */ + hr = sc->lpVtbl->Present(sc, 0, 0); + if (FAILED(hr)) { + printf("__D3D11_BRIDGE__ FAIL reason=Present hr=0x%08X\n", (unsigned)hr); + fflush(stdout); + return 1; + } + + /* ── Step 8: Readback ── */ + size_t bufsize = (size_t)GB_D3D_TEX_W * (size_t)GB_D3D_TEX_H * 4; + uint8_t *pixels = (uint8_t *)malloc(bufsize); + if (!pixels) { + printf("__D3D11_BRIDGE__ FAIL reason=readback_malloc\n"); + fflush(stdout); + return 1; + } + + hr = gamebox_d3d11_readback(sc, pixels, bufsize); + if (FAILED(hr)) { + free(pixels); + printf("__D3D11_BRIDGE__ FAIL reason=readback hr=0x%08X\n", (unsigned)hr); + fflush(stdout); + return 1; + } + + /* ── Step 9: Pixel assertions ── + * + * Triangle vertices at NDC: (-0.5,0.5), (0.5,0.5), (-0.5,-0.5). + * In 64×64 texture (Metal origin = top-left, Y-down): + * pixel_x = (ndc_x + 1) / 2 * 64 + * pixel_y = (1 - ndc_y) / 2 * 64 + * + * Safe triangle interior sample (same as metal_offscreen_smoke.m): + * tri_x = TEX_W/4 = 16, tri_y = TEX_H/4 = 16 + * (sits at / near the top-left vertex, within the filled region) + * + * Clear region (definitely outside triangle): + * clr_x = TEX_W-1 = 63, clr_y = TEX_H-1 = 63 (bottom-right corner) + * + * Expected: + * px_clear = 0x0000FFFF (blue: BGRA8=[0xFF,0x00,0x00,0xFF] → RRGGBBAA) + * px_triangle = 0xFF0000FF (red: BGRA8=[0x00,0x00,0xFF,0xFF] → RRGGBBAA) + * nonuniform = 1 (two distinct colors → real render happened) + */ + int tri_x = GB_D3D_TEX_W / 4; /* 16 — inside triangle */ + int tri_y = GB_D3D_TEX_H / 4; /* 16 */ + int clr_x = GB_D3D_TEX_W - 1; /* 63 — clear region */ + int clr_y = GB_D3D_TEX_H - 1; /* 63 */ + + uint32_t px_tri = pixel_at(pixels, tri_x, tri_y, GB_D3D_TEX_W); + uint32_t px_clr = pixel_at(pixels, clr_x, clr_y, GB_D3D_TEX_W); + free(pixels); + + int clr_ok = (px_clr == 0x0000FFFFu); + int tri_ok = (px_tri == 0xFF0000FFu); + int nonuniform = (px_tri != px_clr); + + if (!clr_ok || !tri_ok || !nonuniform) { + printf("__D3D11_BRIDGE__ FAIL reason=pixel_assertion" + " px_clear=0x%08X(expected=0x0000FFFF)" + " px_triangle=0x%08X(expected=0xFF0000FF)" + " nonuniform=%d\n", + px_clr, px_tri, nonuniform); + fflush(stdout); + return 1; + } + + /* ── Release COM objects (in reverse order) ── */ + rtv->lpVtbl->Release(rtv); + tex->lpVtbl->Release(tex); + dxc->lpVtbl->Release(dxc); + dev->lpVtbl->Release(dev); + sc->lpVtbl->Release(sc); + + printf("__D3D11_BRIDGE__ PASS" + " px_clear=0x%08X px_triangle=0x%08X nonuniform=1\n", + px_clr, px_tri); + fflush(stdout); + return 0; +}