bsnes/ruby/video/direct3d.cpp
Tim Allen 2b8df2e70e Update to v106r27 release.
byuu says:

Changelog:

  - nall: merged Path::config() and Path::local() to Path::userData()
      - ~/.local/share or %appdata or ~/Library/ApplicationSupport
  - higan, bsnes: render main window icon onto viewport instead of
    canvas
      - should hopefully fix a brief flickering glitch that appears on
        Windows
  - icarus: improved Super Famicom heuristics for Starfox / Starwing RAM
  - ruby/Direct3D: handle viewport size changes in lock() instead of
    output()
      - fixes icon disappearing when resizing main window
  - hiro/Windows: remove WS_DISABLED from StatusBar to fix window
    resize grip
      - this is experimental: I initially used WS_DISABLED to work
        around a focus bug
      - yet trying things now, said bug seems(?) to have gone away at
        some point ...
  - bsnes: added advanced settings panel with real-time driver change
    support

I'd like feedback on the real-time driver change, for possible
consideration into adding this to higan as well.

Some drivers just crash, it's a fact of life. The ASIO driver in
particular likes to crash inside the driver itself, without any error
messages ever returned to try and catch.

When you try to change a driver with a game loaded, it gives you a scary
warning, asking if you want to proceed.

When you change a driver, it sets a crash flag, and if the driver
crashes while initializing, then restarting bsnes will disable the
errant driver. If it fails in a recoverable way, then it sets the driver
to “None” and warns you that the driver cannot be used.

