/* * Descent 3 * Copyright (C) 2024 Parallax Software * * 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 3 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, see . --- HISTORICAL COMMENTS FOLLOW --- * $Logfile: /DescentIII/Main/2dlib/surface.cpp $ * $Revision: 7 $ * $Date: 11/30/98 5:50p $ * $Author: Jason $ * * Master Surface class * * $Log: /DescentIII/Main/2dlib/surface.cpp $ * * 7 11/30/98 5:50p Jason * added 4444 bitmap support * * 6 4/23/98 6:38p Jason * made bitmaps use 1555 format * * 5 12/19/97 2:26p Jason * more fixes for 2d/3d intergration * * 4 12/19/97 12:32p Samir * lock now calls renderer functions. * * 3 11/11/97 1:24p Samir * Fixed problem with clearing using the renderer. * * 2 10/15/97 5:20p Jason * did a HUGE overhaul of the bitmap system * * 24 6/12/97 6:29p Samir * DDGR v2.0 Changes in 2d system implemented. * * 23 6/06/97 12:18p Samir * OpenGL HACKS to call renderer for some drawing functions. Must fix. * * 22 5/19/97 5:10p Jason * changes for our new abstracted renderer code * * 21 4/30/97 3:15p Jason * changes to support both 8bit and 16bit rendering in software * * 20 3/28/97 3:07p Samir * Clear function now takes a color * * 19 3/27/97 11:11a Samir * Moved code from ddgr library to 2dlib because it was ANSI compliant and * should work on all systems * * 18 2/28/97 2:51p Samir * took out __cdecl in atexit function. * * 17 2/17/97 2:19p Jason * made blit function scale instead of clip * * 16 2/11/97 3:19p Jason * added displaying of 8bit palettized graphics on 16bit surfaces * * 15 2/04/97 12:49p Samir * Added lock(x,y) for those special cases. * * 14 2/03/97 3:01p Samir * Used one memory surface global to grSurface for scratchpad blitting. * * 13 1/30/97 6:01p Samir * The partition of memory and os surfaces as well as the change in * ddgr_surface structure and all related changes. * * $NoKeywords: $ */ #include #include #include #include "bitmap.h" #include "lib2d.h" #include "pserror.h" #include "renderer.h" static inline unsigned XLAT_RGB_TO_16(ddgr_color c) { uint8_t r, g, b; r = (uint8_t)((c & 0x00ff0000) >> 16); g = (uint8_t)((c & 0x0000ff00) >> 8); b = (uint8_t)(c & 0x000000ff); return (((r >> 3) << 10) + ((g >> 3) << 5) + (b >> 3)); } ddgr_surface_node *grSurface::surf_list = nullptr; // the surface list ddgr_surface_node *grSurface::surf_list_cur = nullptr; // current node on list. ddgr_surface *grSurface::scratch_mem_surf = nullptr; void grSurface::init_system() { // allocate our scratch memory surface used in uniblt if (!grSurface::scratch_mem_surf) { grSurface::scratch_mem_surf = new ddgr_surface; memset(grSurface::scratch_mem_surf, 0, sizeof(ddgr_surface)); } atexit(grSurface::close_system); } void grSurface::close_system() { // cleanup our scratch memory surface for blt if (grSurface::scratch_mem_surf) { if (grSurface::scratch_mem_surf->obj != nullptr) gr_mem_surf_Destroy(grSurface::scratch_mem_surf); delete grSurface::scratch_mem_surf; grSurface::scratch_mem_surf = nullptr; } } // --------------------------------------------------------------------------- // grSurface constructor and destructor // --------------------------------------------------------------------------- grSurface::grSurface() { surf_Locked = 0; m_SurfInit = 0; memset(&ddsfObj, 0, sizeof(ddgr_surface)); } grSurface::grSurface(int w, int h, int bpp, unsigned type, unsigned flags, const char *name) { surf_Locked = 0; m_SurfInit = 0; grSurface::create(w, h, bpp, type, flags, name); } grSurface::~grSurface() { grSurface::free(); } void grSurface::create(int w, int h, int bpp, unsigned type, unsigned flags, const char *name) { bool grerr; if (m_SurfInit) grSurface::free(); // create new surface: set up surface structure memset(&ddsfObj, 0, sizeof(ddgr_surface)); ddsfObj.w = w; ddsfObj.h = h; ddsfObj.bpp = bpp; ddsfObj.type = (uint16_t)type; ddsfObj.flags = (uint16_t)flags; if (name) strncpy(ddsfObj.name, name, 15); switch (type) { case SURFTYPE_VIDEOSCREEN: grerr = ddgr_surf_InitVideo(&ddsfObj); break; case SURFTYPE_GENERIC: grerr = ddgr_surf_Create(&ddsfObj); break; case SURFTYPE_MEMORY: grerr = (bool)gr_mem_surf_Create(&ddsfObj); break; default: Int3(); // Invalid!! } if (!grerr) Int3(); add_to_list(&ddsfObj); m_SurfInit = 1; } void grSurface::free() { /* Freeing surface is same regardless of whether it was loaded from a bitmap handle or created ourselves */ if (!m_SurfInit) return; ASSERT(!surf_Locked); switch (ddsfObj.type) { case SURFTYPE_VIDEOSCREEN: ddgr_surf_CloseVideo(&ddsfObj); break; case SURFTYPE_GENERIC: ddgr_surf_Destroy(&ddsfObj); break; case SURFTYPE_MEMORY: gr_mem_surf_Destroy(&ddsfObj); break; default: Int3(); // This really shouldn't happen. bad type! } remove_from_list(&ddsfObj); m_SurfInit = 0; } void grSurface::load(int handle) { /* just load the bitmap from the handle info */ ASSERT(m_SurfInit); grSurface::load((char *)bm_data(handle, 0), bm_w(handle, 0), bm_h(handle, 0), bm_bpp(handle), bm_format(handle)); } void grSurface::load(char *data, int w, int h, int bpp, int format) { // copy defined bitmap given as arguments to this surface, with correct bpp translation. int rowsize; ASSERT(m_SurfInit); /* perform lock, then copy data to this surface, then unlock */ grSurface::lock(&rowsize); switch (ddsfObj.bpp) { case BPP_16: if (bpp == BPP_16) xlat16_16(data, w, h, format); if (bpp == BPP_24) xlat24_16(data, w, h); if (bpp == BPP_32) xlat32_16(data, w, h); break; case BPP_24: if (bpp == BPP_16) xlat16_24(data, w, h); break; default: Int3(); // NOT SUPPORTED YET } grSurface::unlock(); } // loads 8bit palettized data onto this surface. void grSurface::load(int handle, char *pal) { ASSERT(m_SurfInit); grSurface::load((char *)bm_data(handle, 0), bm_w(handle, 0), bm_h(handle, 0), pal); } // loads 8bit palettized data onto this surface. void grSurface::load(char *data, int w, int h, char *pal) { int rowsize; ASSERT(m_SurfInit); /* perform lock, then copy data to this surface, then unlock */ grSurface::lock(&rowsize); switch (ddsfObj.bpp) { case BPP_16: // TODO: This is probably software renderer only? xlat16_16(data, w, h); default: Int3(); // NOT SUPPORTED YET } grSurface::unlock(); } // --------------------------------------------------------------------------- // blt functions:: clear // --------------------------------------------------------------------------- void grSurface::clear(ddgr_color col) { grSurface::clear(0, 0, ddsfObj.w, ddsfObj.h, col); } void grSurface::clear(int l, int t, int w, int h, ddgr_color col) { ASSERT(m_SurfInit); ASSERT(!surf_Locked); switch (ddsfObj.type) { case SURFTYPE_VIDEOSCREEN: case SURFTYPE_GENERIC: // HACK!!!!!!! I MUST CHANGE THIS // --------------------------------------------------------------------------- if (ddsfObj.flags & SURFFLAG_RENDERER) { rend_FillRect(col, l, t, l + w, t + h); return; } // --------------------------------------------------------------------------- ddgr_surf_Clear(&ddsfObj, col, l, t, w, h); break; case SURFTYPE_MEMORY: gr_mem_surf_Clear(&ddsfObj, col, l, t, w, h); break; default: Int3(); } } // --------------------------------------------------------------------------- // blt functions: // these functions must support memory->os, os->memory, os->os, mem->mem blts. // note that (os) blts and (video) blts use the os library. // --------------------------------------------------------------------------- void grSurface::uniblt(int dx, int dy, grSurface *ssf, int sx, int sy, int sw, int sh) { int rowsize; ASSERT(m_SurfInit); // mode 11b = dd_to_dd, 01b = mem_to_dd, 10b = dd_to_mem, 00b = mem_to_mem int mode = 0; ddgr_surface *dsfs = &ddsfObj, *ssfs = &ssf->ddsfObj; bool err; // setup blitting mode if (ssfs->type == SURFTYPE_GENERIC || ssfs->type == SURFTYPE_VIDEOSCREEN) mode = mode | 0x1; if (dsfs->type == SURFTYPE_GENERIC || dsfs->type == SURFTYPE_VIDEOSCREEN) mode = mode | 0x2; /* case 1: os->os just invoke os blitter */ if (mode == 0x3) { ASSERT(!surf_Locked); err = ddgr_surf_Blt(dsfs, dx, dy, ssfs, sx, sy, sw, sh); return; } /* case 2: mem->mem just lock both surfaces and do a memory blt. */ if (mode == 0) { err = gr_mem_surf_Blt(dsfs, dx, dy, ssfs, sx, sy, sw, sh); return; } /* case 3: os->mem This requires locking both surfaces and performing a memory blt. This also means tricking the memory blitter into thinking that the OS surface is a memory surface */ if (mode == 0x1) { char *data = ssf->lock(&rowsize); grSurface::scratch_mem_surf->w = ssfs->w; grSurface::scratch_mem_surf->h = ssfs->h; grSurface::scratch_mem_surf->bpp = ssfs->bpp; grSurface::scratch_mem_surf->type = SURFTYPE_MEMORY; grSurface::scratch_mem_surf->flags = ssfs->flags; gr_mem_surf_Init(grSurface::scratch_mem_surf, data, rowsize); err = gr_mem_surf_Blt(dsfs, dx, dy, grSurface::scratch_mem_surf, sx, sy, sw, sh); ssf->unlock(); return; } /* case 4: mem->os This requires locking both surfaces and performing a memory blt. This also means tricking the memory blitter into thinking that the OS surface is a memory surface */ if (mode == 0x2) { grSurface::lock(&rowsize); grSurface::scratch_mem_surf->w = dsfs->w; grSurface::scratch_mem_surf->h = dsfs->h; grSurface::scratch_mem_surf->bpp = dsfs->bpp; grSurface::scratch_mem_surf->type = SURFTYPE_MEMORY; grSurface::scratch_mem_surf->flags = dsfs->flags; gr_mem_surf_Init(grSurface::scratch_mem_surf, m_DataPtr, m_DataRowsize); err = gr_mem_surf_Blt(grSurface::scratch_mem_surf, dx, dy, ssfs, sx, sy, sw, sh); grSurface::unlock(); return; } } void grSurface::blt(int dx, int dy, grSurface *ssf) { ASSERT(m_SurfInit); grSurface::uniblt(dx, dy, ssf, 0, 0, ssf->width(), ssf->height()); } void grSurface::blt(int dx, int dy, grSurface *ssf, int sx, int sy, int sw, int sh) { ASSERT(m_SurfInit); grSurface::uniblt(dx, dy, ssf, sx, sy, sw, sh); } /* Helper functions replace_color, simply replaces one color in the surface with another */ void grSurface::replace_color(ddgr_color sc, ddgr_color dc) { int rowsize; ASSERT(m_SurfInit); grSurface::lock(&rowsize); switch (ddsfObj.bpp) { case BPP_16: { uint16_t *data = (uint16_t *)m_DataPtr; int rowsize_w = m_DataRowsize / 2; uint16_t scc = XLAT_RGB_TO_16(sc), dcc = XLAT_RGB_TO_16(dc); for (int y = 0; y < ddsfObj.h; y++) { for (int x = 0; x < ddsfObj.w; x++) if (data[x] == scc) data[x] = dcc; data += rowsize_w; } break; } case BPP_32: default: Int3(); } grSurface::unlock(); } // --------------------------------------------------------------------------- // lock and unlock functions // these handle memory and os locks // --------------------------------------------------------------------------- char *grSurface::lock(int *rowsize) { bool grerr; ASSERT(m_SurfInit); switch (ddsfObj.type) { case SURFTYPE_VIDEOSCREEN: case SURFTYPE_GENERIC: grerr = ddgr_surf_Lock(&ddsfObj, (void **)&m_OrigDataPtr, &m_DataRowsize); break; case SURFTYPE_MEMORY: grerr = gr_mem_surf_Lock(&ddsfObj, (void **)&m_OrigDataPtr, &m_DataRowsize); break; default: Int3(); // invalid type!! } m_DataPtr = m_OrigDataPtr; *rowsize = m_DataRowsize; if (!grerr) return nullptr; surf_Locked++; // increment lock counter for this surface rend_SetSoftwareParameters(ddgr_GetAspectRatio(), ddsfObj.w, ddsfObj.h, m_DataRowsize, (uint8_t *)m_OrigDataPtr); return m_DataPtr; } /* do lock based off of x,y offsets */ char *grSurface::lock(int x, int y, int *rowsize) { bool grerr = false; ASSERT(m_SurfInit); switch (ddsfObj.type) { case SURFTYPE_VIDEOSCREEN: case SURFTYPE_GENERIC: grerr = ddgr_surf_Lock(&ddsfObj, (void **)&m_OrigDataPtr, &m_DataRowsize); break; case SURFTYPE_MEMORY: grerr = gr_mem_surf_Lock(&ddsfObj, (void **)&m_OrigDataPtr, &m_DataRowsize); break; default: Int3(); // invalid type!! } *rowsize = m_DataRowsize; if (!grerr) return nullptr; /* calculate the adjusted data pointer based off of x,y,bpp */ char *data = m_OrigDataPtr; data += (y * m_DataRowsize); switch (ddsfObj.bpp) { case BPP_16: data += (x * 2); break; case BPP_32: data += (x * 4); break; default: Int3(); } m_DataPtr = data; surf_Locked++; // increment lock counter for this surface rend_SetSoftwareParameters(ddgr_GetAspectRatio(), ddsfObj.w, ddsfObj.h, m_DataRowsize, (uint8_t *)m_OrigDataPtr); return m_DataPtr; } void grSurface::unlock() { bool grerr; ASSERT(m_SurfInit); ASSERT(surf_Locked); switch (ddsfObj.type) { case SURFTYPE_VIDEOSCREEN: case SURFTYPE_GENERIC: grerr = ddgr_surf_Unlock(&ddsfObj, m_OrigDataPtr); break; case SURFTYPE_MEMORY: grerr = gr_mem_surf_Unlock(&ddsfObj, m_OrigDataPtr); break; default: Int3(); // invalid type!! } surf_Locked--; } // --------------------------------------------------------------------------- // miscellaneous // --------------------------------------------------------------------------- void grSurface::attach_to_window(unsigned handle) { ASSERT(m_SurfInit); ddgr_surf_AttachHandle(&ddsfObj, handle); } // Private functions // conversion_xlat // --------------------------------------------------------------------------- void grSurface::xlat8_16(char *data, int w, int h, char *pal) { /* copy from 8bit source bitmap to destination surface just created and locked */ uint16_t *dptr; char *sptr; int row, col; int rowsize_w; int height, width; uint8_t *upal = (uint8_t *)pal; dptr = (uint16_t *)m_DataPtr; sptr = (char *)data; rowsize_w = m_DataRowsize / 2; height = std::min(h, ddsfObj.h); width = std::min(w, ddsfObj.w); for (row = 0; row < height; row++) { for (col = 0; col < width; col++) { uint8_t spix = sptr[col]; if (spix != 0) { int r = upal[spix * 3] >> 3; int g = upal[spix * 3 + 1] >> 2; int b = upal[spix * 3 + 2] >> 3; uint16_t destpix = (r << 11) | (g << 5) | b; dptr[col] = destpix; } else dptr[col] = 0; } sptr += 256; dptr += rowsize_w; } } void grSurface::xlat16_16(char *data, int w, int h, int format) { /* 16-bit copy from source bitmap to destination surface just created and locked This function performs scaling if the source width and height don't match that of the destinations - JL */ uint16_t *sptr, *dptr; int row, col; int rowsize_w; int height, width; fix xstep = IntToFix(w) / ddsfObj.w; fix ystep = IntToFix(h) / ddsfObj.h; fix fu = 0, fv = 0; dptr = (uint16_t *)m_DataPtr; sptr = (uint16_t *)data; rowsize_w = m_DataRowsize / 2; height = ddsfObj.h; width = ddsfObj.w; // height = SET_MIN(h, ddsfObj.h); // width = SET_MIN(w, ddsfObj.w); for (row = 0; row < height; row++, fv += ystep, fu = 0) { for (col = 0; col < width; col++, fu += xstep) { int newpix; if (format == BITMAP_FORMAT_1555) newpix = sptr[(FixToInt(fv) * w) + FixToInt(fu)]; else { int temp_pix = sptr[(FixToInt(fv) * w) + FixToInt(fu)]; int r = (temp_pix >> 8) & 0xf; int g = (temp_pix >> 4) & 0xf; int b = (temp_pix) & 0xf; r <<= 4; g <<= 4; b <<= 4; newpix = OPAQUE_FLAG | GR_RGB16(r, g, b); } dptr[col] = newpix; } dptr += rowsize_w; } } void grSurface::xlat16_24(char *data, int w, int h) { uint16_t *sptr; char *dptr; int scol, dcol, row; int height, width; dptr = (char *)m_DataPtr; sptr = (uint16_t *)data; height = std::min(h, ddsfObj.h); width = std::min(w, ddsfObj.w); for (row = 0; row < height; row++) { dcol = 0; for (scol = 0; scol < width; scol++) { uint16_t pix; ddgr_color new_color; char r, g, b; pix = sptr[scol]; new_color = GR_16_TO_COLOR(pix); r = GR_COLOR_RED(new_color); g = GR_COLOR_GREEN(new_color); b = GR_COLOR_BLUE(new_color); dptr[dcol++] = r; dptr[dcol++] = g; dptr[dcol++] = b; } sptr += w; dptr += m_DataRowsize; } } void grSurface::xlat24_16(char *data, int w, int h) { char *sptr; uint16_t *dptr; int scol, dcol, row; int rowsize_w, height, width; dptr = (uint16_t *)m_DataPtr; sptr = (char *)data; rowsize_w = m_DataRowsize / 2; height = std::min(h, ddsfObj.h); width = std::min(w, ddsfObj.w); for (row = 0; row < height; row++) { scol = 0; for (dcol = 0; dcol < width; dcol++) { uint16_t pix; char r, g, b; r = sptr[scol++]; g = sptr[scol++]; b = sptr[scol++]; pix = ((uint16_t)(r >> 3) << 11) + ((uint16_t)(g >> 2) << 5) + ((uint16_t)(b >> 3)); dptr[dcol] = pix; } sptr += (w * 3); dptr += m_DataRowsize; } } void grSurface::xlat32_16(char *data, int w, int h) { unsigned *sptr; uint16_t *dptr; int col, row; int rowsize_w, height, width; ASSERT((w % 4) == 0); dptr = (uint16_t *)m_DataPtr; sptr = (unsigned *)data; rowsize_w = m_DataRowsize / 2; height = std::min(h, ddsfObj.h); width = std::min(w, ddsfObj.w); for (row = 0; row < height; row++) { for (col = 0; col < width; col++) { unsigned pix; uint16_t spix; pix = sptr[col]; spix = (uint16_t)(pix & 0x000000f8) >> 3; spix |= (uint16_t)(pix & 0x0000fc00) >> 5; spix |= (uint16_t)(pix & 0x00f80000) >> 8; dptr[col] = spix; } sptr += w; dptr += rowsize_w; } } //----------------------------------------------------------------------------- // surface list functions void grSurface::add_to_list(ddgr_surface *sf) { ddgr_surface_node *node; ASSERT(sf != nullptr); if (!grSurface::surf_list_cur) { // first surface on list node = new ddgr_surface_node; node->sf = sf; node->prev = nullptr; grSurface::surf_list = grSurface::surf_list_cur = node; } else { // next surface on list. node = new ddgr_surface_node; node->sf = sf; node->prev = grSurface::surf_list_cur; grSurface::surf_list_cur = node; } // mprintf(0, "Allocated surface %s [%d].\n", sf->name, DDData.surfs_alloc); } void grSurface::remove_from_list(ddgr_surface *sf) { ddgr_surface_node *node, *next_node; ASSERT(grSurface::surf_list_cur); ASSERT(sf != nullptr); // node is current node. next node is node after it on the list. since we are // going from end to beginning, this may confuse you. node = grSurface::surf_list_cur; next_node = nullptr; // grSurface::surf_list_cur should be at end of list, so no next node while (node) { if (node->sf == sf) { if (next_node) { // delete from within list. next_node->prev = node->prev; } else { // delete from end of list. set list tail to deleted node prev. grSurface::surf_list_cur = node->prev; } delete node; node = nullptr; // break out of loop } else { // go down one node. next_node = node; node = node->prev; } } // mprintf(0, "Freed surface %s [%d].\n", sf->name, DDData.surfs_alloc); }