quakeforge/libs/video/targets/vid_win_sw.c
Bill Currie 12c84046f3 [cvar] Make cvars properly typed
This is an extremely extensive patch as it hits every cvar, and every
usage of the cvars. Cvars no longer store the value they control,
instead, they use a cexpr value object to reference the value and
specify the value's type (currently, a null type is used for strings).
Non-string cvars are passed through cexpr, allowing expressions in the
cvars' settings. Also, cvars have returned to an enhanced version of the
original (id quake) registration scheme.

As a minor benefit, relevant code having direct access to the
cvar-controlled variables is probably a slight optimization as it
removed a pointer dereference, and the variables can be located for data
locality.

The static cvar descriptors are made private as an additional safety
layer, though there's nothing stopping external modification via
Cvar_FindVar (which is needed for adding listeners).

While not used yet (partly due to working out the design), cvars can
have a validation function.

Registering a cvar allows a primary listener (and its data) to be
specified: it will always be called first when the cvar is modified. The
combination of proper listeners and direct access to the controlled
variable greatly simplifies the more complex cvar interactions as much
less null checking is required, and there's no need for one cvar's
callback to call another's.

nq-x11 is known to work at least well enough for the demos. More testing
will come.
2022-04-24 19:15:22 +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) {
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)
{
}