What I'm thinking of further adding is to call emulator→save() to
write out the save RAM contents beforehand (although the periodic
auto-saving RAM will handle this anyway when it's enabled), and possibly
it might be wise to capture an emulator save state, although those can't
be taken without advancing the emulator to the next frame, so that might
not be a good idea.

I'm also thinking we should show some kind of message somewhere when a
driver is set to “None”. The status bar can be hidden, so perhaps on the
title bar? Or maybe just a warning on startup that a driver is set to
“None”.
2018-05-25 18:02:38 +10:00

361 lines
12 KiB
C++

#undef interface
#define interface struct
#include <d3d9.h>
#undef interface
static LRESULT CALLBACK VideoDirect3D_WindowProcedure(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
return DefWindowProc(hwnd, msg, wparam, lparam);
}
struct VideoDirect3D : Video {
VideoDirect3D() { initialize(); }
~VideoDirect3D() { terminate(); }
auto ready() -> bool { return _ready; }
auto exclusive() -> bool { return _exclusive; }
auto context() -> uintptr { return _context; }
auto blocking() -> bool { return _blocking; }
auto smooth() -> bool { return _smooth; }
auto setExclusive(bool exclusive) -> bool {
if(_exclusive == exclusive) return true;
_exclusive = exclusive;
return initialize();
}
auto setContext(uintptr context) -> bool {
if(_context == context) return true;
_context = context;
return initialize();
}
auto setBlocking(bool blocking) -> bool {
_blocking = blocking;
return true;
}
auto setSmooth(bool smooth) -> bool {
_smooth = smooth;
if(_ready) updateFilter();
return true;
}
auto clear() -> void {
if(!ready()) return;
if(_lost && !recover()) return;
D3DSURFACE_DESC surfaceDescription;
_texture->GetLevelDesc(0, &surfaceDescription);
_texture->GetSurfaceLevel(0, &_surface);
if(_surface) {
_device->ColorFill(_surface, 0, D3DCOLOR_XRGB(0x00, 0x00, 0x00));
_surface->Release();
_surface = nullptr;
}
//clear primary display and all backbuffers
for(uint n : range(3)) {
_device->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0x00, 0x00, 0x00), 1.0f, 0);
_device->Present(0, 0, 0, 0);
}
}
auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool {
if(!ready()) return false;
if(_lost && !recover()) return false;
//if output size changed, driver must be re-initialized.
//failure to do so causes scaling issues on some video drivers.
RECT rectangle;
GetClientRect((HWND)_context, &rectangle);
if(_windowWidth != rectangle.right || _windowHeight != rectangle.bottom) initialize();
if(width != _inputWidth || height != _inputHeight) {
resize(_inputWidth = width, _inputHeight = height);
}
D3DSURFACE_DESC surfaceDescription;
_texture->GetLevelDesc(0, &surfaceDescription);
_texture->GetSurfaceLevel(0, &_surface);
D3DLOCKED_RECT lockedRectangle;
_surface->LockRect(&lockedRectangle, 0, D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD);
pitch = lockedRectangle.Pitch;
return data = (uint32_t*)lockedRectangle.pBits;
}
auto unlock() -> void {
if(!ready()) return;
_surface->UnlockRect();
_surface->Release();
_surface = nullptr;
}
auto output() -> void {
if(!ready()) return;
if(_lost && !recover()) return;
_device->BeginScene();
uint x = 0, y = 0;
if(_exclusive) {
//center output in exclusive mode fullscreen window
x = (_monitorWidth - _windowWidth) / 2;
y = (_monitorHeight - _windowHeight) / 2;
}
setVertex(0, 0, _inputWidth, _inputHeight, _textureWidth, _textureHeight, x, y, _windowWidth, _windowHeight);
_device->SetTexture(0, _texture);
_device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
_device->EndScene();
if(_blocking) {
D3DRASTER_STATUS status;
while(true) { //wait for a previous vblank to finish, if necessary
_device->GetRasterStatus(0, &status);
if(!status.InVBlank) break;
}
while(true) { //wait for next vblank to begin
_device->GetRasterStatus(0, &status);
if(status.InVBlank) break;
}
}
if(_device->Present(0, 0, 0, 0) == D3DERR_DEVICELOST) _lost = true;
}
private:
auto recover() -> bool {
if(!_device) return false;
if(_lost) {
if(_vertexBuffer) { _vertexBuffer->Release(); _vertexBuffer = nullptr; }
if(_surface) { _surface->Release(); _surface = nullptr; }
if(_texture) { _texture->Release(); _texture = nullptr; }
if(_device->Reset(&_presentation) != D3D_OK) return false;
}
_lost = false;
_device->SetDialogBoxMode(false);
_device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
_device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
_device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
_device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
_device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
_device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
_device->SetRenderState(D3DRS_LIGHTING, false);
_device->SetRenderState(D3DRS_ZENABLE, false);
_device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
_device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
_device->SetVertexShader(nullptr);
_device->SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1);
_device->CreateVertexBuffer(sizeof(Vertex) * 4, _vertexUsage, D3DFVF_XYZRHW | D3DFVF_TEX1,
(D3DPOOL)_vertexPool, &_vertexBuffer, nullptr);
_textureWidth = 0;
_textureHeight = 0;
resize(_inputWidth = 256, _inputHeight = 256);
updateFilter();
clear();
return true;
}
auto resize(uint width, uint height) -> void {
if(_textureWidth >= width && _textureHeight >= height) return;
_textureWidth = bit::round(max(width, _textureWidth));
_textureHeight = bit::round(max(height, _textureHeight));
if(_capabilities.MaxTextureWidth < _textureWidth || _capabilities.MaxTextureWidth < _textureHeight) return;
if(_texture) _texture->Release();
_device->CreateTexture(_textureWidth, _textureHeight, 1, _textureUsage, D3DFMT_X8R8G8B8,
(D3DPOOL)_texturePool, &_texture, nullptr);
}
auto updateFilter() -> void {
if(!_device) return;
if(_lost && !recover()) return;
auto filter = !_smooth ? D3DTEXF_POINT : D3DTEXF_LINEAR;
_device->SetSamplerState(0, D3DSAMP_MINFILTER, filter);
_device->SetSamplerState(0, D3DSAMP_MAGFILTER, filter);
}
//(x,y) screen coordinates, in pixels
//(u,v) texture coordinates, betweeen 0.0 (top, left) to 1.0 (bottom, right)
auto setVertex(
uint32_t px, uint32_t py, uint32_t pw, uint32_t ph,
uint32_t tw, uint32_t th,
uint32_t x, uint32_t y, uint32_t w, uint32_t h
) -> void {
Vertex vertex[4];
vertex[0].x = vertex[2].x = (double)(x - 0.5);
vertex[1].x = vertex[3].x = (double)(x + w - 0.5);
vertex[0].y = vertex[1].y = (double)(y - 0.5);
vertex[2].y = vertex[3].y = (double)(y + h - 0.5);
//Z-buffer and RHW are unused for 2D blit, set to normal values
vertex[0].z = vertex[1].z = vertex[2].z = vertex[3].z = 0.0;
vertex[0].rhw = vertex[1].rhw = vertex[2].rhw = vertex[3].rhw = 1.0;
double rw = (double)w / (double)pw * (double)tw;
double rh = (double)h / (double)ph * (double)th;
vertex[0].u = vertex[2].u = (double)(px ) / rw;
vertex[1].u = vertex[3].u = (double)(px + w) / rw;
vertex[0].v = vertex[1].v = (double)(py ) / rh;
vertex[2].v = vertex[3].v = (double)(py + h) / rh;
LPDIRECT3DVERTEXBUFFER9* vertexPointer = nullptr;
_vertexBuffer->Lock(0, sizeof(Vertex) * 4, (void**)&vertexPointer, 0);
memory::copy(vertexPointer, vertex, sizeof(Vertex) * 4);
_vertexBuffer->Unlock();
_device->SetStreamSource(0, _vertexBuffer, 0, sizeof(Vertex));
}
auto initialize() -> bool {
terminate();
if(!_context) return false;
HMONITOR monitor = MonitorFromWindow((HWND)_context, MONITOR_DEFAULTTOPRIMARY);
MONITORINFOEX information = {};
information.cbSize = sizeof(MONITORINFOEX);
GetMonitorInfo(monitor, &information);
_monitorWidth = information.rcMonitor.right - information.rcMonitor.left;
_monitorHeight = information.rcMonitor.bottom - information.rcMonitor.top;
WNDCLASS windowClass = {};
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = 0;
windowClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
windowClass.hCursor = LoadCursor(0, IDC_ARROW);
windowClass.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
windowClass.hInstance = GetModuleHandle(0);
windowClass.lpfnWndProc = VideoDirect3D_WindowProcedure;
windowClass.lpszClassName = L"VideoDirect3D_Window";
windowClass.lpszMenuName = 0;
windowClass.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&windowClass);
_exclusiveContext = (uintptr)CreateWindow(L"VideoDirect3D_Window", L"", WS_POPUP,
information.rcMonitor.left, information.rcMonitor.top, _monitorWidth, _monitorHeight,
nullptr, nullptr, GetModuleHandle(0), nullptr);
RECT rectangle;
GetClientRect((HWND)_context, &rectangle);
_windowWidth = rectangle.right;
_windowHeight = rectangle.bottom;
_instance = Direct3DCreate9(D3D_SDK_VERSION);
if(!_instance) return false;
memory::fill(&_presentation, sizeof(_presentation));
_presentation.Flags = D3DPRESENTFLAG_VIDEO;
_presentation.SwapEffect = D3DSWAPEFFECT_DISCARD;
_presentation.BackBufferCount = 1;
_presentation.MultiSampleType = D3DMULTISAMPLE_NONE;
_presentation.MultiSampleQuality = 0;
_presentation.EnableAutoDepthStencil = false;
_presentation.AutoDepthStencilFormat = D3DFMT_UNKNOWN;
_presentation.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
if(!_exclusive) {
_presentation.hDeviceWindow = (HWND)_context;
_presentation.Windowed = true;
_presentation.BackBufferFormat = D3DFMT_UNKNOWN;
_presentation.BackBufferWidth = 0;
_presentation.BackBufferHeight = 0;
ShowWindow((HWND)_exclusiveContext, SW_HIDE);
if(_instance->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, (HWND)_context,
D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &_presentation, &_device) != D3D_OK) {
return false;
}
} else {
_presentation.hDeviceWindow = (HWND)_exclusiveContext;
_presentation.Windowed = false;
_presentation.BackBufferFormat = D3DFMT_X8R8G8B8;
_presentation.BackBufferWidth = _monitorWidth;
_presentation.BackBufferHeight = _monitorHeight;
_presentation.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
ShowWindow((HWND)_exclusiveContext, SW_SHOWNORMAL);
if(_instance->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, (HWND)_exclusiveContext,
D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &_presentation, &_device) != D3D_OK) {
return false;
}
}
_device->GetDeviceCaps(&_capabilities);
if(_capabilities.Caps2 & D3DCAPS2_DYNAMICTEXTURES) {
_textureUsage = D3DUSAGE_DYNAMIC;
_texturePool = D3DPOOL_DEFAULT;
_vertexUsage = D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC;
_vertexPool = D3DPOOL_DEFAULT;
} else {
_textureUsage = 0;
_texturePool = D3DPOOL_MANAGED;
_vertexUsage = D3DUSAGE_WRITEONLY;
_vertexPool = D3DPOOL_MANAGED;
}
_lost = false;
return _ready = recover();
}
auto terminate() -> void {
_ready = false;
if(_vertexBuffer) { _vertexBuffer->Release(); _vertexBuffer = nullptr; }
if(_surface) { _surface->Release(); _surface = nullptr; }
if(_texture) { _texture->Release(); _texture = nullptr; }
if(_device) { _device->Release(); _device = nullptr; }
if(_instance) { _instance->Release(); _instance = nullptr; }
if(_exclusiveContext) { DestroyWindow((HWND)_exclusiveContext); _exclusiveContext = 0; }
}
struct Vertex {
float x, y, z, rhw; //screen coordinates
float u, v; //texture coordinates
};
bool _exclusive = false;
bool _ready = false;
uintptr _context = 0;
bool _blocking = false;
bool _smooth = true;
uintptr _exclusiveContext = 0;
LPDIRECT3D9 _instance = nullptr;
LPDIRECT3DDEVICE9 _device = nullptr;
LPDIRECT3DVERTEXBUFFER9 _vertexBuffer = nullptr;
D3DPRESENT_PARAMETERS _presentation = {};
D3DCAPS9 _capabilities = {};
LPDIRECT3DTEXTURE9 _texture = nullptr;
LPDIRECT3DSURFACE9 _surface = nullptr;
bool _lost = true;
uint _windowWidth;
uint _windowHeight;
uint _textureWidth;
uint _textureHeight;
uint _monitorWidth;
uint _monitorHeight;
uint _inputWidth;
uint _inputHeight;
uint32_t _textureUsage;
uint32_t _texturePool;
uint32_t _vertexUsage;
uint32_t _vertexPool;
};