/* * VARCem Virtual ARchaeological Computer EMulator. * An emulator of (mostly) x86-based PC systems and devices, * using the ISA,EISA,VLB,MCA and PCI system buses, roughly * spanning the era between 1981 and 1995. * * This file is part of the VARCem Project. * * Rendering module for Microsoft Direct2D. * * Version: @(#)win_d2d.cpp 1.0.6 2019/04/29 * * Authors: Fred N. van Kempen, * David Hrdlicka, * * Copyright 2018,2019 Fred N. van Kempen. * Copyright 2018 David Hrdlicka. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the: * * Free Software Foundation, Inc. * 59 Temple Place - Suite 330 * Boston, MA 02111-1307 * USA. */ #define UNICODE #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include "../emu.h" #include "../version.h" #include "../device.h" #include "../plat.h" #ifdef USE_LIBPNG # include "../png.h" #endif #ifdef _MSC_VER # pragma warning(disable: 4200) #endif #include "../devices/video/video.h" #include "win.h" #include "win_d2d.h" #define PATH_D2D_DLL "d2d1.dll" #if USE_D2D == 2 # define DLLFUNC(x) D2D1_##x /* Pointers to the real functions. */ typedef HRESULT (WINAPI *MyCreateFactory_t)(D2D1_FACTORY_TYPE, REFIID, CONST D2D1_FACTORY_OPTIONS*, void**); static MyCreateFactory_t D2D1_CreateFactory; static const dllimp_t d2d_imports[] = { { "D2D1CreateFactory", &D2D1_CreateFactory }, { NULL, NULL } }; static void *d2d_handle = NULL; #else # define DLLFUNC(x) D2D1##x #endif static HWND d2d_hwnd, old_hwndMain; static ID2D1Factory *d2d_factory; static ID2D1HwndRenderTarget *d2d_hwndRT; static ID2D1BitmapRenderTarget *d2d_btmpRT; static ID2D1Bitmap *d2d_bitmap; static int d2d_width, d2d_height, d2d_screen_width, d2d_screen_height, d2d_fs; static int d2d_enabled; static void d2d_stretch(float *w, float *h, float *x, float *y) { double dw, dh, dx, dy, temp, temp2, ratio_w, ratio_h, gsr, hsr; switch (vid_fullscreen_scale) { case FULLSCR_SCALE_FULL: *w = (float)d2d_screen_width; *h = (float)d2d_screen_height; *x = 0; *y = 0; break; case FULLSCR_SCALE_43: dw = (double)d2d_screen_width; dh = (double)d2d_screen_height; temp = (dh / 3.0) * 4.0; dx = (dw - temp) / 2.0; dw = temp; *w = (float)dw; *h = (float)dh; *x = (float)dx; *y = 0; break; case FULLSCR_SCALE_SQ: dw = (double)d2d_screen_width; dh = (double)d2d_screen_height; temp = ((double) *w); temp2 = ((double) *h); dx = (dw / 2.0) - ((dh * temp) / (temp2 * 2.0)); dy = 0.0; if (dx < 0.0) { dx = 0.0; dy = (dw / 2.0) - ((dh * temp2) / (temp * 2.0)); } dw -= (dx * 2.0); dh -= (dy * 2.0); *w = (float)dw; *h = (float)dh; *x = (float)dx; *y = (float)dy; break; case FULLSCR_SCALE_INT: dw = (double)d2d_screen_width; dh = (double)d2d_screen_height; temp = ((double) *w); temp2 = ((double) *h); ratio_w = dw / ((double) *w); ratio_h = dh / ((double) *h); if (ratio_h < ratio_w) ratio_w = ratio_h; dx = (dw / 2.0) - ((temp * ratio_w) / 2.0); dy = (dh / 2.0) - ((temp2 * ratio_h) / 2.0); dw -= (dx * 2.0); dh -= (dy * 2.0); *w = (float)dw; *h = (float)dh; *x = (float)dx; *y = (float)dy; break; case FULLSCR_SCALE_KEEPRATIO: dw = (double)d2d_screen_width; dh = (double)d2d_screen_height; hsr = dw / dh; gsr = ((double) *w) / ((double) *h); if (gsr <= hsr) { temp = dh * gsr; dx = (dw - temp) / 2.0; dw = temp; *w = (float)dw; *h = (float)dh; *x = (float)dx; *y = 0; } else { temp = dw / gsr; dy = (dh - temp) / 2.0; dh = temp; *w = (float)dw; *h = (float)dh; *x = 0; *y = (float)dy; } break; } } static void d2d_blit(bitmap_t *scr, int x, int y, int y1, int y2, int w, int h) { ID2D1Bitmap *fs_bitmap; ID2D1RenderTarget *RT; D2D1_RECT_U rectU; HRESULT hr = S_OK; void *srcdata; int yy; float fs_x = 0, fs_y = 0; float fs_w = (float)w; float fs_h = (float)h; DEBUG("D2D: blit(x=%d, y=%d, y1=%d, y2=%d, w=%d, h=%d)\n", x,y, y1,y2, w,h); if (! d2d_enabled) { video_blit_done(); return; } // TODO: Detect double scanned mode and resize render target // appropriately for more clear picture if (w != d2d_width || h != d2d_height) { if (d2d_fs) { if (d2d_btmpRT) { d2d_btmpRT->Release(); d2d_btmpRT = NULL; } hr = d2d_hwndRT->CreateCompatibleRenderTarget( D2D1::SizeF((float)w, (float)h), &d2d_btmpRT); } else { hr = d2d_hwndRT->Resize(D2D1::SizeU(w, h)); } if (SUCCEEDED(hr)) { d2d_width = w; d2d_height = h; } } if ((y1 == y2) || (scr == NULL)) { video_blit_done(); return; } // TODO: Copy data directly from screen to d2d_bitmap srcdata = mem_alloc(h * w * 4); for (yy = y1; yy < y2; yy++) { if ((y + yy) >= 0 && (y + yy) < scr->h) { if (vid_grayscale || invert_display) video_transform_copy( (uint32_t *)&(((uint8_t *)srcdata)[yy * w * 4]), &scr->line[y + yy][x], w); else memcpy( (uint32_t *)&(((uint8_t *)srcdata)[yy * w * 4]), &scr->line[y + yy][x], w * 4); } } video_blit_done(); rectU = D2D1::RectU(0, 0, w, h); hr = d2d_bitmap->CopyFromMemory(&rectU, srcdata, w * 4); /* * In fullscreen mode we first draw offscreen to an intermediate * BitmapRenderTarget, which then gets rendered to the actual * HwndRenderTarget in order to implement different scaling modes. * * In windowed mode we draw directly to the HwndRenderTarget. */ if (SUCCEEDED(hr)) { RT = d2d_fs ? (ID2D1RenderTarget *)d2d_btmpRT : (ID2D1RenderTarget *)d2d_hwndRT; RT->BeginDraw(); RT->DrawBitmap(d2d_bitmap, D2D1::RectF(0, (float)y1, (float)w, (float)y2), 1.0f, D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2D1::RectF(0, (float)y1, (float)w, (float)y2)); hr = RT->EndDraw(); } if (d2d_fs) { if (SUCCEEDED(hr)) hr = d2d_btmpRT->GetBitmap(&fs_bitmap); if (SUCCEEDED(hr)) { d2d_stretch(&fs_w, &fs_h, &fs_x, &fs_y); d2d_hwndRT->BeginDraw(); d2d_hwndRT->Clear(D2D1::ColorF(D2D1::ColorF::Black)); d2d_hwndRT->DrawBitmap(fs_bitmap, D2D1::RectF(fs_x, fs_y, fs_x + fs_w, fs_y + fs_h), 1.0f, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, D2D1::RectF(0, 0, (float)w, (float)h)); hr = d2d_hwndRT->EndDraw(); } } if (FAILED(hr)) ERRLOG("D2D: blit: error 0x%08lx\n", hr); /* Clean up. */ free(srcdata); } static void d2d_close(void) { DEBUG("D2D: close()\n"); video_blit_set(NULL); if (d2d_bitmap) { d2d_bitmap->Release(); d2d_bitmap = NULL; } if (d2d_btmpRT) { d2d_btmpRT->Release(); d2d_btmpRT = NULL; } if (d2d_hwndRT) { d2d_hwndRT->Release(); d2d_hwndRT = NULL; } if (d2d_factory) { d2d_factory->Release(); d2d_factory = NULL; } if (d2d_hwnd) { hwndMain = old_hwndMain; plat_set_input(hwndMain); DestroyWindow(d2d_hwnd); d2d_hwnd = NULL; old_hwndMain = NULL; } #if USE_D2D == 2 /* Quit and unload the DLL if possible. */ if (d2d_handle != NULL) { dynld_close(d2d_handle); d2d_handle = NULL; } #endif d2d_enabled = 0; } static int d2d_init(int fs) { WCHAR title[200]; D2D1_HWND_RENDER_TARGET_PROPERTIES props; D2D1_FACTORY_OPTIONS options; HRESULT hr; INFO("D2D: init(fs=%d)\n", fs); #if USE_D2D == 2 /* Try loading the DLL. */ if (d2d_handle == NULL) d2d_handle = dynld_module(PATH_D2D_DLL, d2d_imports); if (d2d_handle == NULL) { ERRLOG("D2D: unable to load '%s', D2D not available.\n", PATH_D2D_DLL); return(0); } else INFO("D2D: module '%s' loaded.\n", PATH_D2D_DLL); #endif ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS)); if (FAILED(DLLFUNC(CreateFactory)(D2D1_FACTORY_TYPE_MULTI_THREADED, __uuidof(ID2D1Factory), &options, reinterpret_cast (&d2d_factory)))) { ERRLOG("D2D: unable to load factory, D2D not available.\n"); d2d_close(); return(0); } if (fs) { /* * Direct2D seems to lack any proper fullscreen mode, * therefore we just create a full screen window and * pass its handle to a HwndRenderTarget. */ d2d_screen_width = GetSystemMetrics(SM_CXSCREEN); d2d_screen_height = GetSystemMetrics(SM_CYSCREEN); mbstowcs(title, emu_version, sizeof_w(title)); d2d_hwnd = CreateWindow(FS_CLASS_NAME, title, WS_POPUP, 0, 0, d2d_screen_width, d2d_screen_height, HWND_DESKTOP, NULL, hInstance, NULL); old_hwndMain = hwndMain; hwndMain = d2d_hwnd; plat_set_input(d2d_hwnd); SetFocus(d2d_hwnd); SetWindowPos(d2d_hwnd, HWND_TOPMOST, 0,0, d2d_screen_width,d2d_screen_height, SWP_SHOWWINDOW); props = D2D1::HwndRenderTargetProperties(d2d_hwnd, D2D1::SizeU(d2d_screen_width, d2d_screen_height)); } else { // HwndRenderTarget will get resized appropriately by d2d_blit, // so it's fine to let D2D imply size of 0x0 for now props = D2D1::HwndRenderTargetProperties(hwndRender); } hr = d2d_factory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(), props, &d2d_hwndRT); if (FAILED(hr)) { ERRLOG("D2D: unable to create target: error 0x%08lx\n", hr); d2d_close(); return(0); } // Create a bitmap for storing intermediate data hr = d2d_hwndRT->CreateBitmap(D2D1::SizeU(2048, 2048), D2D1::BitmapProperties(D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)), &d2d_bitmap); if (FAILED(hr)) { ERRLOG("D2D: unable to create bitmap: error 0x%08lx\n", hr); d2d_close(); return(0); } d2d_fs = fs; d2d_width = 0; d2d_height = 0; /* Make sure we get a clean exit. */ atexit(d2d_close); /* Register our renderer! */ video_blit_set(d2d_blit); d2d_enabled = 1; return(1); } static void d2d_screenshot(const wchar_t *fn) { // Saving a screenshot of a Direct2D render target is harder than // one would think. Keeping this stubbed for the moment // -ryu INFO("D2D: screenshot(%ls)\n", fn); } /* See if this module is available or not. */ static int d2d_available(void) { void *handle; handle = dynld_module(PATH_D2D_DLL, NULL); if (handle != NULL) { dynld_close(handle); return(1); } return(0); } static void d2d_enable(int yes) { d2d_enabled = yes; } const vidapi_t d2d_vidapi = { "d2d", "Direct 2D", 1, d2d_init, d2d_close, NULL, NULL, NULL, d2d_enable, d2d_screenshot, d2d_available };