#undef interface #define interface struct #include #include #undef interface #define D3DVERTEX (D3DFVF_XYZRHW | D3DFVF_TEX1) typedef HRESULT (__stdcall* EffectProc)(LPDIRECT3DDEVICE9, LPCVOID, UINT, D3DXMACRO const*, LPD3DXINCLUDE, DWORD, LPD3DXEFFECTPOOL, LPD3DXEFFECT*, LPD3DXBUFFER*); typedef HRESULT (__stdcall* TextureProc)(LPDIRECT3DDEVICE9, LPCTSTR, LPDIRECT3DTEXTURE9*); struct VideoD3D : Video { ~VideoD3D() { term(); } LPDIRECT3D9 lpd3d = nullptr; LPDIRECT3DDEVICE9 device = nullptr; LPDIRECT3DVERTEXBUFFER9 vertex_buffer = nullptr; LPDIRECT3DVERTEXBUFFER9* vertex_ptr = nullptr; D3DPRESENT_PARAMETERS presentation; D3DSURFACE_DESC d3dsd; D3DLOCKED_RECT d3dlr; D3DRASTER_STATUS d3drs; D3DCAPS9 d3dcaps; LPDIRECT3DTEXTURE9 texture = nullptr; LPDIRECT3DSURFACE9 surface = nullptr; LPD3DXEFFECT effect = nullptr; string shader_source_markup; bool lost = true; unsigned iwidth; unsigned iheight; struct d3dvertex { float x, y, z, rhw; //screen coords float u, v; //texture coords }; struct { uint32_t t_usage, v_usage; uint32_t t_pool, v_pool; uint32_t lock; uint32_t filter; } flags; struct { bool dynamic; //device supports dynamic textures bool shader; //device supports pixel shaders } caps; struct { HWND handle = nullptr; bool synchronize = false; unsigned filter = Video::FilterLinear; unsigned width; unsigned height; } settings; struct { unsigned width; unsigned height; } state; auto cap(const string& name) -> bool { if(name == Video::Handle) return true; if(name == Video::Synchronize) return true; if(name == Video::Filter) return true; if(name == Video::Shader) return false; return false; } auto get(const string& name) -> any { if(name == Video::Handle) return (uintptr_t)settings.handle; if(name == Video::Synchronize) return settings.synchronize; if(name == Video::Filter) return settings.filter; return {}; } auto set(const string& name, const any& value) -> bool { if(name == Video::Handle && value.is()) { settings.handle = (HWND)value.get(); return true; } if(name == Video::Synchronize && value.is()) { settings.synchronize = value.get(); return true; } if(name == Video::Filter && value.is()) { settings.filter = value.get(); if(lpd3d) update_filter(); return true; } if(name == Video::Shader && value.is()) { return false; //set_shader(value.get()); //return true; } return false; } auto recover() -> bool { if(!device) return false; if(lost) { release_resources(); 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(NULL); device->SetFVF(D3DVERTEX); device->CreateVertexBuffer(sizeof(d3dvertex) * 4, flags.v_usage, D3DVERTEX, (D3DPOOL)flags.v_pool, &vertex_buffer, NULL); iwidth = 0; iheight = 0; resize(settings.width = 256, settings.height = 256); update_filter(); clear(); return true; } auto rounded_power_of_two(unsigned n) -> unsigned { n--; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; return n + 1; } auto resize(unsigned width, unsigned height) -> void { if(iwidth >= width && iheight >= height) return; iwidth = rounded_power_of_two(max(width, iwidth )); iheight = rounded_power_of_two(max(height, iheight)); if(d3dcaps.MaxTextureWidth < iwidth || d3dcaps.MaxTextureWidth < iheight) { //TODO: attempt to handle this more gracefully return; } if(texture) texture->Release(); device->CreateTexture(iwidth, iheight, 1, flags.t_usage, D3DFMT_X8R8G8B8, (D3DPOOL)flags.t_pool, &texture, NULL); } auto update_filter() -> void { if(!device) return; if(lost && !recover()) return; flags.filter = (settings.filter == Video::FilterNearest ? D3DTEXF_POINT : D3DTEXF_LINEAR); device->SetSamplerState(0, D3DSAMP_MINFILTER, flags.filter); device->SetSamplerState(0, D3DSAMP_MAGFILTER, flags.filter); } // Vertex format: // // 0----------1 // | /| // | / | // | / | // | / | // | / | // 2----------3 // // (x,y) screen coords, in pixels // (u,v) texture coords, betweeen 0.0 (top, left) to 1.0 (bottom, right) auto set_vertex( 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 { d3dvertex 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; vertex_buffer->Lock(0, sizeof(d3dvertex) * 4, (void**)&vertex_ptr, 0); memcpy(vertex_ptr, vertex, sizeof(d3dvertex) * 4); vertex_buffer->Unlock(); device->SetStreamSource(0, vertex_buffer, 0, sizeof(d3dvertex)); } auto clear() -> void { if(lost && !recover()) return; texture->GetLevelDesc(0, &d3dsd); 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(unsigned i = 0; i < 3; i++) { 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, unsigned& pitch, unsigned width, unsigned height) -> bool { if(lost && !recover()) return false; if(width != settings.width || height != settings.height) { resize(settings.width = width, settings.height = height); } texture->GetLevelDesc(0, &d3dsd); texture->GetSurfaceLevel(0, &surface); surface->LockRect(&d3dlr, 0, flags.lock); pitch = d3dlr.Pitch; return data = (uint32_t*)d3dlr.pBits; } auto unlock() -> void { surface->UnlockRect(); surface->Release(); surface = nullptr; } auto refresh() -> void { if(lost && !recover()) return; RECT rd, rs; //dest, source rectangles GetClientRect(settings.handle, &rd); SetRect(&rs, 0, 0, settings.width, settings.height); //if output size changed, driver must be re-initialized. //failure to do so causes scaling issues on some video drivers. if(state.width != rd.right || state.height != rd.bottom) { init(); set_shader(shader_source_markup); return; } if(caps.shader && effect) { device->BeginScene(); set_vertex(0, 0, settings.width, settings.height, iwidth, iheight, 0, 0, rd.right, rd.bottom); D3DXVECTOR4 rubyTextureSize; rubyTextureSize.x = iwidth; rubyTextureSize.y = iheight; rubyTextureSize.z = 1.0 / iheight; rubyTextureSize.w = 1.0 / iwidth; effect->SetVector("rubyTextureSize", &rubyTextureSize); D3DXVECTOR4 rubyInputSize; rubyInputSize.x = settings.width; rubyInputSize.y = settings.height; rubyInputSize.z = 1.0 / settings.height; rubyInputSize.w = 1.0 / settings.width; effect->SetVector("rubyInputSize", &rubyInputSize); D3DXVECTOR4 rubyOutputSize; rubyOutputSize.x = rd.right; rubyOutputSize.y = rd.bottom; rubyOutputSize.z = 1.0 / rd.bottom; rubyOutputSize.w = 1.0 / rd.right; effect->SetVector("rubyOutputSize", &rubyOutputSize); UINT passes; effect->Begin(&passes, 0); effect->SetTexture("rubyTexture", texture); device->SetTexture(0, texture); for(unsigned pass = 0; pass < passes; pass++) { effect->BeginPass(pass); device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); effect->EndPass(); } effect->End(); device->EndScene(); } else { device->BeginScene(); set_vertex(0, 0, settings.width, settings.height, iwidth, iheight, 0, 0, rd.right, rd.bottom); device->SetTexture(0, texture); device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); device->EndScene(); } if(settings.synchronize) { D3DRASTER_STATUS status; //wait for a previous vblank to finish, if necessary while(true) { device->GetRasterStatus(0, &status); if(status.InVBlank == false) break; } //wait for next vblank to begin while(true) { device->GetRasterStatus(0, &status); if(status.InVBlank == true) break; } } if(device->Present(0, 0, 0, 0) == D3DERR_DEVICELOST) lost = true; } auto set_shader(const char* source) -> void { if(!caps.shader) return; if(effect) { effect->Release(); effect = NULL; } if(!source || !*source) { shader_source_markup = ""; return; } shader_source_markup = source; auto document = BML::unserialize(shader_source_markup); bool is_hlsl = document["shader"]["language"].text() == "HLSL"; string shader_source = document["shader"]["source"].text(); if(shader_source == "") return; HMODULE d3dx; for(unsigned i = 0; i < 256; i++) { char t[256]; sprintf(t, "d3dx9_%u.dll", i); d3dx = LoadLibraryW(utf16_t(t)); if(d3dx) break; } if(!d3dx) d3dx = LoadLibraryW(L"d3dx9.dll"); if(!d3dx) return; EffectProc effectProc = (EffectProc)GetProcAddress(d3dx, "D3DXCreateEffect"); TextureProc textureProc = (TextureProc)GetProcAddress(d3dx, "D3DXCreateTextureFromFileA"); LPD3DXBUFFER pBufferErrors = NULL; effectProc(device, (const void*)shader_source.data(), lstrlenA(shader_source), NULL, NULL, 0, NULL, &effect, &pBufferErrors); D3DXHANDLE hTech; effect->FindNextValidTechnique(NULL, &hTech); effect->SetTechnique(hTech); } auto init() -> bool { RECT rd; GetClientRect(settings.handle, &rd); state.width = rd.right; state.height = rd.bottom; lpd3d = Direct3DCreate9(D3D_SDK_VERSION); if(!lpd3d) return false; memset(&presentation, 0, sizeof(presentation)); presentation.Flags = D3DPRESENTFLAG_VIDEO; presentation.SwapEffect = D3DSWAPEFFECT_FLIP; presentation.hDeviceWindow = settings.handle; presentation.BackBufferCount = 1; presentation.MultiSampleType = D3DMULTISAMPLE_NONE; presentation.MultiSampleQuality = 0; presentation.EnableAutoDepthStencil = false; presentation.AutoDepthStencilFormat = D3DFMT_UNKNOWN; presentation.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; presentation.Windowed = true; presentation.BackBufferFormat = D3DFMT_UNKNOWN; presentation.BackBufferWidth = 0; presentation.BackBufferHeight = 0; if(lpd3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, settings.handle, D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &presentation, &device) != D3D_OK) { return false; } device->GetDeviceCaps(&d3dcaps); caps.dynamic = bool(d3dcaps.Caps2 & D3DCAPS2_DYNAMICTEXTURES); caps.shader = d3dcaps.PixelShaderVersion > D3DPS_VERSION(1, 4); if(caps.dynamic == true) { flags.t_usage = D3DUSAGE_DYNAMIC; flags.v_usage = D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC; flags.t_pool = D3DPOOL_DEFAULT; flags.v_pool = D3DPOOL_DEFAULT; flags.lock = D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD; } else { flags.t_usage = 0; flags.v_usage = D3DUSAGE_WRITEONLY; flags.t_pool = D3DPOOL_MANAGED; flags.v_pool = D3DPOOL_MANAGED; flags.lock = D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD; } lost = false; recover(); return true; } auto release_resources() -> void { if(effect) { effect->Release(); effect = 0; } if(vertex_buffer) { vertex_buffer->Release(); vertex_buffer = 0; } if(surface) { surface->Release(); surface = 0; } if(texture) { texture->Release(); texture = 0; } } auto term() -> void { release_resources(); if(device) { device->Release(); device = 0; } if(lpd3d) { lpd3d->Release(); lpd3d = 0; } } }; #undef D3DVERTEX