Skip to content

feat(d3d11): expose IDXGISurface on 2D texture resources#171

Open
WIndFate wants to merge 1 commit into
3Shain:mainfrom
WIndFate:feat/texture-idxgisurface
Open

feat(d3d11): expose IDXGISurface on 2D texture resources#171
WIndFate wants to merge 1 commit into
3Shain:mainfrom
WIndFate:feat/texture-idxgisurface

Conversation

@WIndFate

Copy link
Copy Markdown

Summary

DXMT's 2D texture resources don't answer QueryInterface(IDXGISurface). When Wine's Direct2D creates a hardware HwndRenderTarget, it creates a backing D3D texture and queries it for IDXGISurface; without it, CreateHwndRenderTarget fails with E_NOINTERFACE.

This adds an aggregated IDXGISurface facet on TResourceBase for 2D textures, mirroring the existing MTLDXGIResource aggregation. It forwards to the owning resource:

  • GetDesc derives a DXGI_SURFACE_DESC from the texture description
  • GetDevice / GetParent / QueryInterface / private-data delegate to the resource
  • GetResource returns the parent resource
  • Map / Unmap / GetDC / ReleaseDC are left E_NOTIMPL, since the current Direct2D GPU path doesn't exercise them

Only 2D textures expose the facet (guarded by if constexpr), matching where Direct2D queries it.

Testing

Verified on macOS with a CrossOver/Wine runtime on an Apple M-series GPU. With this change, ID2D1Factory::CreateHwndRenderTarget succeeds against the DXMT backend (previously E_NOINTERFACE). Full Direct2D rendering additionally needs SwapDeviceContextState, submitted as a separate PR.

DXMT's 2D texture resources do not answer QueryInterface(IDXGISurface).
Wine's Direct2D queries a backing tex2d for IDXGISurface when creating a
hardware HwndRenderTarget, so creation previously failed with E_NOINTERFACE.

Add an aggregated IDXGISurface facet on TResourceBase for 2D textures,
mirroring the existing MTLDXGIResource aggregation: GetDesc derives a
DXGI_SURFACE_DESC from the texture description; GetDevice/GetParent/
QueryInterface/private-data delegate to the owning resource; GetResource
returns the parent. Map/Unmap/GetDC/ReleaseDC are left E_NOTIMPL because
the current Direct2D GPU path does not exercise them.

With this change ID2D1Factory::CreateHwndRenderTarget succeeds against the
DXMT backend.
@3Shain

3Shain commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Thanks for the PRs. Do you have a program/demo that requires Direct2D functionality?

@WIndFate

Copy link
Copy Markdown
Author

I ran into this with Windows apps that draw their UI with Direct2D under Wine on macOS. On the DXMT backend ID2D1Factory::CreateHwndRenderTarget returned E_NOINTERFACE (the backing texture had no IDXGISurface), and once that was fixed the render loop hit the unimplemented SwapDeviceContextState at the first Clear.

Here's a small self-contained Direct2D demo I used to verify both PRs end to end — it opens a window, creates a hardware HwndRenderTarget, and runs a real BeginDraw → Clear → draw → EndDraw loop. No external dependencies, easy to reproduce:

// Minimal Direct2D demo: opens a window, creates a hardware HwndRenderTarget,
// and runs a BeginDraw -> Clear -> draw -> EndDraw loop for a few seconds.
#include <windows.h>
#include <d2d1.h>
#include <cstdio>

static ID2D1Factory *g_factory = nullptr;
static ID2D1HwndRenderTarget *g_rt = nullptr;
static int g_enddraw_logged = 0;

static void log_hr(const char *name, HRESULT hr) {
  std::printf("%s: 0x%08lx %s\n", name, (unsigned long)hr, SUCCEEDED(hr) ? "OK" : "FAIL");
  std::fflush(stdout);
}

