#include #include #define D3DVERTEX (D3DFVF_XYZRHW | D3DFVF_TEX1) namespace ruby { #include "direct3d.hpp" class pVideoD3D { public: VideoD3D &self; LPDIRECT3D9 lpd3d; LPDIRECT3DDEVICE9 device; LPDIRECT3DVERTEXBUFFER9 vertex_buffer, *vertex_ptr; D3DPRESENT_PARAMETERS presentation; D3DSURFACE_DESC d3dsd; D3DLOCKED_RECT d3dlr; D3DRASTER_STATUS d3drs; D3DCAPS9 d3dcaps; LPDIRECT3DTEXTURE9 texture; LPDIRECT3DSURFACE9 surface; 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 stretchrect; //device supports StretchRect } caps; struct { HWND handle; bool synchronize; unsigned filter; } settings; struct { unsigned width; unsigned height; } state; bool cap(Video::Setting setting) { if(setting == Video::Handle) return true; if(setting == Video::Synchronize) return true; if(setting == Video::Filter) return true; return false; } uintptr_t get(Video::Setting setting) { if(setting == Video::Handle) return (uintptr_t)settings.handle; if(setting == Video::Synchronize) return settings.synchronize; if(setting == Video::Filter) return settings.filter; return false; } bool set(Video::Setting setting, uintptr_t param) { if(setting == Video::Handle) { settings.handle = (HWND)param; return true; } if(setting == Video::Synchronize) { settings.synchronize = param; return true; } if(setting == Video::Filter) { settings.filter = param; if(lpd3d) update_filter(); return true; } return false; } void update_filter() { if(!device) return; switch(settings.filter) { default: case Video::FilterPoint: flags.filter = D3DTEXF_POINT; break; case Video::FilterLinear: flags.filter = D3DTEXF_LINEAR; break; } 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) */ void 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 ) { d3dvertex vertex[4]; vertex[0].x = vertex[2].x = (double)(x ); vertex[1].x = vertex[3].x = (double)(x + w); vertex[0].y = vertex[1].y = (double)(y ); vertex[2].y = vertex[3].y = (double)(y + h); //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)); } void clear() { if(!device) return; if(caps.stretchrect == false && !texture) return; if(caps.stretchrect == false) { texture->GetLevelDesc(0, &d3dsd); texture->GetSurfaceLevel(0, &surface); } if(surface) { device->ColorFill(surface, 0, D3DCOLOR_XRGB(0x00, 0x00, 0x00)); if(caps.stretchrect == false) { surface->Release(); } } //clear primary display and all backbuffers for(int 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); } } bool lock(uint32_t *&data, unsigned &pitch) { if(caps.stretchrect == false) { texture->GetLevelDesc(0, &d3dsd); texture->GetSurfaceLevel(0, &surface); } surface->LockRect(&d3dlr, 0, flags.lock); pitch = d3dlr.Pitch; return data = (uint32_t*)d3dlr.pBits; } void unlock() { surface->UnlockRect(); if(caps.stretchrect == false) surface->Release(); } void refresh(unsigned width, unsigned height) { if(!device) return; RECT rd, rs; //dest, source rectangles GetClientRect(settings.handle, &rd); SetRect(&rs, 0, 0, width, height); if(state.width != rd.right || state.height != rd.bottom) { //if window size changed, D3DPRESENT_PARAMETERS must be updated //failure to do so causes scaling issues on some ATI drivers init(); } device->BeginScene(); if(caps.stretchrect == true) { LPDIRECT3DSURFACE9 temp; device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &temp); device->StretchRect(surface, &rs, temp, 0, static_cast(flags.filter)); temp->Release(); } else { set_vertex(0, 0, width, height, 1024, 1024, 0, 0, rd.right, rd.bottom); device->SetTexture(0, texture); device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); } device->EndScene(); if(settings.synchronize) { while(true) { D3DRASTER_STATUS status; device->GetRasterStatus(0, &status); if(status.InVBlank == true) break; } } device->Present(0, 0, 0, 0); } bool init() { term(); 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_SOFTWARE_VERTEXPROCESSING, &presentation, &device) != D3D_OK) { return false; } //detect device capabilities device->GetDeviceCaps(&d3dcaps); if(d3dcaps.MaxTextureWidth < 1024 || d3dcaps.MaxTextureWidth < 1024) return false; caps.dynamic = bool(d3dcaps.Caps2 & D3DCAPS2_DYNAMICTEXTURES); caps.stretchrect = (d3dcaps.DevCaps2 & D3DDEVCAPS2_CAN_STRETCHRECT_FROM_TEXTURES) && (d3dcaps.StretchRectFilterCaps & D3DPTFILTERCAPS_MINFPOINT) && (d3dcaps.StretchRectFilterCaps & D3DPTFILTERCAPS_MAGFPOINT) && (d3dcaps.StretchRectFilterCaps & D3DPTFILTERCAPS_MINFLINEAR) && (d3dcaps.StretchRectFilterCaps & D3DPTFILTERCAPS_MAGFLINEAR); 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; } 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); if(caps.stretchrect == true) { device->CreateOffscreenPlainSurface(1024, 1024, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &surface, NULL); } else { device->CreateTexture(1024, 1024, 1, flags.t_usage, D3DFMT_X8R8G8B8, static_cast(flags.t_pool), &texture, NULL); } device->CreateVertexBuffer(sizeof(d3dvertex) * 4, flags.v_usage, D3DVERTEX, static_cast(flags.v_pool), &vertex_buffer, NULL); update_filter(); clear(); return true; } void term() { if(vertex_buffer) { vertex_buffer->Release(); vertex_buffer = 0; } if(surface) { surface->Release(); surface = 0; } if(texture) { texture->Release(); texture = 0; } if(device) { device->Release(); device = 0; } if(lpd3d) { lpd3d->Release(); lpd3d = 0; } } pVideoD3D(VideoD3D &self_) : self(self_) { vertex_buffer = 0; surface = 0; texture = 0; device = 0; lpd3d = 0; settings.handle = 0; settings.synchronize = false; settings.filter = Video::FilterLinear; } }; bool VideoD3D::cap(Setting setting) { return p.cap(setting); } uintptr_t VideoD3D::get(Setting setting) { return p.get(setting); } bool VideoD3D::set(Setting setting, uintptr_t param) { return p.set(setting, param); } bool VideoD3D::lock(uint32_t *&data, unsigned &pitch) { return p.lock(data, pitch); } void VideoD3D::unlock() { p.unlock(); } void VideoD3D::clear() { p.clear(); } void VideoD3D::refresh(unsigned width, unsigned height) { p.refresh(width, height); } bool VideoD3D::init() { return p.init(); } void VideoD3D::term() { p.term(); } VideoD3D::VideoD3D() : p(*new pVideoD3D(*this)) {} VideoD3D::~VideoD3D() { delete &p; } } //namespace ruby #undef D3DVERTEX