rodecomp/Source/Render/DeviceOwnerDX7.cpp
2026-10-24 00:35:19 +00:00

585 lines
17 KiB
C++

#include "Render/DeviceOwnerDX7.h"
#include <cstring>
namespace {
HRESULT DeviceError(unsigned code) {
return static_cast<HRESULT>(0x82000000u | code);
}
// OFFSET: 0x00548C00
// FUNCTION: fn_01862
DDSURFACEDESC2 SurfaceDescription(DWORD flags, DWORD caps) {
DDSURFACEDESC2 result{};
result.dwSize = sizeof(result);
result.dwFlags = flags;
result.ddsCaps.dwCaps = caps;
result.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
return result;
}
// OFFSET: 0x0054AA50
// FUNCTION: fn_01900
RECT ClientRectangle(HWND window, const RECT& monitor) {
RECT result;
GetClientRect(window, &result);
POINT point{result.left, result.top};
ClientToScreen(window, &point);
result.left = point.x - monitor.left;
result.top = point.y - monitor.top;
point = {result.right, result.bottom};
ClientToScreen(window, &point);
result.right = point.x - monitor.left;
result.bottom = point.y - monitor.top;
return result;
}
// OFFSET: 0x0054A970
// FUNCTION: fn_01899
HRESULT CALLBACK SelectDepthFormat(DDPIXELFORMAT* candidate, void* context) {
auto* selected = static_cast<DDPIXELFORMAT*>(context);
if (!candidate || !selected) {
return D3DENUMRET_CANCEL;
}
if (candidate->dwFlags != selected->dwFlags || candidate->dwZBufferBitDepth <= selected->dwZBufferBitDepth) {
return D3DENUMRET_OK;
}
*selected = *candidate;
return candidate->dwZBufferBitDepth < 32 ? D3DENUMRET_OK : D3DENUMRET_CANCEL;
}
void ChannelMask(DeviceOwnerDX7::Channel& channel, DWORD mask) {
channel = {0, 8};
if (!mask) {
return;
}
while (!(mask & 1)) {
++channel.shift;
mask >>= 1;
}
do {
--channel.expansion;
mask >>= 1;
} while (mask);
}
HRESULT SurfaceError(HRESULT status, unsigned code) {
return status == DDERR_OUTOFVIDEOMEMORY ? status : DeviceError(code);
}
}
// OFFSET: 0x005493B0
// FUNCTION: fn_01888
DeviceOwnerDX7::DeviceOwnerDX7()
: window(nullptr), width(0), height(0), primary(nullptr), backBuffer(nullptr), renderTarget(nullptr),
depthBuffer(nullptr), draw(nullptr), direct3D(nullptr), device(nullptr), depthMemory(0), fullscreen(0),
presentationOption(0), captureSurface(nullptr), red{0, 8}, green{0, 8}, blue{0, 8}, alpha{0, 8}, bitDepth(0),
redMask(0), greenMask(0), blueMask(0), alphaMask(0), surfacesLost(false) {
}
// OFFSET: 0x005498A0
// FUNCTION: fn_01894
HRESULT DeviceOwnerDX7::CreateSurfaces(const DDSURFACEDESC2* mode, DWORD flags, const RECT& monitor) {
const bool buffered = (flags & 2) != 0;
DDSURFACEDESC2 description = SurfaceDescription(DDSD_CAPS, 0);
HRESULT status;
if (flags & 1) {
SetRect(&this->source, 0, 0, mode->dwWidth, mode->dwHeight);
this->width = this->source.right;
this->height = this->source.bottom;
this->destination = this->source;
const DWORD modeFlags =
this->width == 320 && this->height == 200 && mode->ddpfPixelFormat.dwRGBBitCount == 8 ? DDSDM_STANDARDVGAMODE : 0;
if (
FAILED(this->draw->SetDisplayMode(
this->width, this->height, mode->ddpfPixelFormat.dwRGBBitCount, mode->dwRefreshRate, modeFlags
))
) {
return DeviceError(9);
}
description.ddsCaps.dwCaps = 0x2200;
if (buffered) {
description.dwFlags |= DDSD_BACKBUFFERCOUNT;
description.ddsCaps.dwCaps = 0x2218;
description.dwBackBufferCount = 1;
}
status = this->draw->CreateSurface(&description, &this->primary, nullptr);
if (FAILED(status)) {
return SurfaceError(status, 7);
}
if (buffered) {
DDSCAPS2 caps{DDSCAPS_BACKBUFFER};
status = this->primary->GetAttachedSurface(&caps, &this->backBuffer);
if (FAILED(status)) {
return SurfaceError(status, 10);
}
}
} else {
GetClientRect(this->window, &this->source);
this->width = this->source.right;
this->height = this->source.bottom;
this->destination = ClientRectangle(this->window, monitor);
description.ddsCaps.dwCaps = buffered ? 0x200 : 0x2200;
status = this->draw->CreateSurface(&description, &this->primary, nullptr);
if (FAILED(status)) {
return SurfaceError(status, 7);
}
}
IDirectDrawClipper* clipper;
if (FAILED(this->draw->CreateClipper(0, &clipper, nullptr))) {
return DeviceError(8);
}
clipper->SetHWnd(0, this->window);
this->primary->SetClipper(clipper);
if (clipper) {
clipper->Release();
}
if (!(flags & 1)) {
if (buffered) {
description.dwWidth = this->width;
description.dwHeight = this->height;
description.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
description.ddsCaps.dwCaps = 0x2040;
status = this->draw->CreateSurface(&description, &this->backBuffer, nullptr);
if (FAILED(status)) {
return SurfaceError(status, 10);
}
} else {
ClientToScreen(this->window, reinterpret_cast<POINT*>(&this->source.left));
ClientToScreen(this->window, reinterpret_cast<POINT*>(&this->source.right));
}
}
this->primaryDescription = SurfaceDescription(DDSD_CAPS, 0);
this->primary->GetSurfaceDesc(&this->primaryDescription);
const auto& format = this->primaryDescription.ddpfPixelFormat;
this->bitDepth = format.dwRGBBitCount;
this->redMask = format.dwRBitMask;
this->greenMask = format.dwGBitMask;
this->blueMask = format.dwBBitMask;
this->alphaMask = format.dwRGBAlphaBitMask;
ChannelMask(this->red, this->redMask);
ChannelMask(this->green, this->greenMask);
ChannelMask(this->blue, this->blueMask);
ChannelMask(this->alpha, this->alphaMask);
this->renderTarget = buffered ? this->backBuffer : this->primary;
this->renderTarget->AddRef();
description = SurfaceDescription(DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT, 0x840);
description.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
description.dwWidth = this->width;
description.dwHeight = this->height;
status = this->draw->CreateSurface(&description, &this->captureSurface, nullptr);
return FAILED(status) ? E_OUTOFMEMORY : S_OK;
}
// OFFSET: 0x0054A000
// FUNCTION: fn_01895
HRESULT DeviceOwnerDX7::CreateDevice(
GUID* drawIdentity,
const GUID* renderIdentity,
const DDSURFACEDESC2* mode,
DWORD flags,
const RECT& monitor
) {
this->depthMemory = std::memcmp(renderIdentity, &IID_IDirect3DHALDevice, sizeof(GUID)) == 0 ||
std::memcmp(renderIdentity, &IID_IDirect3DTnLHalDevice, sizeof(GUID)) == 0
? DDSCAPS_VIDEOMEMORY
: DDSCAPS_SYSTEMMEMORY;
if (FAILED(DirectDrawCreateEx(drawIdentity, reinterpret_cast<void**>(&this->draw), IID_IDirectDraw7, nullptr))) {
return DeviceError(1);
}
auto description = SurfaceDescription(0, 0);
this->draw->GetDisplayMode(&description);
this->desktopBitDepth = description.ddpfPixelFormat.dwRGBBitCount;
DWORD cooperative = this->fullscreen ? 19 : 8;
if (!(flags & 16)) {
cooperative |= 0x800;
}
if (FAILED(this->draw->SetCooperativeLevel(this->window, cooperative | 0x400))) {
return DeviceError(2);
}
const HRESULT status = this->CreateSurfaces(mode, flags, monitor);
if (FAILED(status)) {
return status;
}
if (renderIdentity) {
if (FAILED(this->draw->QueryInterface(IID_IDirect3D7, reinterpret_cast<void**>(&this->direct3D)))) {
return DeviceError(3);
}
if (FAILED(this->direct3D->CreateDevice(*renderIdentity, this->renderTarget, &this->device))) {
return DeviceError(4);
}
D3DVIEWPORT7 viewport{0, 0, this->width, this->height, 0.0f, 1.0f};
if (FAILED(this->device->SetViewport(&viewport))) {
return DeviceError(6);
}
this->depthFormat = {};
this->depthFormat.dwFlags = ((flags & 8) << 11) | DDPF_ZBUFFER;
this->direct3D->EnumZBufferFormats(*renderIdentity, SelectDepthFormat, &this->depthFormat);
// The original tests the descriptor size, not its depth-bit count.
if (this->depthFormat.dwSize != 32) {
return DeviceError(5);
}
this->device->GetCaps(&this->caps);
if (!(this->caps.dpcTriCaps.dwRasterCaps & D3DPRASTERCAPS_ZBUFFERLESSHSR)) {
description = SurfaceDescription(
DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT, this->depthMemory | DDSCAPS_ZBUFFER
);
description.dwWidth = this->width;
description.dwHeight = this->height;
description.ddpfPixelFormat = this->depthFormat;
const HRESULT depthStatus = this->draw->CreateSurface(&description, &this->depthBuffer, nullptr);
if (FAILED(depthStatus)) {
return SurfaceError(depthStatus, 5);
}
if (
FAILED(this->renderTarget->AddAttachedSurface(this->depthBuffer)) ||
FAILED(this->device->SetRenderTarget(this->renderTarget, 0))
) {
return DeviceError(5);
}
}
}
return S_OK;
}
// OFFSET: 0x0054A7D0
// FUNCTION: fn_01897
HRESULT DeviceOwnerDX7::Close() {
ULONG deviceReferences = 0;
if (this->device) {
deviceReferences = this->device->Release();
if (static_cast<std::int32_t>(deviceReferences) > 0) {
do {
deviceReferences = this->device->Release();
} while (deviceReferences);
}
}
this->device = nullptr;
if (!this->fullscreen && this->backBuffer) {
this->backBuffer->Release();
this->backBuffer = nullptr;
}
if (this->depthBuffer) {
this->depthBuffer->Release();
this->depthBuffer = nullptr;
}
if (this->renderTarget) {
this->renderTarget->Release();
this->renderTarget = nullptr;
}
if (this->primary) {
this->primary->Release();
this->primary = nullptr;
}
if (this->direct3D) {
this->direct3D->Release();
this->direct3D = nullptr;
}
if (this->captureSurface) {
this->captureSurface->Release();
this->captureSurface = nullptr;
}
ULONG drawReferences = 0;
if (this->draw) {
this->draw->SetCooperativeLevel(this->window, DDSCL_NORMAL);
drawReferences = this->draw->Release();
}
this->draw = nullptr;
return drawReferences || deviceReferences ? DeviceError(11) : S_OK;
}
// OFFSET: 0x0054AAD0
// FUNCTION: fn_01901
HRESULT DeviceOwnerDX7::Initialize(
HWND value,
GUID* drawIdentity,
const GUID* renderIdentity,
const DDSURFACEDESC2* mode,
DWORD flags,
HWND applicationWindow,
const RECT& monitor
) {
this->window = value;
if (!value || !renderIdentity || (!mode && (flags & 1))) {
return E_INVALIDARG;
}
this->fullscreen = flags & 1;
if (this->fullscreen) {
SetWindowLongA(applicationWindow, GWL_STYLE, static_cast<LONG>(WS_POPUP));
ShowWindow(applicationWindow, SW_MAXIMIZE);
}
const HRESULT status = this->CreateDevice(drawIdentity, renderIdentity, mode, flags, monitor);
if (FAILED(status)) {
this->Close();
if (status == E_FAIL) {
return DeviceError(0);
}
}
if (this->device) {
const struct {
D3DRENDERSTATETYPE state;
DWORD value;
} states[] = {
{D3DRENDERSTATE_CULLMODE, 1}, {D3DRENDERSTATE_ZFUNC, 4}, {D3DRENDERSTATE_TEXTUREPERSPECTIVE, 1},
{D3DRENDERSTATE_ALPHAFUNC, 7}, {D3DRENDERSTATE_ALPHAREF, 240}, {D3DRENDERSTATE_COLORKEYENABLE, 1},
{D3DRENDERSTATE_ZENABLE, 1}, {D3DRENDERSTATE_ZWRITEENABLE, 1}, {D3DRENDERSTATE_SRCBLEND, 5},
{D3DRENDERSTATE_DESTBLEND, 6}
};
for (const auto& state : states) {
this->device->SetRenderState(state.state, state.value);
}
const struct {
DWORD stage;
D3DTEXTURESTAGESTATETYPE state;
DWORD value;
} stages[] = {{0, D3DTSS_TEXCOORDINDEX, 0}, {0, D3DTSS_COLOROP, 4}, {0, D3DTSS_COLORARG1, 2},
{0, D3DTSS_COLORARG2, 0}, {0, D3DTSS_ALPHAOP, 4}, {0, D3DTSS_ALPHAARG1, 2},
{0, D3DTSS_ALPHAARG2, 0}, {1, D3DTSS_TEXCOORDINDEX, 1}, {1, D3DTSS_COLORARG1, 2},
{1, D3DTSS_COLOROP, 18}, {1, D3DTSS_COLORARG2, 1}, {1, D3DTSS_ALPHAOP, 1},
{2, D3DTSS_COLOROP, 1}, {2, D3DTSS_ALPHAOP, 1}, {0, D3DTSS_MAGFILTER, 2},
{0, D3DTSS_MINFILTER, 2}, {1, D3DTSS_MAGFILTER, 2}, {1, D3DTSS_MINFILTER, 2},
{0, D3DTSS_ADDRESS, 3}, {1, D3DTSS_ADDRESS, 4}, {1, D3DTSS_BORDERCOLOR, 0xFF000000}};
for (const auto& state : stages) {
this->device->SetTextureStageState(state.stage, state.state, state.value);
}
}
return status;
}
// OFFSET: 0x005496D0
// FUNCTION: fn_01892
HRESULT DeviceOwnerDX7::Clear(DWORD color) {
D3DRECT rectangle{0, 0, static_cast<LONG>(this->width), static_cast<LONG>(this->height)};
return this->device->Clear(1, &rectangle, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, color, 1.0f, 0);
}
// OFFSET: 0x00549730
// FUNCTION: fn_01893
HRESULT DeviceOwnerDX7::ClearDepth() {
D3DRECT rectangle{0, 0, static_cast<LONG>(this->width), static_cast<LONG>(this->height)};
return this->device->Clear(1, &rectangle, D3DCLEAR_ZBUFFER, 0, 1.0f, 0);
}
// OFFSET: 0x0054AF10
// FUNCTION: fn_01903
void DeviceOwnerDX7::MarkSurfacesLost() {
this->surfacesLost = true;
}
// OFFSET: 0x005495C0
// FUNCTION: fn_01891
bool DeviceOwnerDX7::RestoreLostSurfaces() {
if (!this->surfacesLost) {
return false;
}
const HRESULT primaryStatus = this->primary->IsLost();
const HRESULT depthStatus = this->depthBuffer ? this->depthBuffer->IsLost() : S_OK;
if (primaryStatus == DDERR_SURFACELOST && depthStatus == DDERR_SURFACELOST) {
if (this->depthBuffer) {
this->depthBuffer->Restore();
}
this->draw->RestoreAllSurfaces();
this->surfacesLost = false;
}
return true;
}
// OFFSET: 0x0054AF20
// FUNCTION: fn_01904
HRESULT DeviceOwnerDX7::Present(bool wait, const RECT& monitor) {
if (!this->primary) {
return DeviceError(14);
}
if (!this->backBuffer) {
return this->primary->IsLost();
}
if (wait && (!this->fullscreen || this->presentationOption)) {
this->destination = ClientRectangle(this->window, monitor);
this->draw->WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN, nullptr);
return this->primary->Blt(&this->destination, this->backBuffer, &this->source, DDBLT_WAIT, nullptr);
}
GetClientRect(this->window, &this->destination);
ClientToScreen(this->window, reinterpret_cast<POINT*>(&this->destination.left));
ClientToScreen(this->window, reinterpret_cast<POINT*>(&this->destination.right));
if (wait) {
this->draw->WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN, nullptr);
return this->primary->Blt(nullptr, this->backBuffer, nullptr, DDBLT_WAIT, nullptr);
}
return this->primary->Blt(&this->destination, this->backBuffer, &this->source, DDBLT_WAIT, nullptr);
}
// OFFSET: 0x005494C0
// FUNCTION: fn_01890
void DeviceOwnerDX7::AdjustTextureDimensions(std::uint32_t& width, std::uint32_t& height) const {
std::uint32_t selectedWidth = 1;
while (selectedWidth < width) {
selectedWidth *= 2;
}
if (selectedWidth < this->minTextureWidth) {
selectedWidth = this->minTextureWidth;
}
if (selectedWidth > this->maxTextureWidth) {
selectedWidth = this->maxTextureWidth;
}
std::uint32_t selectedHeight = 1;
while (selectedHeight < height) {
selectedHeight *= 2;
}
if (selectedHeight < this->minTextureHeight) {
selectedHeight = this->minTextureHeight;
}
if (selectedHeight > this->maxTextureHeight) {
selectedHeight = this->maxTextureHeight;
}
const auto aspect = this->maxTextureAspectRatio;
if (aspect) {
if (selectedWidth / selectedHeight > aspect) {
selectedHeight = selectedWidth / aspect;
}
if (selectedHeight / selectedWidth > aspect) {
selectedWidth = selectedHeight / aspect;
}
}
width = selectedWidth;
height = selectedHeight;
}
// OFFSET: 0x0054AE50
// FUNCTION: fn_01902
bool DeviceOwnerDX7::SupportsTextureDimensions(std::uint32_t width, std::uint32_t height) const {
return this->minTextureWidth <= width && width <= this->maxTextureWidth && this->minTextureHeight <= height &&
height <= this->maxTextureHeight;
}
// OFFSET: 0x0056B890
// FUNCTION: fn_02324
void DeviceOwnerDX7::PrepareErrorDialog() {
if (this->direct3D) {
this->direct3D->EvictManagedTextures();
}
}
// OFFSET: 0x0056B8B0
// FUNCTION: fn_02325
void DeviceOwnerDX7::RestoreDisplayMode() {
if (this->draw) {
if (this->backBuffer) {
this->backBuffer->Unlock(nullptr);
}
if (this->primary) {
this->primary->Unlock(nullptr);
}
this->draw->FlipToGDISurface();
}
}
// OFFSET: 0x0056C4B0
// FUNCTION: fn_02336
HRESULT DeviceOwnerDX7::RestoreAllSurfaces() {
return this->draw->RestoreAllSurfaces();
}
// OFFSET: 0x0054A8D0
// FUNCTION: fn_45420
void DeviceOwnerDX7::SetDialogClipper(int enabled) {
if (!this->fullscreen) {
return;
}
IDirectDrawClipper* clipper = nullptr;
if (enabled == 1) {
this->draw->CreateClipper(0, &clipper, nullptr);
clipper->SetHWnd(0, this->window);
}
this->primary->SetClipper(clipper);
if (clipper) {
clipper->Release();
}
}