static void create_target(HWND hwnd) {
  RECT rc;
  GetClientRect(hwnd, &rc);
  D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top);
  HRESULT hr = g_factory->CreateHwndRenderTarget(
      D2D1::RenderTargetProperties(),
      D2D1::HwndRenderTargetProperties(hwnd, size), &g_rt);
  log_hr("CreateHwndRenderTarget", hr);
}

static void render() {
  if (!g_rt)
    return;
  g_rt->BeginDraw();
  g_rt->Clear(D2D1::ColorF(0.05f, 0.10f, 0.30f));
  ID2D1SolidColorBrush *brush = nullptr;
  if (SUCCEEDED(g_rt->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Red), &brush)) && brush) {
    g_rt->FillRectangle(D2D1::RectF(40, 40, 200, 140), brush);
    brush->Release(); brush = nullptr;
  }
  if (SUCCEEDED(g_rt->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::LimeGreen), &brush)) && brush) {
    g_rt->DrawRectangle(D2D1::RectF(60, 90, 280, 230), brush, 6.0f);
    brush->Release(); brush = nullptr;
  }
  if (SUCCEEDED(g_rt->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), &brush)) && brush) {
    g_rt->DrawLine(D2D1::Point2F(0, 0), D2D1::Point2F(380, 260), brush, 3.0f);
    g_rt->DrawLine(D2D1::Point2F(380, 0), D2D1::Point2F(0, 260), brush, 3.0f);
    g_rt->FillEllipse(D2D1::Ellipse(D2D1::Point2F(300, 80), 40, 40), brush);
    brush->Release(); brush = nullptr;
  }
  HRESULT hr = g_rt->EndDraw();
  if (!g_enddraw_logged) { log_hr("EndDraw(first frame)", hr); g_enddraw_logged = 1; }
}

static LRESULT CALLBACK WndProc(HWND h, UINT m, WPARAM w, LPARAM l) {
  switch (m) {
  case WM_PAINT: render(); ValidateRect(h, nullptr); return 0;
  case WM_DESTROY: PostQuitMessage(0); return 0;
  }
  return DefWindowProcW(h, m, w, l);
}

int main() {
  HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &g_factory);
  log_hr("D2D1CreateFactory", hr);
  if (FAILED(hr)) return 1;
  HINSTANCE inst = GetModuleHandleW(nullptr);
  WNDCLASSW wc = {};
  wc.lpfnWndProc = WndProc; wc.hInstance = inst; wc.lpszClassName = L"D2DDemo";
  wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
  RegisterClassW(&wc);
  HWND hwnd = CreateWindowExW(0, L"D2DDemo", L"Direct2D DXMT demo",
      WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
      nullptr, nullptr, inst, nullptr);
  if (!hwnd) { std::printf("CreateWindowExW failed\n"); return 1; }
  create_target(hwnd);
  ShowWindow(hwnd, SW_SHOW); UpdateWindow(hwnd);
  DWORD start = GetTickCount(); int frames = 0; MSG msg; bool quit = false;
  while (!quit) {
    while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
      if (msg.message == WM_QUIT) { quit = true; break; }
      TranslateMessage(&msg); DispatchMessageW(&msg);
    }
    render(); frames++; Sleep(50);
    if (GetTickCount() - start > 8000) break;
  }
  std::printf("frames=%d\ndemo finished\n", frames);
  if (g_rt) g_rt->Release();
  if (g_factory) g_factory->Release();
  return 0;
}

Build (mingw-w64):

x86_64-w64-mingw32-g++ -std=c++17 -O2 -static-libgcc -static-libstdc++ \
  d2d_demo.cpp -o d2d_demo.exe -ld2d1 -lole32 -luuid -luser32 -lgdi32

Behavior on the DXMT backend:

  • Without the patches: CreateHwndRenderTarget fails with E_NOINTERFACE; with only the IDXGISurface change applied, the first Clear aborts at SwapDeviceContextState.
  • With both PRs: CreateHwndRenderTarget and EndDraw(first frame) both return OK, the loop runs to frames=~150, and the window shows the colored shapes.

Tested on macOS (Apple M-series GPU) with a CrossOver/Wine runtime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants