quakeforge/libs/video/targets/vid_win_sw.c
Bill Currie 0c437492b4 [renderer] Move to using dynamic frame buffers
For now, OpenGL and Vulkan renderers are broken as I focused on getting
the software renderer working (which was quite tricky to get right).

This fixes a couple of issues: the segfault when warping the screen (due
to the scene rendering move invalidating the warp buffer), and warp
always having 320x200 resolution. There's still the problem of the
effect being too subtle at high resolution, but that's just a matter of
updating the tables and tweaking the code in D_WarpScreen.

Another issue is the Draw functions should probably write directly to
the main frame buffer or even one passed in as a parameter. This would
remove the need for binding the main buffer at the beginning and end of
the frame.
2022-03-24 12:56:29 +09:00

494 lines
12 KiB
C

/*
vid_win.c
Win32 SW vid component
Copyright (C) 1996-1997 Id Software, Inc.
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:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "winquake.h"
#include <ddraw.h>
#include "QF/cvar.h"
#include "QF/qargs.h"
#include "QF/sys.h"
#include "QF/vid.h"
#include "context_win.h"
#include "r_internal.h"
#include "vid_internal.h"
#include "vid_sw.h"
typedef union {
byte bgra[4];
uint32_t value;
} win_palette_t;
static win_palette_t st2d_8to32table[256];
static byte current_palette[768];
static int palette_changed;
static LPDIRECTDRAW dd_Object;
static HINSTANCE hInstDDraw;
static LPDIRECTDRAWSURFACE dd_frontbuffer;
static LPDIRECTDRAWSURFACE dd_backbuffer;
static RECT src_rect;
static RECT dst_rect;
static HDC win_gdi;
static HDC dib_section;
static HBITMAP dib_bitmap;
static HGDIOBJ previous_dib;
static int using_ddraw;
static LPDIRECTDRAWCLIPPER dd_Clipper;
typedef HRESULT (WINAPI *ddCreateProc_t) (GUID FAR *,
LPDIRECTDRAW FAR *,
IUnknown FAR *);
static ddCreateProc_t ddCreate;
static int dd_window_width = 640;
static int dd_window_height = 480;
static sw_framebuffer_t swfb;
static framebuffer_t fb = { .buffer = &swfb };
static void
DD_UpdateRects (int width, int height)
{
POINT p = { .x = 0, .y = 0 };
// first we need to figure out where on the primary surface our window
// lives
ClientToScreen (win_mainwindow, &p);
GetClientRect (win_mainwindow, &dst_rect);
OffsetRect (&dst_rect, p.x, p.y);
SetRect (&src_rect, 0, 0, width, height);
}
static void
VID_CreateDDrawDriver (int width, int height)
{
DDSURFACEDESC ddsd;
using_ddraw = false;
dd_window_width = width;
dd_window_height = height;
fb.width = width;
fb.height = height;
swfb.color = malloc (width * height);
swfb.rowbytes = width;
if (!(hInstDDraw = LoadLibrary ("ddraw.dll"))) {
return;
}
if (!(ddCreate = (ddCreateProc_t) GetProcAddress (hInstDDraw,
"DirectDrawCreate"))) {
return;
}
if (FAILED (ddCreate (0, &dd_Object, 0))) {
return;
}
if (FAILED (dd_Object->lpVtbl->SetCooperativeLevel (dd_Object,
win_mainwindow,
DDSCL_NORMAL))) {
return;
}
// the primary surface in windowed mode is the full screen
memset (&ddsd, 0, sizeof (ddsd));
ddsd.dwSize = sizeof (ddsd);
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_VIDEOMEMORY;
// ...and create it
if (FAILED (dd_Object->lpVtbl->CreateSurface (dd_Object, &ddsd,
&dd_frontbuffer, 0))) {
return;
}
// not using a clipper will slow things down and switch aero off
if (FAILED (IDirectDraw_CreateClipper (dd_Object, 0, &dd_Clipper, 0))) {
return;
}
if (FAILED (IDirectDrawClipper_SetHWnd (dd_Clipper, 0, win_mainwindow))) {
return;
}
if (FAILED (IDirectDrawSurface_SetClipper (dd_frontbuffer,
dd_Clipper))) {
return;
}
// the secondary surface is an offscreen surface that is the currect
// dimensions
// this will be blitted to the correct location on the primary surface
// (which is the full screen) during our draw op
memset (&ddsd, 0, sizeof (ddsd));
ddsd.dwSize = sizeof (ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;
ddsd.dwWidth = width;
ddsd.dwHeight = height;
if (FAILED (IDirectDraw_CreateSurface (dd_Object, &ddsd,
&dd_backbuffer, 0))) {
return;
}
// direct draw is working now
using_ddraw = true;
// create initial rects
DD_UpdateRects (dd_window_width, dd_window_height);
}
static void
VID_CreateGDIDriver (int width, int height, const byte *palette, byte **buffer,
int *rowbytes)
{
// common bitmap definition
typedef struct dibinfo {
BITMAPINFOHEADER header;
RGBQUAD acolors[256];
} dibinfo_t;
dibinfo_t dibheader;
BITMAPINFO *pbmiDIB = (BITMAPINFO *) & dibheader;
int i;
byte *dib_base = 0;
win_gdi = GetDC (win_mainwindow);
memset (&dibheader, 0, sizeof (dibheader));
// fill in the bitmap info
pbmiDIB->bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
pbmiDIB->bmiHeader.biWidth = width;
pbmiDIB->bmiHeader.biHeight = height;
pbmiDIB->bmiHeader.biPlanes = 1;
pbmiDIB->bmiHeader.biCompression = BI_RGB;
pbmiDIB->bmiHeader.biSizeImage = 0;
pbmiDIB->bmiHeader.biXPelsPerMeter = 0;
pbmiDIB->bmiHeader.biYPelsPerMeter = 0;
pbmiDIB->bmiHeader.biClrUsed = 256;
pbmiDIB->bmiHeader.biClrImportant = 256;
pbmiDIB->bmiHeader.biBitCount = 8;
// fill in the palette
for (i = 0; i < 256; i++) {
// d_8to24table isn't filled in yet so this is just for testing
dibheader.acolors[i].rgbRed = palette[i * 3];
dibheader.acolors[i].rgbGreen = palette[i * 3 + 1];
dibheader.acolors[i].rgbBlue = palette[i * 3 + 2];
}
// create the DIB section
dib_bitmap = CreateDIBSection (win_gdi,
pbmiDIB,
DIB_RGB_COLORS,
(void **) &dib_base, 0, 0);
// set video buffers
if (pbmiDIB->bmiHeader.biHeight > 0) {
// bottom up
buffer[0] = dib_base + (height - 1) * width;
rowbytes[0] = -width;
} else {
// top down
buffer[0] = dib_base;
rowbytes[0] = width;
}
// clear the buffer
memset (dib_base, 0xff, width * height);
if ((dib_section = CreateCompatibleDC (win_gdi)) == 0)
Sys_Error ("DIB_Init() - CreateCompatibleDC failed\n");
if ((previous_dib = SelectObject (dib_section, dib_bitmap)) == 0)
Sys_Error ("DIB_Init() - SelectObject failed\n");
// create a palette
VID_InitGamma (palette);
viddef.vid_internal->set_palette (viddef.vid_internal->data, palette);
}
void
Win_UnloadAllDrivers (void)
{
// shut down ddraw
if (swfb.color) {
free (swfb.color);
swfb.color = 0;
}
if (swfb.depth) {
free (swfb.depth);
swfb.depth = 0;
}
if (dd_Clipper) {
IDirectDrawClipper_Release (dd_Clipper);
dd_Clipper = 0;
}
if (dd_frontbuffer) {
IDirectDrawSurface_Release (dd_frontbuffer);
dd_frontbuffer = 0;
}
if (dd_backbuffer) {
IDirectDrawSurface_Release (dd_backbuffer);
dd_backbuffer = 0;
}
if (dd_Object) {
IDirectDraw_Release (dd_Object);
dd_Object = 0;
}
if (hInstDDraw) {
FreeLibrary (hInstDDraw);
hInstDDraw = 0;
}
ddCreate = 0;
// shut down gdi
if (dib_section) {
SelectObject (dib_section, previous_dib);
DeleteDC (dib_section);
dib_section = 0;
}
if (dib_bitmap) {
DeleteObject (dib_bitmap);
dib_bitmap = 0;
}
if (win_gdi) {
// if win_gdi exists then win_mainwindow must also be valid
ReleaseDC (win_mainwindow, win_gdi);
win_gdi = 0;
}
// not using ddraw now
using_ddraw = false;
}
static void
Win_CreateDriver (void)
{
if (vid_ddraw->int_val) {
VID_CreateDDrawDriver (viddef.width, viddef.height);
}
if (!using_ddraw) {
// directdraw failed or was not requested
//
// if directdraw failed, it may be partially initialized, so make sure
// the slate is clean
Win_UnloadAllDrivers ();
VID_CreateGDIDriver (viddef.width, viddef.height, viddef.palette,
&swfb.color, &swfb.rowbytes);
}
}
static void
win_init_bufers (void *data)
{
sw_ctx_t *ctx = data;
ctx->framebuffer = &fb;
Win_UnloadAllDrivers ();
Win_CreateDriver ();
}
static void
win_set_palette (sw_ctx_t *ctx, const byte *palette)
{
palette_changed = 1;
if (palette != current_palette) {
memcpy (current_palette, palette, sizeof (current_palette));
}
for (int i = 0; i < 256; i++) {
const byte *pal = palette + 3 * i;
st2d_8to32table[i].bgra[0] = viddef.gammatable[pal[2]];
st2d_8to32table[i].bgra[1] = viddef.gammatable[pal[1]];
st2d_8to32table[i].bgra[2] = viddef.gammatable[pal[0]];
st2d_8to32table[i].bgra[3] = 255;
}
if (!win_minimized && !using_ddraw && dib_section) {
RGBQUAD colors[256];
memcpy (colors, st2d_8to32table, sizeof (colors));
for (int i = 0; i < 256; i++) {
colors[i].rgbReserved = 0;
}
colors[0].rgbRed = 0;
colors[0].rgbGreen = 0;
colors[0].rgbBlue = 0;
colors[255].rgbRed = 0xff;
colors[255].rgbGreen = 0xff;
colors[255].rgbBlue = 0xff;
if (SetDIBColorTable (dib_section, 0, 256, colors) == 0) {
Sys_Printf ("win_set_palette() - SetDIBColorTable failed\n");
}
}
}
static void
dd_blit_rect (vrect_t *rect)
{
RECT TheRect;
RECT sRect, dRect;
DDSURFACEDESC ddsd;
memset (&ddsd, 0, sizeof (ddsd));
ddsd.dwSize = sizeof (DDSURFACEDESC);
// lock the correct subrect
TheRect.left = rect->x;
TheRect.right = rect->x + rect->width;
TheRect.top = rect->y;
TheRect.bottom = rect->y + rect->height;
if (IDirectDrawSurface_Lock (dd_backbuffer, &TheRect, &ddsd,
DDLOCK_WRITEONLY | DDLOCK_SURFACEMEMORYPTR,
0) == DDERR_WASSTILLDRAWING) {
return;
}
// convert pitch to 32-bit addressable
ddsd.lPitch >>= 2;
byte *src = swfb.color + rect->y * swfb.rowbytes + rect->x;
unsigned *dst = ddsd.lpSurface;
for (int y = rect->height; y-- > 0; ) {
for (int x = rect->width; x-- > 0; ) {
*dst++ = st2d_8to32table[*src++].value;
}
src += swfb.rowbytes - rect->width;
dst += ddsd.lPitch - rect->width;
}
IDirectDrawSurface_Unlock (dd_backbuffer, 0);
// correctly offset source
sRect.left = src_rect.left + rect->x;
sRect.right = src_rect.left + rect->x + rect->width;
sRect.top = src_rect.top + rect->y;
sRect.bottom = src_rect.top + rect->y + rect->height;
// correctly offset dest
dRect.left = dst_rect.left + rect->x;
dRect.right = dst_rect.left + rect->x + rect->width;
dRect.top = dst_rect.top + rect->y;
dRect.bottom = dst_rect.top + rect->y + rect->height;
// copy to front buffer
IDirectDrawSurface_Blt (dd_frontbuffer, &dRect, dd_backbuffer,
&sRect, 0, 0);
}
static void
win_sw_update (sw_ctx_t *ctx, vrect_t *rects)
{
vrect_t full_rect;
if (!win_palettized && palette_changed) {
palette_changed = false;
full_rect.x = 0;
full_rect.y = 0;
full_rect.width = viddef.width;
full_rect.height = viddef.height;
full_rect.next = 0;
rects = &full_rect;
}
if (using_ddraw) {
while (rects) {
dd_blit_rect (rects);
rects = rects->next;
}
} else if (dib_section) {
while (rects) {
BitBlt (win_gdi, rects->x, rects->y,
rects->x + rects->width, rects->y + rects->height,
dib_section, rects->x, rects->y, SRCCOPY);
rects = rects->next;
}
}
}
static void
win_choose_visual (sw_ctx_t *ctx)
{
}
static void
win_set_background (void)
{
// because we have set the background brush for the window to 0 (to
// avoid flickering when re-sizing the window on the desktop), we clear
// the window to black when created, otherwise it will be empty while
// Quake starts up. This also prevents a screen flash to white when
// switching drivers. it still flashes, but at least it's black now
HDC hdc = GetDC (win_mainwindow);
PatBlt (hdc, 0, 0, win_rect.right, win_rect.bottom, BLACKNESS);
ReleaseDC (win_mainwindow, hdc);
}
static void
win_create_context (sw_ctx_t *ctx)
{
// shutdown any old driver that was active
Win_UnloadAllDrivers ();
win_sw_context = ctx;
win_set_background ();
// create the new driver
using_ddraw = false;
viddef.vid_internal->init_buffers = win_init_bufers;
}
sw_ctx_t *
Win_SW_Context (void)
{
sw_ctx_t *ctx = calloc (1, sizeof (sw_ctx_t));
ctx->set_palette = win_set_palette;
ctx->choose_visual = win_choose_visual;
ctx->create_context = win_create_context;
ctx->update = win_sw_update;
return ctx;
}
void
Win_SW_Init_Cvars (void)
{
}