diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 88f754c6f..b52de0266 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -444,6 +444,9 @@ endif() # Start defining source files for ZDoom set( PLAT_WIN32_SOURCES win32/eaxedit.cpp + win32/fb_d3d9.cpp + win32/fb_d3d9_wipe.cpp + win32/fb_ddraw.cpp win32/hardware.cpp win32/helperthread.cpp win32/i_cd.cpp @@ -636,7 +639,15 @@ set( NOT_COMPILED_SOURCE_FILES ) set( FASTMATH_PCH_SOURCES + r_3dfloors.cpp + r_bsp.cpp + r_draw.cpp + r_drawt.cpp + r_main.cpp + r_plane.cpp + r_segs.cpp r_sky.cpp + r_things.cpp s_advsound.cpp s_environment.cpp s_playlist.cpp diff --git a/src/portal.cpp b/src/portal.cpp index 4ba334225..41f25d620 100644 --- a/src/portal.cpp +++ b/src/portal.cpp @@ -40,6 +40,8 @@ #include "p_local.h" #include "p_blockmap.h" #include "p_lnspec.h" +#include "r_bsp.h" +#include "r_segs.h" #include "c_cvars.h" #include "m_bbox.h" #include "p_tags.h" diff --git a/src/r_3dfloors.cpp b/src/r_3dfloors.cpp index 87c8af618..ee7dc4c81 100644 --- a/src/r_3dfloors.cpp +++ b/src/r_3dfloors.cpp @@ -15,11 +15,6 @@ #include "c_cvars.h" #include "r_3dfloors.h" -CVAR(Int, r_3dfloors, true, 0); - -namespace swrenderer -{ - // external variables int fake3D; F3DFloor *fakeFloor; @@ -33,6 +28,8 @@ HeightLevel *height_cur = NULL; int CurrentMirror = 0; int CurrentSkybox = 0; +CVAR(Int, r_3dfloors, true, 0); + // private variables int height_max = -1; TArray toplist; @@ -56,10 +53,11 @@ void R_3D_AddHeight(secplane_t *add, sector_t *sec) HeightLevel *near; HeightLevel *curr; - double height = add->ZatPoint(ViewPos); - if(height >= sec->CenterCeiling()) return; - if(height <= sec->CenterFloor()) return; + double fheight = add->ZatPoint(ViewPos); + if(fheight >= sec->CenterCeiling()) return; + if(fheight <= sec->CenterFloor()) return; + fixed_t height = FLOAT2FIXED(fheight); fakeActive = 1; if(height_max >= 0) { @@ -163,4 +161,3 @@ void R_3D_LeaveSkybox() CurrentSkybox--; } -} diff --git a/src/r_3dfloors.h b/src/r_3dfloors.h new file mode 100644 index 000000000..cacb97444 --- /dev/null +++ b/src/r_3dfloors.h @@ -0,0 +1,70 @@ +#ifndef SOFT_FAKE3D_H +#define SOFT_FAKE3D_H + +#include "p_3dfloors.h" + +// special types + +struct HeightLevel +{ + double height; + struct HeightLevel *prev; + struct HeightLevel *next; +}; + +struct HeightStack +{ + HeightLevel *height_top; + HeightLevel *height_cur; + int height_max; +}; + +struct ClipStack +{ + short floorclip[MAXWIDTH]; + short ceilingclip[MAXWIDTH]; + F3DFloor *ffloor; + ClipStack *next; +}; + +// external varialbes + +// fake3D flags: +enum +{ + // BSP stage: + FAKE3D_FAKEFLOOR = 1, // fake floor, mark seg as FAKE + FAKE3D_FAKECEILING = 2, // fake ceiling, mark seg as FAKE + FAKE3D_FAKEBACK = 4, // R_AddLine with fake backsector, mark seg as FAKE + FAKE3D_FAKEMASK = 7, + FAKE3D_CLIPBOTFRONT = 8, // use front sector clipping info (bottom) + FAKE3D_CLIPTOPFRONT = 16, // use front sector clipping info (top) + + // sorting stage: + FAKE3D_CLIPBOTTOM = 1, // clip bottom + FAKE3D_CLIPTOP = 2, // clip top + FAKE3D_REFRESHCLIP = 4, // refresh clip info + FAKE3D_DOWN2UP = 8, // rendering from down to up (floors) +}; + +extern int fake3D; +extern F3DFloor *fakeFloor; +extern fixed_t fakeAlpha; +extern int fakeActive; +extern double sclipBottom; +extern double sclipTop; +extern HeightLevel *height_top; +extern HeightLevel *height_cur; +extern int CurrentMirror; +extern int CurrentSkybox; +EXTERN_CVAR(Int, r_3dfloors); + +// functions +void R_3D_DeleteHeights(); +void R_3D_AddHeight(secplane_t *add, sector_t *sec); +void R_3D_NewClip(); +void R_3D_ResetClip(); +void R_3D_EnterSkybox(); +void R_3D_LeaveSkybox(); + +#endif diff --git a/src/r_bsp.cpp b/src/r_bsp.cpp new file mode 100644 index 000000000..88c907e2b --- /dev/null +++ b/src/r_bsp.cpp @@ -0,0 +1,1389 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id:$ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This source is available for distribution and/or modification +// only under the terms of the DOOM Source Code License as +// published by id Software. All rights reserved. +// +// The source is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License +// for more details. +// +// DESCRIPTION: +// BSP traversal, handling of LineSegs for rendering. +// +// This file contains some code from the Build Engine. +// +// "Build Engine & Tools" Copyright (c) 1993-1997 Ken Silverman +// Ken Silverman's official web site: "http://www.advsys.net/ken" +// See the included license file "BUILDLIC.TXT" for license info. +// +//----------------------------------------------------------------------------- + + +#include + +#include "templates.h" + +#include "doomdef.h" + +#include "m_bbox.h" + +#include "i_system.h" +#include "p_lnspec.h" +#include "p_setup.h" + +#include "r_local.h" +#include "r_main.h" +#include "r_plane.h" +#include "r_draw.h" +#include "r_things.h" +#include "r_3dfloors.h" +#include "a_sharedglobal.h" +#include "g_level.h" +#include "p_effect.h" + +// State. +#include "doomstat.h" +#include "r_state.h" +#include "r_bsp.h" +#include "r_segs.h" +#include "v_palette.h" +#include "r_sky.h" +#include "po_man.h" +#include "r_data/colormaps.h" + +seg_t* curline; +side_t* sidedef; +line_t* linedef; +sector_t* frontsector; +sector_t* backsector; + +// killough 4/7/98: indicates doors closed wrt automap bugfix: +int doorclosed; + +bool r_fakingunderwater; + +extern bool rw_prepped; +extern bool rw_havehigh, rw_havelow; +extern int rw_floorstat, rw_ceilstat; +extern bool rw_mustmarkfloor, rw_mustmarkceiling; +extern short walltop[MAXWIDTH]; // [RH] record max extents of wall +extern short wallbottom[MAXWIDTH]; +extern short wallupper[MAXWIDTH]; +extern short walllower[MAXWIDTH]; + +double rw_backcz1, rw_backcz2; +double rw_backfz1, rw_backfz2; +double rw_frontcz1, rw_frontcz2; +double rw_frontfz1, rw_frontfz2; + + +size_t MaxDrawSegs; +drawseg_t *drawsegs; +drawseg_t* firstdrawseg; +drawseg_t* ds_p; + +size_t FirstInterestingDrawseg; +TArray InterestingDrawsegs; + +FWallCoords WallC; +FWallTmapVals WallT; + +static BYTE FakeSide; + +int WindowLeft, WindowRight; +WORD MirrorFlags; +TArray WallPortals(1000); // note: this array needs to go away as reallocation can cause crashes. + + +subsector_t *InSubsector; + +CVAR (Bool, r_drawflat, false, 0) // [RH] Don't texture segs? + + +void R_StoreWallRange (int start, int stop); + +// +// R_ClearDrawSegs +// +void R_ClearDrawSegs (void) +{ + if (drawsegs == NULL) + { + MaxDrawSegs = 256; // [RH] Default. Increased as needed. + firstdrawseg = drawsegs = (drawseg_t *)M_Malloc (MaxDrawSegs * sizeof(drawseg_t)); + } + FirstInterestingDrawseg = 0; + InterestingDrawsegs.Clear (); + ds_p = drawsegs; +} + + + +// +// ClipWallSegment +// Clips the given range of columns +// and includes it in the new clip list. +// +// +// 1/11/98 killough: Since a type "short" is sufficient, we +// should use it, since smaller arrays fit better in cache. +// + +struct cliprange_t +{ + short first, last; // killough +}; + + +// newend is one past the last valid seg +static cliprange_t *newend; +static cliprange_t solidsegs[MAXWIDTH/2+2]; + + + +//========================================================================== +// +// R_ClipWallSegment +// +// Clips the given range of columns, possibly including it in the clip list. +// Handles both windows (e.g. LineDefs with upper and lower textures) and +// solid walls (e.g. single sided LineDefs [middle texture]) that entirely +// block the view. +// +//========================================================================== + +bool R_ClipWallSegment (int first, int last, bool solid) +{ + cliprange_t *next, *start; + int i, j; + bool res = false; + + // Find the first range that touches the range + // (adjacent pixels are touching). + start = solidsegs; + while (start->last < first) + start++; + + if (first < start->first) + { + res = true; + if (last <= start->first) + { + // Post is entirely visible (above start). + R_StoreWallRange (first, last); + if (fake3D & FAKE3D_FAKEMASK) + { + return true; + } + + // Insert a new clippost for solid walls. + if (solid) + { + if (last == start->first) + { + start->first = first; + } + else + { + next = newend; + newend++; + while (next != start) + { + *next = *(next-1); + next--; + } + next->first = first; + next->last = last; + } + } + return true; + } + + // There is a fragment above *start. + R_StoreWallRange (first, start->first); + + // Adjust the clip size for solid walls + if (solid && !(fake3D & FAKE3D_FAKEMASK)) + { + start->first = first; + } + } + + // Bottom contained in start? + if (last <= start->last) + return res; + + next = start; + while (last >= (next+1)->first) + { + // There is a fragment between two posts. + R_StoreWallRange (next->last, (next+1)->first); + next++; + + if (last <= next->last) + { + // Bottom is contained in next. + last = next->last; + goto crunch; + } + } + + // There is a fragment after *next. + R_StoreWallRange (next->last, last); + +crunch: + if (fake3D & FAKE3D_FAKEMASK) + { + return true; + } + if (solid) + { + // Adjust the clip size. + start->last = last; + + if (next != start) + { + // Remove start+1 to next from the clip list, + // because start now covers their area. + for (i = 1, j = (int)(newend - next); j > 0; i++, j--) + { + start[i] = next[i]; + } + newend = start+i; + } + } + return true; +} + +bool R_CheckClipWallSegment (int first, int last) +{ + cliprange_t *start; + + // Find the first range that touches the range + // (adjacent pixels are touching). + start = solidsegs; + while (start->last < first) + start++; + + if (first < start->first) + { + return true; + } + + // Bottom contained in start? + if (last > start->last) + { + return true; + } + + return false; +} + + + +// +// R_ClearClipSegs +// +void R_ClearClipSegs (short left, short right) +{ + solidsegs[0].first = -0x7fff; // new short limit -- killough + solidsegs[0].last = left; + solidsegs[1].first = right; + solidsegs[1].last = 0x7fff; // new short limit -- killough + newend = solidsegs+2; +} + + +// +// killough 3/7/98: Hack floor/ceiling heights for deep water etc. +// +// If player's view height is underneath fake floor, lower the +// drawn ceiling to be just under the floor height, and replace +// the drawn floor and ceiling textures, and light level, with +// the control sector's. +// +// Similar for ceiling, only reflected. +// +// killough 4/11/98, 4/13/98: fix bugs, add 'back' parameter +// + +sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, + int *floorlightlevel, int *ceilinglightlevel, + bool back) +{ + // [RH] allow per-plane lighting + if (floorlightlevel != NULL) + { + *floorlightlevel = sec->GetFloorLight (); + } + + if (ceilinglightlevel != NULL) + { + *ceilinglightlevel = sec->GetCeilingLight (); + } + + FakeSide = FAKED_Center; + + const sector_t *s = sec->GetHeightSec(); + if (s != NULL) + { + sector_t *heightsec = viewsector->heightsec; + bool underwater = r_fakingunderwater || + (heightsec && heightsec->floorplane.PointOnSide(ViewPos) <= 0); + bool doorunderwater = false; + int diffTex = (s->MoreFlags & SECF_CLIPFAKEPLANES); + + // Replace sector being drawn with a copy to be hacked + *tempsec = *sec; + + // Replace floor and ceiling height with control sector's heights. + if (diffTex) + { + if (s->floorplane.CopyPlaneIfValid (&tempsec->floorplane, &sec->ceilingplane)) + { + tempsec->SetTexture(sector_t::floor, s->GetTexture(sector_t::floor), false); + } + else if (s->MoreFlags & SECF_FAKEFLOORONLY) + { + if (underwater) + { + tempsec->ColorMap = s->ColorMap; + if (!(s->MoreFlags & SECF_NOFAKELIGHT)) + { + tempsec->lightlevel = s->lightlevel; + + if (floorlightlevel != NULL) + { + *floorlightlevel = s->GetFloorLight (); + } + + if (ceilinglightlevel != NULL) + { + *ceilinglightlevel = s->GetCeilingLight (); + } + } + FakeSide = FAKED_BelowFloor; + return tempsec; + } + return sec; + } + } + else + { + tempsec->floorplane = s->floorplane; + } + + if (!(s->MoreFlags & SECF_FAKEFLOORONLY)) + { + if (diffTex) + { + if (s->ceilingplane.CopyPlaneIfValid (&tempsec->ceilingplane, &sec->floorplane)) + { + tempsec->SetTexture(sector_t::ceiling, s->GetTexture(sector_t::ceiling), false); + } + } + else + { + tempsec->ceilingplane = s->ceilingplane; + } + } + + double refceilz = s->ceilingplane.ZatPoint(ViewPos); + double orgceilz = sec->ceilingplane.ZatPoint(ViewPos); + +#if 1 + // [RH] Allow viewing underwater areas through doors/windows that + // are underwater but not in a water sector themselves. + // Only works if you cannot see the top surface of any deep water + // sectors at the same time. + if (back && !r_fakingunderwater && curline->frontsector->heightsec == NULL) + { + if (rw_frontcz1 <= s->floorplane.ZatPoint(curline->v1) && + rw_frontcz2 <= s->floorplane.ZatPoint(curline->v2)) + { + // Check that the window is actually visible + for (int z = WallC.sx1; z < WallC.sx2; ++z) + { + if (floorclip[z] > ceilingclip[z]) + { + doorunderwater = true; + r_fakingunderwater = true; + break; + } + } + } + } +#endif + + if (underwater || doorunderwater) + { + tempsec->floorplane = sec->floorplane; + tempsec->ceilingplane = s->floorplane; + tempsec->ceilingplane.FlipVert (); + tempsec->ceilingplane.ChangeHeight(-1 / 65536.); + tempsec->ColorMap = s->ColorMap; + } + + // killough 11/98: prevent sudden light changes from non-water sectors: + if ((underwater && !back) || doorunderwater) + { // head-below-floor hack + tempsec->SetTexture(sector_t::floor, diffTex ? sec->GetTexture(sector_t::floor) : s->GetTexture(sector_t::floor), false); + tempsec->planes[sector_t::floor].xform = s->planes[sector_t::floor].xform; + + tempsec->ceilingplane = s->floorplane; + tempsec->ceilingplane.FlipVert (); + tempsec->ceilingplane.ChangeHeight (-1 / 65536.); + if (s->GetTexture(sector_t::ceiling) == skyflatnum) + { + tempsec->floorplane = tempsec->ceilingplane; + tempsec->floorplane.FlipVert (); + tempsec->floorplane.ChangeHeight (+1 / 65536.); + tempsec->SetTexture(sector_t::ceiling, tempsec->GetTexture(sector_t::floor), false); + tempsec->planes[sector_t::ceiling].xform = tempsec->planes[sector_t::floor].xform; + } + else + { + tempsec->SetTexture(sector_t::ceiling, diffTex ? s->GetTexture(sector_t::floor) : s->GetTexture(sector_t::ceiling), false); + tempsec->planes[sector_t::ceiling].xform = s->planes[sector_t::ceiling].xform; + } + + if (!(s->MoreFlags & SECF_NOFAKELIGHT)) + { + tempsec->lightlevel = s->lightlevel; + + if (floorlightlevel != NULL) + { + *floorlightlevel = s->GetFloorLight (); + } + + if (ceilinglightlevel != NULL) + { + *ceilinglightlevel = s->GetCeilingLight (); + } + } + FakeSide = FAKED_BelowFloor; + } + else if (heightsec && heightsec->ceilingplane.PointOnSide(ViewPos) <= 0 && + orgceilz > refceilz && !(s->MoreFlags & SECF_FAKEFLOORONLY)) + { // Above-ceiling hack + tempsec->ceilingplane = s->ceilingplane; + tempsec->floorplane = s->ceilingplane; + tempsec->floorplane.FlipVert (); + tempsec->floorplane.ChangeHeight (+1 / 65536.); + tempsec->ColorMap = s->ColorMap; + tempsec->ColorMap = s->ColorMap; + + tempsec->SetTexture(sector_t::ceiling, diffTex ? sec->GetTexture(sector_t::ceiling) : s->GetTexture(sector_t::ceiling), false); + tempsec->SetTexture(sector_t::floor, s->GetTexture(sector_t::ceiling), false); + tempsec->planes[sector_t::ceiling].xform = tempsec->planes[sector_t::floor].xform = s->planes[sector_t::ceiling].xform; + + if (s->GetTexture(sector_t::floor) != skyflatnum) + { + tempsec->ceilingplane = sec->ceilingplane; + tempsec->SetTexture(sector_t::floor, s->GetTexture(sector_t::floor), false); + tempsec->planes[sector_t::floor].xform = s->planes[sector_t::floor].xform; + } + + if (!(s->MoreFlags & SECF_NOFAKELIGHT)) + { + tempsec->lightlevel = s->lightlevel; + + if (floorlightlevel != NULL) + { + *floorlightlevel = s->GetFloorLight (); + } + + if (ceilinglightlevel != NULL) + { + *ceilinglightlevel = s->GetCeilingLight (); + } + } + FakeSide = FAKED_AboveCeiling; + } + sec = tempsec; // Use other sector + } + return sec; +} + + +bool R_SkyboxCompare(sector_t *frontsector, sector_t *backsector) +{ + FSectorPortal *frontc = frontsector->GetPortal(sector_t::ceiling); + FSectorPortal *frontf = frontsector->GetPortal(sector_t::floor); + FSectorPortal *backc = backsector->GetPortal(sector_t::ceiling); + FSectorPortal *backf = backsector->GetPortal(sector_t::floor); + + // return true if any of the planes has a linedef-based portal (unless both sides have the same one. + // Ideally this should also check thing based portals but the omission of this check had been abused to hell and back for those. + // (Note: This may require a compatibility option if some maps ran into this for line based portals as well.) + if (!frontc->MergeAllowed()) return (frontc != backc); + if (!frontf->MergeAllowed()) return (frontf != backf); + if (!backc->MergeAllowed()) return true; + if (!backf->MergeAllowed()) return true; + return false; +} + +// +// R_AddLine +// Clips the given segment +// and adds any visible pieces to the line list. +// + +void R_AddLine (seg_t *line) +{ + static sector_t tempsec; // killough 3/8/98: ceiling/water hack + bool solid; + DVector2 pt1, pt2; + + curline = line; + + // [RH] Color if not texturing line + dc_color = (((int)(line - segs) * 8) + 4) & 255; + + pt1 = line->v1->fPos() - ViewPos; + pt2 = line->v2->fPos() - ViewPos; + + // Reject lines not facing viewer + if (pt1.Y * (pt1.X - pt2.X) + pt1.X * (pt2.Y - pt1.Y) >= 0) + return; + + if (WallC.Init(pt1, pt2, 32.0 / (1 << 12))) + return; + + if (WallC.sx1 >= WindowRight || WallC.sx2 <= WindowLeft) + return; + + if (line->linedef == NULL) + { + if (R_CheckClipWallSegment (WallC.sx1, WallC.sx2)) + { + InSubsector->flags |= SSECF_DRAWN; + } + return; + } + + // reject lines that aren't seen from the portal (if any) + // [ZZ] 10.01.2016: lines inside a skybox shouldn't be clipped, although this imposes some limitations on portals in skyboxes. + if (!CurrentPortalInSkybox && CurrentPortal && P_ClipLineToPortal(line->linedef, CurrentPortal->dst, ViewPos)) + return; + + vertex_t *v1, *v2; + + v1 = line->linedef->v1; + v2 = line->linedef->v2; + + if ((v1 == line->v1 && v2 == line->v2) || (v2 == line->v1 && v1 == line->v2)) + { // The seg is the entire wall. + WallT.InitFromWallCoords(&WallC); + } + else + { // The seg is only part of the wall. + if (line->linedef->sidedef[0] != line->sidedef) + { + swapvalues (v1, v2); + } + WallT.InitFromLine(v1->fPos() - ViewPos, v2->fPos() - ViewPos); + } + + if (!(fake3D & FAKE3D_FAKEBACK)) + { + backsector = line->backsector; + } + rw_frontcz1 = frontsector->ceilingplane.ZatPoint(line->v1); + rw_frontfz1 = frontsector->floorplane.ZatPoint(line->v1); + rw_frontcz2 = frontsector->ceilingplane.ZatPoint(line->v2); + rw_frontfz2 = frontsector->floorplane.ZatPoint(line->v2); + + rw_mustmarkfloor = rw_mustmarkceiling = false; + rw_havehigh = rw_havelow = false; + + // Single sided line? + if (backsector == NULL || (line->linedef->isVisualPortal() && line->sidedef == line->linedef->sidedef[0])) + { + solid = true; + } + else + { + // kg3D - its fake, no transfer_heights + if (!(fake3D & FAKE3D_FAKEBACK)) + { // killough 3/8/98, 4/4/98: hack for invisible ceilings / deep water + backsector = R_FakeFlat (backsector, &tempsec, NULL, NULL, true); + } + doorclosed = 0; // killough 4/16/98 + + rw_backcz1 = backsector->ceilingplane.ZatPoint(line->v1); + rw_backfz1 = backsector->floorplane.ZatPoint(line->v1); + rw_backcz2 = backsector->ceilingplane.ZatPoint(line->v2); + rw_backfz2 = backsector->floorplane.ZatPoint(line->v2); + + // Cannot make these walls solid, because it can result in + // sprite clipping problems for sprites near the wall + if (rw_frontcz1 > rw_backcz1 || rw_frontcz2 > rw_backcz2) + { + rw_havehigh = true; + WallMost (wallupper, backsector->ceilingplane, &WallC); + } + if (rw_frontfz1 < rw_backfz1 || rw_frontfz2 < rw_backfz2) + { + rw_havelow = true; + WallMost (walllower, backsector->floorplane, &WallC); + } + + // Closed door. + if ((rw_backcz1 <= rw_frontfz1 && rw_backcz2 <= rw_frontfz2) || + (rw_backfz1 >= rw_frontcz1 && rw_backfz2 >= rw_frontcz2)) + { + solid = true; + } + else if ( + // properly render skies (consider door "open" if both ceilings are sky): + (backsector->GetTexture(sector_t::ceiling) != skyflatnum || frontsector->GetTexture(sector_t::ceiling) != skyflatnum) + + // if door is closed because back is shut: + && rw_backcz1 <= rw_backfz1 && rw_backcz2 <= rw_backfz2 + + // preserve a kind of transparent door/lift special effect: + && ((rw_backcz1 >= rw_frontcz1 && rw_backcz2 >= rw_frontcz2) || line->sidedef->GetTexture(side_t::top).isValid()) + && ((rw_backfz1 <= rw_frontfz1 && rw_backfz2 <= rw_frontfz2) || line->sidedef->GetTexture(side_t::bottom).isValid())) + { + // killough 1/18/98 -- This function is used to fix the automap bug which + // showed lines behind closed doors simply because the door had a dropoff. + // + // It assumes that Doom has already ruled out a door being closed because + // of front-back closure (e.g. front floor is taller than back ceiling). + + // This fixes the automap floor height bug -- killough 1/18/98: + // killough 4/7/98: optimize: save result in doorclosed for use in r_segs.c + doorclosed = true; + solid = true; + } + else if (frontsector->ceilingplane != backsector->ceilingplane || + frontsector->floorplane != backsector->floorplane) + { + // Window. + solid = false; + } + else if (R_SkyboxCompare(frontsector, backsector)) + { + solid = false; + } + else if (backsector->lightlevel != frontsector->lightlevel + || backsector->GetTexture(sector_t::floor) != frontsector->GetTexture(sector_t::floor) + || backsector->GetTexture(sector_t::ceiling) != frontsector->GetTexture(sector_t::ceiling) + || curline->sidedef->GetTexture(side_t::mid).isValid() + + // killough 3/7/98: Take flats offsets into account: + || backsector->planes[sector_t::floor].xform != frontsector->planes[sector_t::floor].xform + || backsector->planes[sector_t::ceiling].xform != frontsector->planes[sector_t::ceiling].xform + + || backsector->GetPlaneLight(sector_t::floor) != frontsector->GetPlaneLight(sector_t::floor) + || backsector->GetPlaneLight(sector_t::ceiling) != frontsector->GetPlaneLight(sector_t::ceiling) + || backsector->GetVisFlags(sector_t::floor) != frontsector->GetVisFlags(sector_t::floor) + || backsector->GetVisFlags(sector_t::ceiling) != frontsector->GetVisFlags(sector_t::ceiling) + + // [RH] Also consider colormaps + || backsector->ColorMap != frontsector->ColorMap + + + + // kg3D - and fake lights + || (frontsector->e && frontsector->e->XFloor.lightlist.Size()) + || (backsector->e && backsector->e->XFloor.lightlist.Size()) + ) + { + solid = false; + } + else + { + // Reject empty lines used for triggers and special events. + // Identical floor and ceiling on both sides, identical light levels + // on both sides, and no middle texture. + + // When using GL nodes, do a clipping test for these lines so we can + // mark their subsectors as visible for automap texturing. + if (hasglnodes && !(InSubsector->flags & SSECF_DRAWN)) + { + if (R_CheckClipWallSegment(WallC.sx1, WallC.sx2)) + { + InSubsector->flags |= SSECF_DRAWN; + } + } + return; + } + } + + rw_prepped = false; + + if (line->linedef->special == Line_Horizon) + { + // Be aware: Line_Horizon does not work properly with sloped planes + clearbufshort (walltop+WallC.sx1, WallC.sx2 - WallC.sx1, centery); + clearbufshort (wallbottom+WallC.sx1, WallC.sx2 - WallC.sx1, centery); + } + else + { + rw_ceilstat = WallMost (walltop, frontsector->ceilingplane, &WallC); + rw_floorstat = WallMost (wallbottom, frontsector->floorplane, &WallC); + + // [RH] treat off-screen walls as solid +#if 0 // Maybe later... + if (!solid) + { + if (rw_ceilstat == 12 && line->sidedef->GetTexture(side_t::top) != 0) + { + rw_mustmarkceiling = true; + solid = true; + } + if (rw_floorstat == 3 && line->sidedef->GetTexture(side_t::bottom) != 0) + { + rw_mustmarkfloor = true; + solid = true; + } + } +#endif + } + + if (R_ClipWallSegment (WallC.sx1, WallC.sx2, solid)) + { + InSubsector->flags |= SSECF_DRAWN; + } +} + +// +// FWallCoords :: Init +// +// Transform and clip coordinates. Returns true if it was clipped away +// +bool FWallCoords::Init(const DVector2 &pt1, const DVector2 &pt2, double too_close) +{ + tleft.X = float(pt1.X * ViewSin - pt1.Y * ViewCos); + tright.X = float(pt2.X * ViewSin - pt2.Y * ViewCos); + + tleft.Y = float(pt1.X * ViewTanCos + pt1.Y * ViewTanSin); + tright.Y = float(pt2.X * ViewTanCos + pt2.Y * ViewTanSin); + + if (MirrorFlags & RF_XFLIP) + { + float t = -tleft.X; + tleft.X = -tright.X; + tright.X = t; + swapvalues(tleft.Y, tright.Y); + } + + if (tleft.X >= -tleft.Y) + { + if (tleft.X > tleft.Y) return true; // left edge is off the right side + if (tleft.Y == 0) return true; + sx1 = xs_RoundToInt(CenterX + tleft.X * CenterX / tleft.Y); + sz1 = tleft.Y; + } + else + { + if (tright.X < -tright.Y) return true; // wall is off the left side + float den = tleft.X - tright.X - tright.Y + tleft.Y; + if (den == 0) return true; + sx1 = 0; + sz1 = tleft.Y + (tright.Y - tleft.Y) * (tleft.X + tleft.Y) / den; + } + + if (sz1 < too_close) + return true; + + if (tright.X <= tright.Y) + { + if (tright.X < -tright.Y) return true; // right edge is off the left side + if (tright.Y == 0) return true; + sx2 = xs_RoundToInt(CenterX + tright.X * CenterX / tright.Y); + sz2 = tright.Y; + } + else + { + if (tleft.X > tleft.Y) return true; // wall is off the right side + float den = tright.Y - tleft.Y - tright.X + tleft.X; + if (den == 0) return true; + sx2 = viewwidth; + sz2 = tleft.Y + (tright.Y - tleft.Y) * (tleft.X - tleft.Y) / den; + } + + if (sz2 < too_close || sx2 <= sx1) + return true; + + return false; +} + +void FWallTmapVals::InitFromWallCoords(const FWallCoords *wallc) +{ + const FVector2 *left = &wallc->tleft; + const FVector2 *right = &wallc->tright; + + if (MirrorFlags & RF_XFLIP) + { + swapvalues(left, right); + } + UoverZorg = left->X * centerx; + UoverZstep = -left->Y; + InvZorg = (left->X - right->X) * centerx; + InvZstep = right->Y - left->Y; +} + +void FWallTmapVals::InitFromLine(const DVector2 &left, const DVector2 &right) +{ // Coordinates should have already had viewx,viewy subtracted + double fullx1 = left.X * ViewSin - left.Y * ViewCos; + double fullx2 = right.X * ViewSin - right.Y * ViewCos; + double fully1 = left.X * ViewTanCos + left.Y * ViewTanSin; + double fully2 = right.X * ViewTanCos + right.Y * ViewTanSin; + + if (MirrorFlags & RF_XFLIP) + { + fullx1 = -fullx1; + fullx2 = -fullx2; + } + + UoverZorg = float(fullx1 * centerx); + UoverZstep = float(-fully1); + InvZorg = float((fullx1 - fullx2) * centerx); + InvZstep = float(fully2 - fully1); +} + +// +// R_CheckBBox +// Checks BSP node/subtree bounding box. +// Returns true if some part of the bbox might be visible. +// +extern "C" const int checkcoord[12][4] = +{ + {3,0,2,1}, + {3,0,2,0}, + {3,1,2,0}, + {0}, + {2,0,2,1}, + {0,0,0,0}, + {3,1,3,0}, + {0}, + {2,0,3,1}, + {2,1,3,1}, + {2,1,3,0} +}; + + +static bool R_CheckBBox (fixed_t *bspcoord) // killough 1/28/98: static +{ + int boxx; + int boxy; + int boxpos; + + double x1, y1, x2, y2; + double rx1, ry1, rx2, ry2; + int sx1, sx2; + + cliprange_t* start; + + // Find the corners of the box + // that define the edges from current viewpoint. + if (ViewPos.X <= FIXED2DBL(bspcoord[BOXLEFT])) + boxx = 0; + else if (ViewPos.X < FIXED2DBL(bspcoord[BOXRIGHT])) + boxx = 1; + else + boxx = 2; + + if (ViewPos.Y >= FIXED2DBL(bspcoord[BOXTOP])) + boxy = 0; + else if (ViewPos.Y > FIXED2DBL(bspcoord[BOXBOTTOM])) + boxy = 1; + else + boxy = 2; + + boxpos = (boxy<<2)+boxx; + if (boxpos == 5) + return true; + + x1 = FIXED2DBL(bspcoord[checkcoord[boxpos][0]]) - ViewPos.X; + y1 = FIXED2DBL(bspcoord[checkcoord[boxpos][1]]) - ViewPos.Y; + x2 = FIXED2DBL(bspcoord[checkcoord[boxpos][2]]) - ViewPos.X; + y2 = FIXED2DBL(bspcoord[checkcoord[boxpos][3]]) - ViewPos.Y; + + // check clip list for an open space + + // Sitting on a line? + if (y1 * (x1 - x2) + x1 * (y2 - y1) >= -EQUAL_EPSILON) + return true; + + rx1 = x1 * ViewSin - y1 * ViewCos; + rx2 = x2 * ViewSin - y2 * ViewCos; + ry1 = x1 * ViewTanCos + y1 * ViewTanSin; + ry2 = x2 * ViewTanCos + y2 * ViewTanSin; + + if (MirrorFlags & RF_XFLIP) + { + double t = -rx1; + rx1 = -rx2; + rx2 = t; + swapvalues(ry1, ry2); + } + + if (rx1 >= -ry1) + { + if (rx1 > ry1) return false; // left edge is off the right side + if (ry1 == 0) return false; + sx1 = xs_RoundToInt(CenterX + rx1 * CenterX / ry1); + } + else + { + if (rx2 < -ry2) return false; // wall is off the left side + if (rx1 - rx2 - ry2 + ry1 == 0) return false; // wall does not intersect view volume + sx1 = 0; + } + + if (rx2 <= ry2) + { + if (rx2 < -ry2) return false; // right edge is off the left side + if (ry2 == 0) return false; + sx2 = xs_RoundToInt(CenterX + rx2 * CenterX / ry2); + } + else + { + if (rx1 > ry1) return false; // wall is off the right side + if (ry2 - ry1 - rx2 + rx1 == 0) return false; // wall does not intersect view volume + sx2 = viewwidth; + } + + // Find the first clippost that touches the source post + // (adjacent pixels are touching). + + // Does not cross a pixel. + if (sx2 <= sx1) + return false; + + start = solidsegs; + while (start->last < sx2) + start++; + + if (sx1 >= start->first && sx2 <= start->last) + { + // The clippost contains the new span. + return false; + } + + return true; +} + + +void R_Subsector (subsector_t *sub); +static void R_AddPolyobjs(subsector_t *sub) +{ + if (sub->BSP == NULL || sub->BSP->bDirty) + { + sub->BuildPolyBSP(); + } + if (sub->BSP->Nodes.Size() == 0) + { + R_Subsector(&sub->BSP->Subsectors[0]); + } + else + { + R_RenderBSPNode(&sub->BSP->Nodes.Last()); + } +} + +// kg3D - add fake segs, never rendered +void R_FakeDrawLoop(subsector_t *sub) +{ + int count; + seg_t* line; + + count = sub->numlines; + line = sub->firstline; + + while (count--) + { + if ((line->sidedef) && !(line->sidedef->Flags & WALLF_POLYOBJ)) + { + R_AddLine (line); + } + line++; + } +} + +// +// R_Subsector +// Determine floor/ceiling planes. +// Add sprites of things in sector. +// Draw one or more line segments. +// +void R_Subsector (subsector_t *sub) +{ + int count; + seg_t* line; + sector_t tempsec; // killough 3/7/98: deep water hack + int floorlightlevel; // killough 3/16/98: set floor lightlevel + int ceilinglightlevel; // killough 4/11/98 + bool outersubsector; + int fll, cll, position; + FSectorPortal *portal; + + // kg3D - fake floor stuff + visplane_t *backupfp; + visplane_t *backupcp; + //secplane_t templane; + lightlist_t *light; + + if (InSubsector != NULL) + { // InSubsector is not NULL. This means we are rendering from a mini-BSP. + outersubsector = false; + } + else + { + outersubsector = true; + InSubsector = sub; + } + +#ifdef RANGECHECK + if (outersubsector && sub - subsectors >= (ptrdiff_t)numsubsectors) + I_Error ("R_Subsector: ss %ti with numss = %i", sub - subsectors, numsubsectors); +#endif + + assert(sub->sector != NULL); + + if (sub->polys) + { // Render the polyobjs in the subsector first + R_AddPolyobjs(sub); + if (outersubsector) + { + InSubsector = NULL; + } + return; + } + + frontsector = sub->sector; + frontsector->MoreFlags |= SECF_DRAWN; + count = sub->numlines; + line = sub->firstline; + + // killough 3/8/98, 4/4/98: Deep water / fake ceiling effect + frontsector = R_FakeFlat(frontsector, &tempsec, &floorlightlevel, + &ceilinglightlevel, false); // killough 4/11/98 + + fll = floorlightlevel; + cll = ceilinglightlevel; + + // [RH] set foggy flag + foggy = level.fadeto || frontsector->ColorMap->Fade || (level.flags & LEVEL_HASFADETABLE); + r_actualextralight = foggy ? 0 : extralight << 4; + + // kg3D - fake lights + if (fixedlightlev < 0 && frontsector->e && frontsector->e->XFloor.lightlist.Size()) + { + light = P_GetPlaneLight(frontsector, &frontsector->ceilingplane, false); + basecolormap = light->extra_colormap; + // If this is the real ceiling, don't discard plane lighting R_FakeFlat() + // accounted for. + if (light->p_lightlevel != &frontsector->lightlevel) + { + ceilinglightlevel = *light->p_lightlevel; + } + } + else + { + basecolormap = frontsector->ColorMap; + } + + portal = frontsector->ValidatePortal(sector_t::ceiling); + + ceilingplane = frontsector->ceilingplane.PointOnSide(ViewPos) > 0 || + frontsector->GetTexture(sector_t::ceiling) == skyflatnum || + portal != NULL || + (frontsector->heightsec && + !(frontsector->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC) && + frontsector->heightsec->GetTexture(sector_t::floor) == skyflatnum) ? + R_FindPlane(frontsector->ceilingplane, // killough 3/8/98 + frontsector->GetTexture(sector_t::ceiling), + ceilinglightlevel + r_actualextralight, // killough 4/11/98 + frontsector->GetAlpha(sector_t::ceiling), + !!(frontsector->GetFlags(sector_t::ceiling) & PLANEF_ADDITIVE), + frontsector->planes[sector_t::ceiling].xform, + frontsector->sky, + portal + ) : NULL; + + if (fixedlightlev < 0 && frontsector->e && frontsector->e->XFloor.lightlist.Size()) + { + light = P_GetPlaneLight(frontsector, &frontsector->floorplane, false); + basecolormap = light->extra_colormap; + // If this is the real floor, don't discard plane lighting R_FakeFlat() + // accounted for. + if (light->p_lightlevel != &frontsector->lightlevel) + { + floorlightlevel = *light->p_lightlevel; + } + } + else + { + basecolormap = frontsector->ColorMap; + } + + // killough 3/7/98: Add (x,y) offsets to flats, add deep water check + // killough 3/16/98: add floorlightlevel + // killough 10/98: add support for skies transferred from sidedefs + portal = frontsector->ValidatePortal(sector_t::floor); + + floorplane = frontsector->floorplane.PointOnSide(ViewPos) > 0 || // killough 3/7/98 + frontsector->GetTexture(sector_t::floor) == skyflatnum || + portal != NULL || + (frontsector->heightsec && + !(frontsector->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC) && + frontsector->heightsec->GetTexture(sector_t::ceiling) == skyflatnum) ? + R_FindPlane(frontsector->floorplane, + frontsector->GetTexture(sector_t::floor), + floorlightlevel + r_actualextralight, // killough 3/16/98 + frontsector->GetAlpha(sector_t::floor), + !!(frontsector->GetFlags(sector_t::floor) & PLANEF_ADDITIVE), + frontsector->planes[sector_t::floor].xform, + frontsector->sky, + portal + ) : NULL; + + // kg3D - fake planes rendering + if (r_3dfloors && frontsector->e && frontsector->e->XFloor.ffloors.Size()) + { + backupfp = floorplane; + backupcp = ceilingplane; + // first check all floors + for (int i = 0; i < (int)frontsector->e->XFloor.ffloors.Size(); i++) + { + fakeFloor = frontsector->e->XFloor.ffloors[i]; + if (!(fakeFloor->flags & FF_EXISTS)) continue; + if (!fakeFloor->model) continue; + if (fakeFloor->bottom.plane->isSlope()) continue; + if (!(fakeFloor->flags & FF_NOSHADE) || (fakeFloor->flags & (FF_RENDERPLANES|FF_RENDERSIDES))) + { + R_3D_AddHeight(fakeFloor->top.plane, frontsector); + } + if (!(fakeFloor->flags & FF_RENDERPLANES)) continue; + if (fakeFloor->alpha == 0) continue; + if (fakeFloor->flags & FF_THISINSIDE && fakeFloor->flags & FF_INVERTSECTOR) continue; + fakeAlpha = MIN(Scale(fakeFloor->alpha, OPAQUE, 255), OPAQUE); + if (fakeFloor->validcount != validcount) + { + fakeFloor->validcount = validcount; + R_3D_NewClip(); + } + double fakeHeight = fakeFloor->top.plane->ZatPoint(frontsector->centerspot); + if (fakeHeight < ViewPos.Z && + fakeHeight > frontsector->floorplane.ZatPoint(frontsector->centerspot)) + { + fake3D = FAKE3D_FAKEFLOOR; + tempsec = *fakeFloor->model; + tempsec.floorplane = *fakeFloor->top.plane; + tempsec.ceilingplane = *fakeFloor->bottom.plane; + if (!(fakeFloor->flags & FF_THISINSIDE) && !(fakeFloor->flags & FF_INVERTSECTOR)) + { + tempsec.SetTexture(sector_t::floor, tempsec.GetTexture(sector_t::ceiling)); + position = sector_t::ceiling; + } else position = sector_t::floor; + frontsector = &tempsec; + + if (fixedlightlev < 0 && sub->sector->e->XFloor.lightlist.Size()) + { + light = P_GetPlaneLight(sub->sector, &frontsector->floorplane, false); + basecolormap = light->extra_colormap; + floorlightlevel = *light->p_lightlevel; + } + + ceilingplane = NULL; + floorplane = R_FindPlane(frontsector->floorplane, + frontsector->GetTexture(sector_t::floor), + floorlightlevel + r_actualextralight, // killough 3/16/98 + frontsector->GetAlpha(sector_t::floor), + !!(fakeFloor->flags & FF_ADDITIVETRANS), + frontsector->planes[position].xform, + frontsector->sky, + NULL); + + R_FakeDrawLoop(sub); + fake3D = 0; + frontsector = sub->sector; + } + } + // and now ceilings + for (unsigned int i = 0; i < frontsector->e->XFloor.ffloors.Size(); i++) + { + fakeFloor = frontsector->e->XFloor.ffloors[i]; + if (!(fakeFloor->flags & FF_EXISTS)) continue; + if (!fakeFloor->model) continue; + if (fakeFloor->top.plane->isSlope()) continue; + if (!(fakeFloor->flags & FF_NOSHADE) || (fakeFloor->flags & (FF_RENDERPLANES|FF_RENDERSIDES))) + { + R_3D_AddHeight(fakeFloor->bottom.plane, frontsector); + } + if (!(fakeFloor->flags & FF_RENDERPLANES)) continue; + if (fakeFloor->alpha == 0) continue; + if (!(fakeFloor->flags & FF_THISINSIDE) && (fakeFloor->flags & (FF_SWIMMABLE|FF_INVERTSECTOR)) == (FF_SWIMMABLE|FF_INVERTSECTOR)) continue; + fakeAlpha = MIN(Scale(fakeFloor->alpha, OPAQUE, 255), OPAQUE); + + if (fakeFloor->validcount != validcount) + { + fakeFloor->validcount = validcount; + R_3D_NewClip(); + } + double fakeHeight = fakeFloor->bottom.plane->ZatPoint(frontsector->centerspot); + if (fakeHeight > ViewPos.Z && + fakeHeight < frontsector->ceilingplane.ZatPoint(frontsector->centerspot)) + { + fake3D = FAKE3D_FAKECEILING; + tempsec = *fakeFloor->model; + tempsec.floorplane = *fakeFloor->top.plane; + tempsec.ceilingplane = *fakeFloor->bottom.plane; + if ((!(fakeFloor->flags & FF_THISINSIDE) && !(fakeFloor->flags & FF_INVERTSECTOR)) || + (fakeFloor->flags & FF_THISINSIDE && fakeFloor->flags & FF_INVERTSECTOR)) + { + tempsec.SetTexture(sector_t::ceiling, tempsec.GetTexture(sector_t::floor)); + position = sector_t::floor; + } else position = sector_t::ceiling; + frontsector = &tempsec; + + tempsec.ceilingplane.ChangeHeight(-1 / 65536.); + if (fixedlightlev < 0 && sub->sector->e->XFloor.lightlist.Size()) + { + light = P_GetPlaneLight(sub->sector, &frontsector->ceilingplane, false); + basecolormap = light->extra_colormap; + ceilinglightlevel = *light->p_lightlevel; + } + tempsec.ceilingplane.ChangeHeight(1 / 65536.); + + floorplane = NULL; + ceilingplane = R_FindPlane(frontsector->ceilingplane, // killough 3/8/98 + frontsector->GetTexture(sector_t::ceiling), + ceilinglightlevel + r_actualextralight, // killough 4/11/98 + frontsector->GetAlpha(sector_t::ceiling), + !!(fakeFloor->flags & FF_ADDITIVETRANS), + frontsector->planes[position].xform, + frontsector->sky, + NULL); + + R_FakeDrawLoop(sub); + fake3D = 0; + frontsector = sub->sector; + } + } + fakeFloor = NULL; + floorplane = backupfp; + ceilingplane = backupcp; + } + + basecolormap = frontsector->ColorMap; + floorlightlevel = fll; + ceilinglightlevel = cll; + + // killough 9/18/98: Fix underwater slowdown, by passing real sector + // instead of fake one. Improve sprite lighting by basing sprite + // lightlevels on floor & ceiling lightlevels in the surrounding area. + // [RH] Handle sprite lighting like Duke 3D: If the ceiling is a sky, sprites are lit by + // it, otherwise they are lit by the floor. + R_AddSprites (sub->sector, frontsector->GetTexture(sector_t::ceiling) == skyflatnum ? + ceilinglightlevel : floorlightlevel, FakeSide); + + // [RH] Add particles + if ((unsigned int)(sub - subsectors) < (unsigned int)numsubsectors) + { // Only do it for the main BSP. + int shade = LIGHT2SHADE((floorlightlevel + ceilinglightlevel)/2 + r_actualextralight); + for (WORD i = ParticlesInSubsec[(unsigned int)(sub-subsectors)]; i != NO_PARTICLE; i = Particles[i].snext) + { + R_ProjectParticle (Particles + i, subsectors[sub-subsectors].sector, shade, FakeSide); + } + } + + count = sub->numlines; + line = sub->firstline; + + while (count--) + { + if (!outersubsector || line->sidedef == NULL || !(line->sidedef->Flags & WALLF_POLYOBJ)) + { + // kg3D - fake planes bounding calculation + if (r_3dfloors && line->backsector && frontsector->e && line->backsector->e->XFloor.ffloors.Size()) + { + backupfp = floorplane; + backupcp = ceilingplane; + floorplane = NULL; + ceilingplane = NULL; + for (unsigned int i = 0; i < line->backsector->e->XFloor.ffloors.Size(); i++) + { + fakeFloor = line->backsector->e->XFloor.ffloors[i]; + if (!(fakeFloor->flags & FF_EXISTS)) continue; + if (!(fakeFloor->flags & FF_RENDERPLANES)) continue; + if (!fakeFloor->model) continue; + fake3D = FAKE3D_FAKEBACK; + tempsec = *fakeFloor->model; + tempsec.floorplane = *fakeFloor->top.plane; + tempsec.ceilingplane = *fakeFloor->bottom.plane; + backsector = &tempsec; + if (fakeFloor->validcount != validcount) + { + fakeFloor->validcount = validcount; + R_3D_NewClip(); + } + if (frontsector->CenterFloor() >= backsector->CenterFloor()) + { + fake3D |= FAKE3D_CLIPBOTFRONT; + } + if (frontsector->CenterCeiling() <= backsector->CenterCeiling()) + { + fake3D |= FAKE3D_CLIPTOPFRONT; + } + R_AddLine(line); // fake + } + fakeFloor = NULL; + fake3D = 0; + floorplane = backupfp; + ceilingplane = backupcp; + } + R_AddLine (line); // now real + } + line++; + } + if (outersubsector) + { + InSubsector = NULL; + } +} + +// +// RenderBSPNode +// Renders all subsectors below a given node, traversing subtree recursively. +// Just call with BSP root and -1. +// killough 5/2/98: reformatted, removed tail recursion + +void R_RenderBSPNode (void *node) +{ + if (numnodes == 0) + { + R_Subsector (subsectors); + return; + } + while (!((size_t)node & 1)) // Keep going until found a subsector + { + node_t *bsp = (node_t *)node; + + // Decide which side the view point is on. + int side = R_PointOnSide (ViewPos, bsp); + + // Recursively divide front space (toward the viewer). + R_RenderBSPNode (bsp->children[side]); + + // Possibly divide back space (away from the viewer). + side ^= 1; + if (!R_CheckBBox (bsp->bbox[side])) + return; + + node = bsp->children[side]; + } + R_Subsector ((subsector_t *)((BYTE *)node - 1)); +} diff --git a/src/r_bsp.h b/src/r_bsp.h new file mode 100644 index 000000000..48ca7565b --- /dev/null +++ b/src/r_bsp.h @@ -0,0 +1,123 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id:$ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This source is available for distribution and/or modification +// only under the terms of the DOOM Source Code License as +// published by id Software. All rights reserved. +// +// The source is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License +// for more details. +// +// DESCRIPTION: +// Refresh module, BSP traversal and handling. +// +//----------------------------------------------------------------------------- + + +#ifndef __R_BSP__ +#define __R_BSP__ + +#include "tarray.h" +#include +#include "r_defs.h" + +// The 3072 below is just an arbitrary value picked to avoid +// drawing lines the player is too close to that would overflow +// the texture calculations. +#define TOO_CLOSE_Z (3072.0 / (1<<12)) + +struct FWallCoords +{ + FVector2 tleft; // coords at left of wall in view space rx1,ry1 + FVector2 tright; // coords at right of wall in view space rx2,ry2 + + float sz1, sz2; // depth at left, right of wall in screen space yb1,yb2 + short sx1, sx2; // x coords at left, right of wall in screen space xb1,xb2 + + bool Init(const DVector2 &pt1, const DVector2 &pt2, double too_close); +}; + +struct FWallTmapVals +{ + float UoverZorg, UoverZstep; + float InvZorg, InvZstep; + + void InitFromWallCoords(const FWallCoords *wallc); + void InitFromLine(const DVector2 &left, const DVector2 &right); +}; + +extern FWallCoords WallC; +extern FWallTmapVals WallT; + +enum +{ + FAKED_Center, + FAKED_BelowFloor, + FAKED_AboveCeiling +}; + +struct drawseg_t +{ + seg_t* curline; + float light, lightstep; + float iscale, iscalestep; + short x1, x2; // Same as sx1 and sx2, but clipped to the drawseg + short sx1, sx2; // left, right of parent seg on screen + float sz1, sz2; // z for left, right of parent seg on screen + float siz1, siz2; // 1/z for left, right of parent seg on screen + float cx, cy, cdx, cdy; + float yscale; + BYTE silhouette; // 0=none, 1=bottom, 2=top, 3=both + BYTE bFogBoundary; + BYTE bFakeBoundary; // for fake walls + int shade; +// Pointers to lists for sprite clipping, +// all three adjusted so [x1] is first value. + ptrdiff_t sprtopclip; // type short + ptrdiff_t sprbottomclip; // type short + ptrdiff_t maskedtexturecol; // type short + ptrdiff_t swall; // type float + int fake; // ident fake drawseg, don't draw and clip sprites +// backups + ptrdiff_t bkup; // sprtopclip backup, for mid and fake textures + FWallTmapVals tmapvals; + int CurrentPortalUniq; // [ZZ] to identify the portal that this drawseg is in. used for sprite clipping. +}; + + +extern seg_t* curline; +extern side_t* sidedef; +extern line_t* linedef; +extern sector_t* frontsector; +extern sector_t* backsector; + +extern drawseg_t *drawsegs; +extern drawseg_t *firstdrawseg; +extern drawseg_t* ds_p; + +extern TArray InterestingDrawsegs; // drawsegs that have something drawn on them +extern size_t FirstInterestingDrawseg; + +extern int WindowLeft, WindowRight; +extern WORD MirrorFlags; + +typedef void (*drawfunc_t) (int start, int stop); + +EXTERN_CVAR (Bool, r_drawflat) // [RH] Don't texture segs? + +// BSP? +void R_ClearClipSegs (short left, short right); +void R_ClearDrawSegs (); +void R_RenderBSPNode (void *node); + +// killough 4/13/98: fake floors/ceilings for deep water / fake ceilings: +sector_t *R_FakeFlat(sector_t *, sector_t *, int *, int *, bool); + + +#endif diff --git a/src/r_draw.cpp b/src/r_draw.cpp new file mode 100644 index 000000000..c58175912 --- /dev/null +++ b/src/r_draw.cpp @@ -0,0 +1,2489 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id:$ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This source is available for distribution and/or modification +// only under the terms of the DOOM Source Code License as +// published by id Software. All rights reserved. +// +// The source is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License +// for more details. +// +// $Log:$ +// +// DESCRIPTION: +// The actual span/column drawing functions. +// Here find the main potential for optimization, +// e.g. inline assembly, different algorithms. +// +//----------------------------------------------------------------------------- + +#include + +#include "templates.h" +#include "doomdef.h" +#include "i_system.h" +#include "w_wad.h" +#include "r_local.h" +#include "v_video.h" +#include "doomstat.h" +#include "st_stuff.h" +#include "g_game.h" +#include "g_level.h" +#include "r_data/r_translate.h" +#include "v_palette.h" +#include "r_data/colormaps.h" + +#include "gi.h" +#include "stats.h" +#include "x86.h" + +#undef RANGECHECK + +// status bar height at bottom of screen +// [RH] status bar position at bottom of screen +extern int ST_Y; + +// +// All drawing to the view buffer is accomplished in this file. +// The other refresh files only know about ccordinates, +// not the architecture of the frame buffer. +// Conveniently, the frame buffer is a linear one, +// and we need only the base address, +// and the total size == width*height*depth/8., +// + +BYTE* viewimage; +extern "C" { +int halfviewwidth; +int ylookup[MAXHEIGHT]; +BYTE *dc_destorg; +} +int scaledviewwidth; + +// [RH] Pointers to the different column drawers. +// These get changed depending on the current +// screen depth and asm/no asm. +void (*R_DrawColumnHoriz)(void); +void (*R_DrawColumn)(void); +void (*R_DrawFuzzColumn)(void); +void (*R_DrawTranslatedColumn)(void); +void (*R_DrawShadedColumn)(void); +void (*R_DrawSpan)(void); +void (*R_DrawSpanMasked)(void); +void (*R_DrawSpanTranslucent)(void); +void (*R_DrawSpanMaskedTranslucent)(void); +void (*R_DrawSpanAddClamp)(void); +void (*R_DrawSpanMaskedAddClamp)(void); +void (*rt_map4cols)(int,int,int); + +// +// R_DrawColumn +// Source is the top of the column to scale. +// +double dc_texturemid; +extern "C" { +int dc_pitch=0xABadCafe; // [RH] Distance between rows + +lighttable_t* dc_colormap; +int dc_x; +int dc_yl; +int dc_yh; +fixed_t dc_iscale; +fixed_t dc_texturefrac; +int dc_color; // [RH] Color for column filler +DWORD dc_srccolor; +DWORD *dc_srcblend; // [RH] Source and destination +DWORD *dc_destblend; // blending lookups + +// first pixel in a column (possibly virtual) +const BYTE* dc_source; + +BYTE* dc_dest; +int dc_count; + +DWORD vplce[4]; +DWORD vince[4]; +BYTE* palookupoffse[4]; +const BYTE* bufplce[4]; + +// just for profiling +int dccount; +} + +int dc_fillcolor; +BYTE *dc_translation; +BYTE shadetables[NUMCOLORMAPS*16*256]; +FDynamicColormap ShadeFakeColormap[16]; +BYTE identitymap[256]; + +EXTERN_CVAR (Int, r_columnmethod) + + +void R_InitShadeMaps() +{ + int i,j; + // set up shading tables for shaded columns + // 16 colormap sets, progressing from full alpha to minimum visible alpha + + BYTE *table = shadetables; + + // Full alpha + for (i = 0; i < 16; ++i) + { + ShadeFakeColormap[i].Color = ~0u; + ShadeFakeColormap[i].Desaturate = ~0u; + ShadeFakeColormap[i].Next = NULL; + ShadeFakeColormap[i].Maps = table; + + for (j = 0; j < NUMCOLORMAPS; ++j) + { + int a = (NUMCOLORMAPS - j) * 256 / NUMCOLORMAPS * (16-i); + for (int k = 0; k < 256; ++k) + { + BYTE v = (((k+2) * a) + 256) >> 14; + table[k] = MIN (v, 64); + } + table += 256; + } + } + for (i = 0; i < NUMCOLORMAPS*16*256; ++i) + { + assert(shadetables[i] <= 64); + } + + // Set up a guaranteed identity map + for (i = 0; i < 256; ++i) + { + identitymap[i] = i; + } +} + +/************************************/ +/* */ +/* Palettized drawers (C versions) */ +/* */ +/************************************/ + +#ifndef X86_ASM +// +// A column is a vertical slice/span from a wall texture that, +// given the DOOM style restrictions on the view orientation, +// will always have constant z depth. +// Thus a special case loop for very fast rendering can +// be used. It has also been used with Wolfenstein 3D. +// +void R_DrawColumnP_C (void) +{ + int count; + BYTE* dest; + fixed_t frac; + fixed_t fracstep; + + count = dc_count; + + // Zero length, column does not exceed a pixel. + if (count <= 0) + return; + + // Framebuffer destination address. + dest = dc_dest; + + // Determine scaling, + // which is the only mapping to be done. + fracstep = dc_iscale; + frac = dc_texturefrac; + + { + // [RH] Get local copies of these variables so that the compiler + // has a better chance of optimizing this well. + BYTE *colormap = dc_colormap; + const BYTE *source = dc_source; + int pitch = dc_pitch; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. + do + { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + *dest = colormap[source[frac>>FRACBITS]]; + + dest += pitch; + frac += fracstep; + + } while (--count); + } +} +#endif + +// [RH] Just fills a column with a color +void R_FillColumnP (void) +{ + int count; + BYTE* dest; + + count = dc_count; + + if (count <= 0) + return; + + dest = dc_dest; + + { + int pitch = dc_pitch; + BYTE color = dc_color; + + do + { + *dest = color; + dest += pitch; + } while (--count); + } +} + +void R_FillAddColumn (void) +{ + int count; + BYTE *dest; + + count = dc_count; + if (count <= 0) + return; + + dest = dc_dest; + DWORD *bg2rgb; + DWORD fg; + + bg2rgb = dc_destblend; + fg = dc_srccolor; + int pitch = dc_pitch; + + do + { + DWORD bg; + bg = (fg + bg2rgb[*dest]) | 0x1f07c1f; + *dest = RGB32k.All[bg & (bg>>15)]; + dest += pitch; + } while (--count); + +} + +void R_FillAddClampColumn (void) +{ + int count; + BYTE *dest; + + count = dc_count; + if (count <= 0) + return; + + dest = dc_dest; + DWORD *bg2rgb; + DWORD fg; + + bg2rgb = dc_destblend; + fg = dc_srccolor; + int pitch = dc_pitch; + + do + { + DWORD a = fg + bg2rgb[*dest]; + DWORD b = a; + + a |= 0x01f07c1f; + b &= 0x40100400; + a &= 0x3fffffff; + b = b - (b >> 5); + a |= b; + *dest = RGB32k.All[a & (a>>15)]; + dest += pitch; + } while (--count); + +} + +void R_FillSubClampColumn (void) +{ + int count; + BYTE *dest; + + count = dc_count; + if (count <= 0) + return; + + dest = dc_dest; + DWORD *bg2rgb; + DWORD fg; + + bg2rgb = dc_destblend; + fg = dc_srccolor | 0x40100400; + int pitch = dc_pitch; + + do + { + DWORD a = fg - bg2rgb[*dest]; + DWORD b = a; + + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + *dest = RGB32k.All[a & (a>>15)]; + dest += pitch; + } while (--count); + +} + +void R_FillRevSubClampColumn (void) +{ + int count; + BYTE *dest; + + count = dc_count; + if (count <= 0) + return; + + dest = dc_dest; + DWORD *bg2rgb; + DWORD fg; + + bg2rgb = dc_destblend; + fg = dc_srccolor; + int pitch = dc_pitch; + + do + { + DWORD a = (bg2rgb[*dest] | 0x40100400) - fg; + DWORD b = a; + + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + *dest = RGB32k.All[a & (a>>15)]; + dest += pitch; + } while (--count); + +} + +// +// Spectre/Invisibility. +// +#define FUZZTABLE 50 + +extern "C" +{ +int fuzzoffset[FUZZTABLE+1]; // [RH] +1 for the assembly routine +int fuzzpos = 0; +int fuzzviewheight; +} +/* + FUZZOFF,-FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF, + FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF, + FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF, + FUZZOFF,-FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF, + FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,-FUZZOFF,FUZZOFF, + FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF, + FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF +*/ + +static const signed char fuzzinit[FUZZTABLE] = { + 1,-1, 1,-1, 1, 1,-1, + 1, 1,-1, 1, 1, 1,-1, + 1, 1, 1,-1,-1,-1,-1, + 1,-1,-1, 1, 1, 1, 1,-1, + 1,-1, 1, 1,-1,-1, 1, + 1,-1,-1,-1,-1, 1, 1, + 1, 1,-1, 1, 1,-1, 1 +}; + +void R_InitFuzzTable (int fuzzoff) +{ + int i; + + for (i = 0; i < FUZZTABLE; i++) + { + fuzzoffset[i] = fuzzinit[i] * fuzzoff; + } +} + +#ifndef X86_ASM +// +// Creates a fuzzy image by copying pixels from adjacent ones above and below. +// Used with an all black colormap, this could create the SHADOW effect, +// i.e. spectres and invisible players. +// +void R_DrawFuzzColumnP_C (void) +{ + int count; + BYTE *dest; + + // Adjust borders. Low... + if (dc_yl == 0) + dc_yl = 1; + + // .. and high. + if (dc_yh > fuzzviewheight) + dc_yh = fuzzviewheight; + + count = dc_yh - dc_yl; + + // Zero length. + if (count < 0) + return; + + count++; + + dest = ylookup[dc_yl] + dc_x + dc_destorg; + + // colormap #6 is used for shading (of 0-31, a bit brighter than average) + { + // [RH] Make local copies of global vars to try and improve + // the optimizations made by the compiler. + int pitch = dc_pitch; + int fuzz = fuzzpos; + int cnt; + BYTE *map = &NormalLight.Maps[6*256]; + + // [RH] Split this into three separate loops to minimize + // the number of times fuzzpos needs to be clamped. + if (fuzz) + { + cnt = MIN(FUZZTABLE-fuzz,count); + count -= cnt; + do + { + *dest = map[dest[fuzzoffset[fuzz++]]]; + dest += pitch; + } while (--cnt); + } + if (fuzz == FUZZTABLE || count > 0) + { + while (count >= FUZZTABLE) + { + fuzz = 0; + cnt = FUZZTABLE; + count -= FUZZTABLE; + do + { + *dest = map[dest[fuzzoffset[fuzz++]]]; + dest += pitch; + } while (--cnt); + } + fuzz = 0; + if (count > 0) + { + do + { + *dest = map[dest[fuzzoffset[fuzz++]]]; + dest += pitch; + } while (--count); + } + } + fuzzpos = fuzz; + } +} +#endif + +// +// R_DrawTranlucentColumn +// + +/* +[RH] This translucency algorithm is based on DOSDoom 0.65's, but uses +a 32k RGB table instead of an 8k one. At least on my machine, it's +slightly faster (probably because it uses only one shift instead of +two), and it looks considerably less green at the ends of the +translucency range. The extra size doesn't appear to be an issue. + +The following note is from DOSDoom 0.65: + +New translucency algorithm, by Erik Sandberg: + +Basically, we compute the red, green and blue values for each pixel, and +then use a RGB table to check which one of the palette colours that best +represents those RGB values. The RGB table is 8k big, with 4 R-bits, +5 G-bits and 4 B-bits. A 4k table gives a bit too bad precision, and a 32k +table takes up more memory and results in more cache misses, so an 8k +table seemed to be quite ultimate. + +The computation of the RGB for each pixel is accelerated by using two +1k tables for each translucency level. +The xth element of one of these tables contains the r, g and b values for +the colour x, weighted for the current translucency level (for example, +the weighted rgb values for background colour at 75% translucency are 1/4 +of the original rgb values). The rgb values are stored as three +low-precision fixed point values, packed into one long per colour: +Bit 0-4: Frac part of blue (5 bits) +Bit 5-8: Int part of blue (4 bits) +Bit 9-13: Frac part of red (5 bits) +Bit 14-17: Int part of red (4 bits) +Bit 18-22: Frac part of green (5 bits) +Bit 23-27: Int part of green (5 bits) +Bit 28-31: All zeros (4 bits) + +The point of this format is that the two colours now can be added, and +then be converted to a RGB table index very easily: First, we just set +all the frac bits and the four upper zero bits to 1. It's now possible +to get the RGB table index by anding the current value >> 5 with the +current value >> 19. When asm-optimised, this should be the fastest +algorithm that uses RGB tables. + +*/ + +void R_DrawAddColumnP_C (void) +{ + int count; + BYTE *dest; + fixed_t frac; + fixed_t fracstep; + + count = dc_count; + if (count <= 0) + return; + + dest = dc_dest; + + fracstep = dc_iscale; + frac = dc_texturefrac; + + { + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + BYTE *colormap = dc_colormap; + const BYTE *source = dc_source; + int pitch = dc_pitch; + + do + { + DWORD fg = colormap[source[frac>>FRACBITS]]; + DWORD bg = *dest; + + fg = fg2rgb[fg]; + bg = bg2rgb[bg]; + fg = (fg+bg) | 0x1f07c1f; + *dest = RGB32k.All[fg & (fg>>15)]; + dest += pitch; + frac += fracstep; + } while (--count); + } +} + +// +// R_DrawTranslatedColumn +// Used to draw player sprites with the green colorramp mapped to others. +// Could be used with different translation tables, e.g. the lighter colored +// version of the BaronOfHell, the HellKnight, uses identical sprites, kinda +// brightened up. +// + +void R_DrawTranslatedColumnP_C (void) +{ + int count; + BYTE* dest; + fixed_t frac; + fixed_t fracstep; + + count = dc_count; + if (count <= 0) + return; + + dest = dc_dest; + + fracstep = dc_iscale; + frac = dc_texturefrac; + + { + // [RH] Local copies of global vars to improve compiler optimizations + BYTE *colormap = dc_colormap; + BYTE *translation = dc_translation; + const BYTE *source = dc_source; + int pitch = dc_pitch; + + do + { + *dest = colormap[translation[source[frac>>FRACBITS]]]; + dest += pitch; + + frac += fracstep; + } while (--count); + } +} + +// Draw a column that is both translated and translucent +void R_DrawTlatedAddColumnP_C (void) +{ + int count; + BYTE *dest; + fixed_t frac; + fixed_t fracstep; + + count = dc_count; + if (count <= 0) + return; + + dest = dc_dest; + + fracstep = dc_iscale; + frac = dc_texturefrac; + + { + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + BYTE *translation = dc_translation; + BYTE *colormap = dc_colormap; + const BYTE *source = dc_source; + int pitch = dc_pitch; + + do + { + DWORD fg = colormap[translation[source[frac>>FRACBITS]]]; + DWORD bg = *dest; + + fg = fg2rgb[fg]; + bg = bg2rgb[bg]; + fg = (fg+bg) | 0x1f07c1f; + *dest = RGB32k.All[fg & (fg>>15)]; + dest += pitch; + frac += fracstep; + } while (--count); + } +} + +// Draw a column whose "color" values are actually translucency +// levels for a base color stored in dc_color. +void R_DrawShadedColumnP_C (void) +{ + int count; + BYTE *dest; + fixed_t frac, fracstep; + + count = dc_count; + + if (count <= 0) + return; + + dest = dc_dest; + + fracstep = dc_iscale; + frac = dc_texturefrac; + + { + const BYTE *source = dc_source; + BYTE *colormap = dc_colormap; + int pitch = dc_pitch; + DWORD *fgstart = &Col2RGB8[0][dc_color]; + + do + { + DWORD val = colormap[source[frac>>FRACBITS]]; + DWORD fg = fgstart[val<<8]; + val = (Col2RGB8[64-val][*dest] + fg) | 0x1f07c1f; + *dest = RGB32k.All[val & (val>>15)]; + + dest += pitch; + frac += fracstep; + } while (--count); + } +} + +// Add source to destination, clamping it to white +void R_DrawAddClampColumnP_C () +{ + int count; + BYTE *dest; + fixed_t frac; + fixed_t fracstep; + + count = dc_count; + if (count <= 0) + return; + + dest = dc_dest; + + fracstep = dc_iscale; + frac = dc_texturefrac; + + { + BYTE *colormap = dc_colormap; + const BYTE *source = dc_source; + int pitch = dc_pitch; + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + do + { + DWORD a = fg2rgb[colormap[source[frac>>FRACBITS]]] + bg2rgb[*dest]; + DWORD b = a; + + a |= 0x01f07c1f; + b &= 0x40100400; + a &= 0x3fffffff; + b = b - (b >> 5); + a |= b; + *dest = RGB32k.All[a & (a>>15)]; + dest += pitch; + frac += fracstep; + } while (--count); + } +} + +// Add translated source to destination, clamping it to white +void R_DrawAddClampTranslatedColumnP_C () +{ + int count; + BYTE *dest; + fixed_t frac; + fixed_t fracstep; + + count = dc_count; + if (count <= 0) + return; + + dest = dc_dest; + + fracstep = dc_iscale; + frac = dc_texturefrac; + + { + BYTE *translation = dc_translation; + BYTE *colormap = dc_colormap; + const BYTE *source = dc_source; + int pitch = dc_pitch; + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + do + { + DWORD a = fg2rgb[colormap[translation[source[frac>>FRACBITS]]]] + bg2rgb[*dest]; + DWORD b = a; + + a |= 0x01f07c1f; + b &= 0x40100400; + a &= 0x3fffffff; + b = b - (b >> 5); + a |= b; + *dest = RGB32k.All[(a>>15) & a]; + dest += pitch; + frac += fracstep; + } while (--count); + } +} + +// Subtract destination from source, clamping it to black +void R_DrawSubClampColumnP_C () +{ + int count; + BYTE *dest; + fixed_t frac; + fixed_t fracstep; + + count = dc_count; + if (count <= 0) + return; + + dest = dc_dest; + + fracstep = dc_iscale; + frac = dc_texturefrac; + + { + BYTE *colormap = dc_colormap; + const BYTE *source = dc_source; + int pitch = dc_pitch; + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + do + { + DWORD a = (fg2rgb[colormap[source[frac>>FRACBITS]]] | 0x40100400) - bg2rgb[*dest]; + DWORD b = a; + + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + *dest = RGB32k.All[a & (a>>15)]; + dest += pitch; + frac += fracstep; + } while (--count); + } +} + +// Subtract destination from source, clamping it to black +void R_DrawSubClampTranslatedColumnP_C () +{ + int count; + BYTE *dest; + fixed_t frac; + fixed_t fracstep; + + count = dc_count; + if (count <= 0) + return; + + dest = dc_dest; + + fracstep = dc_iscale; + frac = dc_texturefrac; + + { + BYTE *translation = dc_translation; + BYTE *colormap = dc_colormap; + const BYTE *source = dc_source; + int pitch = dc_pitch; + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + do + { + DWORD a = (fg2rgb[colormap[translation[source[frac>>FRACBITS]]]] | 0x40100400) - bg2rgb[*dest]; + DWORD b = a; + + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + *dest = RGB32k.All[(a>>15) & a]; + dest += pitch; + frac += fracstep; + } while (--count); + } +} + +// Subtract source from destination, clamping it to black +void R_DrawRevSubClampColumnP_C () +{ + int count; + BYTE *dest; + fixed_t frac; + fixed_t fracstep; + + count = dc_count; + if (count <= 0) + return; + + dest = dc_dest; + + fracstep = dc_iscale; + frac = dc_texturefrac; + + { + BYTE *colormap = dc_colormap; + const BYTE *source = dc_source; + int pitch = dc_pitch; + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + do + { + DWORD a = (bg2rgb[*dest] | 0x40100400) - fg2rgb[colormap[source[frac>>FRACBITS]]]; + DWORD b = a; + + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + *dest = RGB32k.All[a & (a>>15)]; + dest += pitch; + frac += fracstep; + } while (--count); + } +} + +// Subtract source from destination, clamping it to black +void R_DrawRevSubClampTranslatedColumnP_C () +{ + int count; + BYTE *dest; + fixed_t frac; + fixed_t fracstep; + + count = dc_count; + if (count <= 0) + return; + + dest = dc_dest; + + fracstep = dc_iscale; + frac = dc_texturefrac; + + { + BYTE *translation = dc_translation; + BYTE *colormap = dc_colormap; + const BYTE *source = dc_source; + int pitch = dc_pitch; + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + do + { + DWORD a = (bg2rgb[*dest] | 0x40100400) - fg2rgb[colormap[translation[source[frac>>FRACBITS]]]]; + DWORD b = a; + + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + *dest = RGB32k.All[(a>>15) & a]; + dest += pitch; + frac += fracstep; + } while (--count); + } +} + + + +// +// R_DrawSpan +// With DOOM style restrictions on view orientation, +// the floors and ceilings consist of horizontal slices +// or spans with constant z depth. +// However, rotation around the world z axis is possible, +// thus this mapping, while simpler and faster than +// perspective correct texture mapping, has to traverse +// the texture at an angle in all but a few cases. +// In consequence, flats are not stored by column (like walls), +// and the inner loop has to step in texture space u and v. +// +// [RH] I'm not sure who wrote this, but floor/ceiling mapping +// *is* perspective correct for spans of constant z depth, which +// Doom guarantees because it does not let you change your pitch. +// Also, because of the new texture system, flats *are* stored by +// column to make it easy to use them on walls too. To accomodate +// this, the use of x/u and y/v in R_DrawSpan just needs to be +// swapped. +// +extern "C" { +int ds_color; // [RH] color for non-textured spans + +int ds_y; +int ds_x1; +int ds_x2; + +lighttable_t* ds_colormap; + +dsfixed_t ds_xfrac; +dsfixed_t ds_yfrac; +dsfixed_t ds_xstep; +dsfixed_t ds_ystep; +int ds_xbits; +int ds_ybits; + +// start of a floor/ceiling tile image +const BYTE* ds_source; + +// just for profiling +int dscount; + +#ifdef X86_ASM +extern "C" void R_SetSpanSource_ASM (const BYTE *flat); +extern "C" void R_SetSpanSize_ASM (int xbits, int ybits); +extern "C" void R_SetSpanColormap_ASM (BYTE *colormap); +extern "C" BYTE *ds_curcolormap, *ds_cursource, *ds_curtiltedsource; +#endif +} + +//========================================================================== +// +// R_SetSpanSource +// +// Sets the source bitmap for the span drawing routines. +// +//========================================================================== + +void R_SetSpanSource(const BYTE *pixels) +{ + ds_source = pixels; +#ifdef X86_ASM + if (ds_cursource != ds_source) + { + R_SetSpanSource_ASM(pixels); + } +#endif +} + +//========================================================================== +// +// R_SetSpanColormap +// +// Sets the colormap for the span drawing routines. +// +//========================================================================== + +void R_SetSpanColormap(BYTE *colormap) +{ + ds_colormap = colormap; +#ifdef X86_ASM + if (ds_colormap != ds_curcolormap) + { + R_SetSpanColormap_ASM (ds_colormap); + } +#endif +} + +//========================================================================== +// +// R_SetupSpanBits +// +// Sets the texture size for the span drawing routines. +// +//========================================================================== + +void R_SetupSpanBits(FTexture *tex) +{ + tex->GetWidth (); + ds_xbits = tex->WidthBits; + ds_ybits = tex->HeightBits; + if ((1 << ds_xbits) > tex->GetWidth()) + { + ds_xbits--; + } + if ((1 << ds_ybits) > tex->GetHeight()) + { + ds_ybits--; + } +#ifdef X86_ASM + R_SetSpanSize_ASM (ds_xbits, ds_ybits); +#endif +} + +// +// Draws the actual span. +#ifndef X86_ASM +void R_DrawSpanP_C (void) +{ + dsfixed_t xfrac; + dsfixed_t yfrac; + dsfixed_t xstep; + dsfixed_t ystep; + BYTE* dest; + const BYTE* source = ds_source; + const BYTE* colormap = ds_colormap; + int count; + int spot; + +#ifdef RANGECHECK + if (ds_x2 < ds_x1 || ds_x1 < 0 + || ds_x2 >= screen->width || ds_y > screen->height) + { + I_Error ("R_DrawSpan: %i to %i at %i", ds_x1, ds_x2, ds_y); + } +// dscount++; +#endif + + xfrac = ds_xfrac; + yfrac = ds_yfrac; + + dest = ylookup[ds_y] + ds_x1 + dc_destorg; + + count = ds_x2 - ds_x1 + 1; + + xstep = ds_xstep; + ystep = ds_ystep; + + if (ds_xbits == 6 && ds_ybits == 6) + { + // 64x64 is the most common case by far, so special case it. + do + { + // Current texture index in u,v. + spot = ((xfrac>>(32-6-6))&(63*64)) + (yfrac>>(32-6)); + + // Lookup pixel from flat texture tile, + // re-index using light/colormap. + *dest++ = colormap[source[spot]]; + + // Next step in u,v. + xfrac += xstep; + yfrac += ystep; + } while (--count); + } + else + { + BYTE yshift = 32 - ds_ybits; + BYTE xshift = yshift - ds_xbits; + int xmask = ((1 << ds_xbits) - 1) << ds_ybits; + + do + { + // Current texture index in u,v. + spot = ((xfrac >> xshift) & xmask) + (yfrac >> yshift); + + // Lookup pixel from flat texture tile, + // re-index using light/colormap. + *dest++ = colormap[source[spot]]; + + // Next step in u,v. + xfrac += xstep; + yfrac += ystep; + } while (--count); + } +} + +// [RH] Draw a span with holes +void R_DrawSpanMaskedP_C (void) +{ + dsfixed_t xfrac; + dsfixed_t yfrac; + dsfixed_t xstep; + dsfixed_t ystep; + BYTE* dest; + const BYTE* source = ds_source; + const BYTE* colormap = ds_colormap; + int count; + int spot; + + xfrac = ds_xfrac; + yfrac = ds_yfrac; + + dest = ylookup[ds_y] + ds_x1 + dc_destorg; + + count = ds_x2 - ds_x1 + 1; + + xstep = ds_xstep; + ystep = ds_ystep; + + if (ds_xbits == 6 && ds_ybits == 6) + { + // 64x64 is the most common case by far, so special case it. + do + { + BYTE texdata; + + spot = ((xfrac>>(32-6-6))&(63*64)) + (yfrac>>(32-6)); + texdata = source[spot]; + if (texdata != 0) + { + *dest = colormap[texdata]; + } + dest++; + xfrac += xstep; + yfrac += ystep; + } while (--count); + } + else + { + BYTE yshift = 32 - ds_ybits; + BYTE xshift = yshift - ds_xbits; + int xmask = ((1 << ds_xbits) - 1) << ds_ybits; + do + { + BYTE texdata; + + spot = ((xfrac >> xshift) & xmask) + (yfrac >> yshift); + texdata = source[spot]; + if (texdata != 0) + { + *dest = colormap[texdata]; + } + dest++; + xfrac += xstep; + yfrac += ystep; + } while (--count); + } +} +#endif + +void R_DrawSpanTranslucentP_C (void) +{ + dsfixed_t xfrac; + dsfixed_t yfrac; + dsfixed_t xstep; + dsfixed_t ystep; + BYTE* dest; + const BYTE* source = ds_source; + const BYTE* colormap = ds_colormap; + int count; + int spot; + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + xfrac = ds_xfrac; + yfrac = ds_yfrac; + + dest = ylookup[ds_y] + ds_x1 + dc_destorg; + + count = ds_x2 - ds_x1 + 1; + + xstep = ds_xstep; + ystep = ds_ystep; + + if (ds_xbits == 6 && ds_ybits == 6) + { + // 64x64 is the most common case by far, so special case it. + do + { + spot = ((xfrac>>(32-6-6))&(63*64)) + (yfrac>>(32-6)); + DWORD fg = colormap[source[spot]]; + DWORD bg = *dest; + fg = fg2rgb[fg]; + bg = bg2rgb[bg]; + fg = (fg+bg) | 0x1f07c1f; + *dest++ = RGB32k.All[fg & (fg>>15)]; + xfrac += xstep; + yfrac += ystep; + } while (--count); + } + else + { + BYTE yshift = 32 - ds_ybits; + BYTE xshift = yshift - ds_xbits; + int xmask = ((1 << ds_xbits) - 1) << ds_ybits; + do + { + spot = ((xfrac >> xshift) & xmask) + (yfrac >> yshift); + DWORD fg = colormap[source[spot]]; + DWORD bg = *dest; + fg = fg2rgb[fg]; + bg = bg2rgb[bg]; + fg = (fg+bg) | 0x1f07c1f; + *dest++ = RGB32k.All[fg & (fg>>15)]; + xfrac += xstep; + yfrac += ystep; + } while (--count); + } +} + +void R_DrawSpanMaskedTranslucentP_C (void) +{ + dsfixed_t xfrac; + dsfixed_t yfrac; + dsfixed_t xstep; + dsfixed_t ystep; + BYTE* dest; + const BYTE* source = ds_source; + const BYTE* colormap = ds_colormap; + int count; + int spot; + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + xfrac = ds_xfrac; + yfrac = ds_yfrac; + + dest = ylookup[ds_y] + ds_x1 + dc_destorg; + + count = ds_x2 - ds_x1 + 1; + + xstep = ds_xstep; + ystep = ds_ystep; + + if (ds_xbits == 6 && ds_ybits == 6) + { + // 64x64 is the most common case by far, so special case it. + do + { + BYTE texdata; + + spot = ((xfrac>>(32-6-6))&(63*64)) + (yfrac>>(32-6)); + texdata = source[spot]; + if (texdata != 0) + { + DWORD fg = colormap[texdata]; + DWORD bg = *dest; + fg = fg2rgb[fg]; + bg = bg2rgb[bg]; + fg = (fg+bg) | 0x1f07c1f; + *dest = RGB32k.All[fg & (fg>>15)]; + } + dest++; + xfrac += xstep; + yfrac += ystep; + } while (--count); + } + else + { + BYTE yshift = 32 - ds_ybits; + BYTE xshift = yshift - ds_xbits; + int xmask = ((1 << ds_xbits) - 1) << ds_ybits; + do + { + BYTE texdata; + + spot = ((xfrac >> xshift) & xmask) + (yfrac >> yshift); + texdata = source[spot]; + if (texdata != 0) + { + DWORD fg = colormap[texdata]; + DWORD bg = *dest; + fg = fg2rgb[fg]; + bg = bg2rgb[bg]; + fg = (fg+bg) | 0x1f07c1f; + *dest = RGB32k.All[fg & (fg>>15)]; + } + dest++; + xfrac += xstep; + yfrac += ystep; + } while (--count); + } +} + +void R_DrawSpanAddClampP_C (void) +{ + dsfixed_t xfrac; + dsfixed_t yfrac; + dsfixed_t xstep; + dsfixed_t ystep; + BYTE* dest; + const BYTE* source = ds_source; + const BYTE* colormap = ds_colormap; + int count; + int spot; + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + xfrac = ds_xfrac; + yfrac = ds_yfrac; + + dest = ylookup[ds_y] + ds_x1 + dc_destorg; + + count = ds_x2 - ds_x1 + 1; + + xstep = ds_xstep; + ystep = ds_ystep; + + if (ds_xbits == 6 && ds_ybits == 6) + { + // 64x64 is the most common case by far, so special case it. + do + { + spot = ((xfrac>>(32-6-6))&(63*64)) + (yfrac>>(32-6)); + DWORD a = fg2rgb[colormap[source[spot]]] + bg2rgb[*dest]; + DWORD b = a; + + a |= 0x01f07c1f; + b &= 0x40100400; + a &= 0x3fffffff; + b = b - (b >> 5); + a |= b; + *dest++ = RGB32k.All[a & (a>>15)]; + xfrac += xstep; + yfrac += ystep; + } while (--count); + } + else + { + BYTE yshift = 32 - ds_ybits; + BYTE xshift = yshift - ds_xbits; + int xmask = ((1 << ds_xbits) - 1) << ds_ybits; + do + { + spot = ((xfrac >> xshift) & xmask) + (yfrac >> yshift); + DWORD a = fg2rgb[colormap[source[spot]]] + bg2rgb[*dest]; + DWORD b = a; + + a |= 0x01f07c1f; + b &= 0x40100400; + a &= 0x3fffffff; + b = b - (b >> 5); + a |= b; + *dest++ = RGB32k.All[a & (a>>15)]; + xfrac += xstep; + yfrac += ystep; + } while (--count); + } +} + +void R_DrawSpanMaskedAddClampP_C (void) +{ + dsfixed_t xfrac; + dsfixed_t yfrac; + dsfixed_t xstep; + dsfixed_t ystep; + BYTE* dest; + const BYTE* source = ds_source; + const BYTE* colormap = ds_colormap; + int count; + int spot; + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + xfrac = ds_xfrac; + yfrac = ds_yfrac; + + dest = ylookup[ds_y] + ds_x1 + dc_destorg; + + count = ds_x2 - ds_x1 + 1; + + xstep = ds_xstep; + ystep = ds_ystep; + + if (ds_xbits == 6 && ds_ybits == 6) + { + // 64x64 is the most common case by far, so special case it. + do + { + BYTE texdata; + + spot = ((xfrac>>(32-6-6))&(63*64)) + (yfrac>>(32-6)); + texdata = source[spot]; + if (texdata != 0) + { + DWORD a = fg2rgb[colormap[texdata]] + bg2rgb[*dest]; + DWORD b = a; + + a |= 0x01f07c1f; + b &= 0x40100400; + a &= 0x3fffffff; + b = b - (b >> 5); + a |= b; + *dest = RGB32k.All[a & (a>>15)]; + } + dest++; + xfrac += xstep; + yfrac += ystep; + } while (--count); + } + else + { + BYTE yshift = 32 - ds_ybits; + BYTE xshift = yshift - ds_xbits; + int xmask = ((1 << ds_xbits) - 1) << ds_ybits; + do + { + BYTE texdata; + + spot = ((xfrac >> xshift) & xmask) + (yfrac >> yshift); + texdata = source[spot]; + if (texdata != 0) + { + DWORD a = fg2rgb[colormap[texdata]] + bg2rgb[*dest]; + DWORD b = a; + + a |= 0x01f07c1f; + b &= 0x40100400; + a &= 0x3fffffff; + b = b - (b >> 5); + a |= b; + *dest = RGB32k.All[a & (a>>15)]; + } + dest++; + xfrac += xstep; + yfrac += ystep; + } while (--count); + } +} + +// [RH] Just fill a span with a color +void R_FillSpan (void) +{ + memset (ylookup[ds_y] + ds_x1 + dc_destorg, ds_color, ds_x2 - ds_x1 + 1); +} + +// Draw a voxel slab +// +// "Build Engine & Tools" Copyright (c) 1993-1997 Ken Silverman +// Ken Silverman's official web site: "http://www.advsys.net/ken" +// See the included license file "BUILDLIC.TXT" for license info. + +// Actually, this is just R_DrawColumn with an extra width parameter. + +#ifndef X86_ASM +static const BYTE *slabcolormap; + +extern "C" void R_SetupDrawSlabC(const BYTE *colormap) +{ + slabcolormap = colormap; +} + +extern "C" void R_DrawSlabC(int dx, fixed_t v, int dy, fixed_t vi, const BYTE *vptr, BYTE *p) +{ + int x; + const BYTE *colormap = slabcolormap; + int pitch = dc_pitch; + + assert(dx > 0); + + if (dx == 1) + { + while (dy > 0) + { + *p = colormap[vptr[v >> FRACBITS]]; + p += pitch; + v += vi; + dy--; + } + } + else if (dx == 2) + { + while (dy > 0) + { + BYTE color = colormap[vptr[v >> FRACBITS]]; + p[0] = color; + p[1] = color; + p += pitch; + v += vi; + dy--; + } + } + else if (dx == 3) + { + while (dy > 0) + { + BYTE color = colormap[vptr[v >> FRACBITS]]; + p[0] = color; + p[1] = color; + p[2] = color; + p += pitch; + v += vi; + dy--; + } + } + else if (dx == 4) + { + while (dy > 0) + { + BYTE color = colormap[vptr[v >> FRACBITS]]; + p[0] = color; + p[1] = color; + p[2] = color; + p[3] = color; + p += pitch; + v += vi; + dy--; + } + } + else while (dy > 0) + { + BYTE color = colormap[vptr[v >> FRACBITS]]; + // The optimizer will probably turn this into a memset call. + // Since dx is not likely to be large, I'm not sure that's a good thing, + // hence the alternatives above. + for (x = 0; x < dx; x++) + { + p[x] = color; + } + p += pitch; + v += vi; + dy--; + } +} +#endif + + +/****************************************************/ +/****************************************************/ + +// wallscan stuff, in C + +#ifndef X86_ASM +static DWORD vlinec1 (); +static int vlinebits; + +DWORD (*dovline1)() = vlinec1; +DWORD (*doprevline1)() = vlinec1; + +#ifdef X64_ASM +extern "C" void vlinetallasm4(); +#define dovline4 vlinetallasm4 +extern "C" void setupvlinetallasm (int); +#else +static void vlinec4 (); +void (*dovline4)() = vlinec4; +#endif + +static DWORD mvlinec1(); +static void mvlinec4(); +static int mvlinebits; + +DWORD (*domvline1)() = mvlinec1; +void (*domvline4)() = mvlinec4; + +#else + +extern "C" +{ +DWORD vlineasm1 (); +DWORD prevlineasm1 (); +DWORD vlinetallasm1 (); +DWORD prevlinetallasm1 (); +void vlineasm4 (); +void vlinetallasmathlon4 (); +void vlinetallasm4 (); +void setupvlineasm (int); +void setupvlinetallasm (int); + +DWORD mvlineasm1(); +void mvlineasm4(); +void setupmvlineasm (int); +} + +DWORD (*dovline1)() = vlinetallasm1; +DWORD (*doprevline1)() = prevlinetallasm1; +void (*dovline4)() = vlinetallasm4; + +DWORD (*domvline1)() = mvlineasm1; +void (*domvline4)() = mvlineasm4; +#endif + +void setupvline (int fracbits) +{ +#ifdef X86_ASM + if (CPU.Family <= 5) + { + if (fracbits >= 24) + { + setupvlineasm (fracbits); + dovline4 = vlineasm4; + dovline1 = vlineasm1; + doprevline1 = prevlineasm1; + } + else + { + setupvlinetallasm (fracbits); + dovline1 = vlinetallasm1; + doprevline1 = prevlinetallasm1; + dovline4 = vlinetallasm4; + } + } + else + { + setupvlinetallasm (fracbits); + if (CPU.bIsAMD && CPU.AMDFamily >= 7) + { + dovline4 = vlinetallasmathlon4; + } + } +#else + vlinebits = fracbits; +#ifdef X64_ASM + setupvlinetallasm(fracbits); +#endif +#endif +} + +#if !defined(X86_ASM) +DWORD vlinec1 () +{ + DWORD fracstep = dc_iscale; + DWORD frac = dc_texturefrac; + BYTE *colormap = dc_colormap; + int count = dc_count; + const BYTE *source = dc_source; + BYTE *dest = dc_dest; + int bits = vlinebits; + int pitch = dc_pitch; + + do + { + *dest = colormap[source[frac>>bits]]; + frac += fracstep; + dest += pitch; + } while (--count); + + return frac; +} + +void vlinec4 () +{ + BYTE *dest = dc_dest; + int count = dc_count; + int bits = vlinebits; + DWORD place; + + do + { + dest[0] = palookupoffse[0][bufplce[0][(place=vplce[0])>>bits]]; vplce[0] = place+vince[0]; + dest[1] = palookupoffse[1][bufplce[1][(place=vplce[1])>>bits]]; vplce[1] = place+vince[1]; + dest[2] = palookupoffse[2][bufplce[2][(place=vplce[2])>>bits]]; vplce[2] = place+vince[2]; + dest[3] = palookupoffse[3][bufplce[3][(place=vplce[3])>>bits]]; vplce[3] = place+vince[3]; + dest += dc_pitch; + } while (--count); +} +#endif + +void setupmvline (int fracbits) +{ +#if defined(X86_ASM) + setupmvlineasm (fracbits); + domvline1 = mvlineasm1; + domvline4 = mvlineasm4; +#else + mvlinebits = fracbits; +#endif +} + +#if !defined(X86_ASM) +DWORD mvlinec1 () +{ + DWORD fracstep = dc_iscale; + DWORD frac = dc_texturefrac; + BYTE *colormap = dc_colormap; + int count = dc_count; + const BYTE *source = dc_source; + BYTE *dest = dc_dest; + int bits = mvlinebits; + int pitch = dc_pitch; + + do + { + BYTE pix = source[frac>>bits]; + if (pix != 0) + { + *dest = colormap[pix]; + } + frac += fracstep; + dest += pitch; + } while (--count); + + return frac; +} + +void mvlinec4 () +{ + BYTE *dest = dc_dest; + int count = dc_count; + int bits = mvlinebits; + DWORD place; + + do + { + BYTE pix; + + pix = bufplce[0][(place=vplce[0])>>bits]; if(pix) dest[0] = palookupoffse[0][pix]; vplce[0] = place+vince[0]; + pix = bufplce[1][(place=vplce[1])>>bits]; if(pix) dest[1] = palookupoffse[1][pix]; vplce[1] = place+vince[1]; + pix = bufplce[2][(place=vplce[2])>>bits]; if(pix) dest[2] = palookupoffse[2][pix]; vplce[2] = place+vince[2]; + pix = bufplce[3][(place=vplce[3])>>bits]; if(pix) dest[3] = palookupoffse[3][pix]; vplce[3] = place+vince[3]; + dest += dc_pitch; + } while (--count); +} +#endif + +extern "C" short spanend[MAXHEIGHT]; +extern float rw_light; +extern float rw_lightstep; +extern int wallshade; + +static void R_DrawFogBoundarySection (int y, int y2, int x1) +{ + BYTE *colormap = dc_colormap; + BYTE *dest = ylookup[y] + dc_destorg; + + for (; y < y2; ++y) + { + int x2 = spanend[y]; + int x = x1; + do + { + dest[x] = colormap[dest[x]]; + } while (++x <= x2); + dest += dc_pitch; + } +} + +static void R_DrawFogBoundaryLine (int y, int x) +{ + int x2 = spanend[y]; + BYTE *colormap = dc_colormap; + BYTE *dest = ylookup[y] + dc_destorg; + do + { + dest[x] = colormap[dest[x]]; + } while (++x <= x2); +} + +void R_DrawFogBoundary (int x1, int x2, short *uclip, short *dclip) +{ + // This is essentially the same as R_MapVisPlane but with an extra step + // to create new horizontal spans whenever the light changes enough that + // we need to use a new colormap. + + double lightstep = rw_lightstep; + double light = rw_light + rw_lightstep*(x2-x1-1); + int x = x2-1; + int t2 = uclip[x]; + int b2 = dclip[x]; + int rcolormap = GETPALOOKUP(light, wallshade); + int lcolormap; + BYTE *basecolormapdata = basecolormap->Maps; + + if (b2 > t2) + { + clearbufshort (spanend+t2, b2-t2, x); + } + + dc_colormap = basecolormapdata + (rcolormap << COLORMAPSHIFT); + + for (--x; x >= x1; --x) + { + int t1 = uclip[x]; + int b1 = dclip[x]; + const int xr = x+1; + int stop; + + light -= rw_lightstep; + lcolormap = GETPALOOKUP(light, wallshade); + if (lcolormap != rcolormap) + { + if (t2 < b2 && rcolormap != 0) + { // Colormap 0 is always the identity map, so rendering it is + // just a waste of time. + R_DrawFogBoundarySection (t2, b2, xr); + } + if (t1 < t2) t2 = t1; + if (b1 > b2) b2 = b1; + if (t2 < b2) + { + clearbufshort (spanend+t2, b2-t2, x); + } + rcolormap = lcolormap; + dc_colormap = basecolormapdata + (lcolormap << COLORMAPSHIFT); + } + else + { + if (dc_colormap != basecolormapdata) + { + stop = MIN (t1, b2); + while (t2 < stop) + { + R_DrawFogBoundaryLine (t2++, xr); + } + stop = MAX (b1, t2); + while (b2 > stop) + { + R_DrawFogBoundaryLine (--b2, xr); + } + } + else + { + t2 = MAX (t2, MIN (t1, b2)); + b2 = MIN (b2, MAX (b1, t2)); + } + + stop = MIN (t2, b1); + while (t1 < stop) + { + spanend[t1++] = x; + } + stop = MAX (b2, t2); + while (b1 > stop) + { + spanend[--b1] = x; + } + } + + t2 = uclip[x]; + b2 = dclip[x]; + } + if (t2 < b2 && rcolormap != 0) + { + R_DrawFogBoundarySection (t2, b2, x1); + } +} + +int tmvlinebits; + +void setuptmvline (int bits) +{ + tmvlinebits = bits; +} + +fixed_t tmvline1_add () +{ + DWORD fracstep = dc_iscale; + DWORD frac = dc_texturefrac; + BYTE *colormap = dc_colormap; + int count = dc_count; + const BYTE *source = dc_source; + BYTE *dest = dc_dest; + int bits = tmvlinebits; + int pitch = dc_pitch; + + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + do + { + BYTE pix = source[frac>>bits]; + if (pix != 0) + { + DWORD fg = fg2rgb[colormap[pix]]; + DWORD bg = bg2rgb[*dest]; + fg = (fg+bg) | 0x1f07c1f; + *dest = RGB32k.All[fg & (fg>>15)]; + } + frac += fracstep; + dest += pitch; + } while (--count); + + return frac; +} + +void tmvline4_add () +{ + BYTE *dest = dc_dest; + int count = dc_count; + int bits = tmvlinebits; + + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + do + { + for (int i = 0; i < 4; ++i) + { + BYTE pix = bufplce[i][vplce[i] >> bits]; + if (pix != 0) + { + DWORD fg = fg2rgb[palookupoffse[i][pix]]; + DWORD bg = bg2rgb[dest[i]]; + fg = (fg+bg) | 0x1f07c1f; + dest[i] = RGB32k.All[fg & (fg>>15)]; + } + vplce[i] += vince[i]; + } + dest += dc_pitch; + } while (--count); +} + +fixed_t tmvline1_addclamp () +{ + DWORD fracstep = dc_iscale; + DWORD frac = dc_texturefrac; + BYTE *colormap = dc_colormap; + int count = dc_count; + const BYTE *source = dc_source; + BYTE *dest = dc_dest; + int bits = tmvlinebits; + int pitch = dc_pitch; + + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + do + { + BYTE pix = source[frac>>bits]; + if (pix != 0) + { + DWORD a = fg2rgb[colormap[pix]] + bg2rgb[*dest]; + DWORD b = a; + + a |= 0x01f07c1f; + b &= 0x40100400; + a &= 0x3fffffff; + b = b - (b >> 5); + a |= b; + *dest = RGB32k.All[a & (a>>15)]; + } + frac += fracstep; + dest += pitch; + } while (--count); + + return frac; +} + +void tmvline4_addclamp () +{ + BYTE *dest = dc_dest; + int count = dc_count; + int bits = tmvlinebits; + + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + do + { + for (int i = 0; i < 4; ++i) + { + BYTE pix = bufplce[i][vplce[i] >> bits]; + if (pix != 0) + { + DWORD a = fg2rgb[palookupoffse[i][pix]] + bg2rgb[dest[i]]; + DWORD b = a; + + a |= 0x01f07c1f; + b &= 0x40100400; + a &= 0x3fffffff; + b = b - (b >> 5); + a |= b; + dest[i] = RGB32k.All[a & (a>>15)]; + } + vplce[i] += vince[i]; + } + dest += dc_pitch; + } while (--count); +} + +fixed_t tmvline1_subclamp () +{ + DWORD fracstep = dc_iscale; + DWORD frac = dc_texturefrac; + BYTE *colormap = dc_colormap; + int count = dc_count; + const BYTE *source = dc_source; + BYTE *dest = dc_dest; + int bits = tmvlinebits; + int pitch = dc_pitch; + + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + do + { + BYTE pix = source[frac>>bits]; + if (pix != 0) + { + DWORD a = (fg2rgb[colormap[pix]] | 0x40100400) - bg2rgb[*dest]; + DWORD b = a; + + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + *dest = RGB32k.All[a & (a>>15)]; + } + frac += fracstep; + dest += pitch; + } while (--count); + + return frac; +} + +void tmvline4_subclamp () +{ + BYTE *dest = dc_dest; + int count = dc_count; + int bits = tmvlinebits; + + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + do + { + for (int i = 0; i < 4; ++i) + { + BYTE pix = bufplce[i][vplce[i] >> bits]; + if (pix != 0) + { + DWORD a = (fg2rgb[palookupoffse[i][pix]] | 0x40100400) - bg2rgb[dest[i]]; + DWORD b = a; + + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + dest[i] = RGB32k.All[a & (a>>15)]; + } + vplce[i] += vince[i]; + } + dest += dc_pitch; + } while (--count); +} + +fixed_t tmvline1_revsubclamp () +{ + DWORD fracstep = dc_iscale; + DWORD frac = dc_texturefrac; + BYTE *colormap = dc_colormap; + int count = dc_count; + const BYTE *source = dc_source; + BYTE *dest = dc_dest; + int bits = tmvlinebits; + int pitch = dc_pitch; + + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + do + { + BYTE pix = source[frac>>bits]; + if (pix != 0) + { + DWORD a = (bg2rgb[*dest] | 0x40100400) - fg2rgb[colormap[pix]]; + DWORD b = a; + + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + *dest = RGB32k.All[a & (a>>15)]; + } + frac += fracstep; + dest += pitch; + } while (--count); + + return frac; +} + +void tmvline4_revsubclamp () +{ + BYTE *dest = dc_dest; + int count = dc_count; + int bits = tmvlinebits; + + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + + do + { + for (int i = 0; i < 4; ++i) + { + BYTE pix = bufplce[i][vplce[i] >> bits]; + if (pix != 0) + { + DWORD a = (bg2rgb[dest[i]] | 0x40100400) - fg2rgb[palookupoffse[i][pix]]; + DWORD b = a; + + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + dest[i] = RGB32k.All[a & (a>>15)]; + } + vplce[i] += vince[i]; + } + dest += dc_pitch; + } while (--count); +} + + +//========================================================================== +// +// R_GetColumn +// +//========================================================================== + +const BYTE *R_GetColumn (FTexture *tex, int col) +{ + int width; + + // If the texture's width isn't a power of 2, then we need to make it a + // positive offset for proper clamping. + if (col < 0 && (width = tex->GetWidth()) != (1 << tex->WidthBits)) + { + col = width + (col % width); + } + return tex->GetColumn (col, NULL); +} + + +// [RH] Initialize the column drawer pointers +void R_InitColumnDrawers () +{ +#ifdef X86_ASM + R_DrawColumn = R_DrawColumnP_ASM; + R_DrawColumnHoriz = R_DrawColumnHorizP_ASM; + R_DrawFuzzColumn = R_DrawFuzzColumnP_ASM; + R_DrawTranslatedColumn = R_DrawTranslatedColumnP_C; + R_DrawShadedColumn = R_DrawShadedColumnP_C; + R_DrawSpan = R_DrawSpanP_ASM; + R_DrawSpanMasked = R_DrawSpanMaskedP_ASM; + if (CPU.Family <= 5) + { + rt_map4cols = rt_map4cols_asm2; + } + else + { + rt_map4cols = rt_map4cols_asm1; + } +#else + R_DrawColumnHoriz = R_DrawColumnHorizP_C; + R_DrawColumn = R_DrawColumnP_C; + R_DrawFuzzColumn = R_DrawFuzzColumnP_C; + R_DrawTranslatedColumn = R_DrawTranslatedColumnP_C; + R_DrawShadedColumn = R_DrawShadedColumnP_C; + R_DrawSpan = R_DrawSpanP_C; + R_DrawSpanMasked = R_DrawSpanMaskedP_C; + rt_map4cols = rt_map4cols_c; +#endif + R_DrawSpanTranslucent = R_DrawSpanTranslucentP_C; + R_DrawSpanMaskedTranslucent = R_DrawSpanMaskedTranslucentP_C; + R_DrawSpanAddClamp = R_DrawSpanAddClampP_C; + R_DrawSpanMaskedAddClamp = R_DrawSpanMaskedAddClampP_C; +} + +// [RH] Choose column drawers in a single place +EXTERN_CVAR (Int, r_drawfuzz) +EXTERN_CVAR (Bool, r_drawtrans) +EXTERN_CVAR (Float, transsouls) + +static FDynamicColormap *basecolormapsave; + +static bool R_SetBlendFunc (int op, fixed_t fglevel, fixed_t bglevel, int flags) +{ + // r_drawtrans is a seriously bad thing to turn off. I wonder if I should + // just remove it completely. + if (!r_drawtrans || (op == STYLEOP_Add && fglevel == FRACUNIT && bglevel == 0 && !(flags & STYLEF_InvertSource))) + { + if (flags & STYLEF_ColorIsFixed) + { + colfunc = R_FillColumnP; + hcolfunc_post1 = rt_copy1col; + hcolfunc_post4 = rt_copy4cols; + } + else if (dc_translation == NULL) + { + colfunc = basecolfunc; + hcolfunc_post1 = rt_map1col; + hcolfunc_post4 = rt_map4cols; + } + else + { + colfunc = transcolfunc; + hcolfunc_post1 = rt_tlate1col; + hcolfunc_post4 = rt_tlate4cols; + } + return true; + } + if (flags & STYLEF_InvertSource) + { + dc_srcblend = Col2RGB8_Inverse[fglevel>>10]; + dc_destblend = Col2RGB8_LessPrecision[bglevel>>10]; + } + else if (op == STYLEOP_Add && fglevel + bglevel <= FRACUNIT) + { + dc_srcblend = Col2RGB8[fglevel>>10]; + dc_destblend = Col2RGB8[bglevel>>10]; + } + else + { + dc_srcblend = Col2RGB8_LessPrecision[fglevel>>10]; + dc_destblend = Col2RGB8_LessPrecision[bglevel>>10]; + } + switch (op) + { + case STYLEOP_Add: + if (fglevel == 0 && bglevel == FRACUNIT) + { + return false; + } + if (fglevel + bglevel <= FRACUNIT) + { // Colors won't overflow when added + if (flags & STYLEF_ColorIsFixed) + { + colfunc = R_FillAddColumn; + hcolfunc_post1 = rt_add1col; + hcolfunc_post4 = rt_add4cols; + } + else if (dc_translation == NULL) + { + colfunc = R_DrawAddColumnP_C; + hcolfunc_post1 = rt_add1col; + hcolfunc_post4 = rt_add4cols; + } + else + { + colfunc = R_DrawTlatedAddColumnP_C; + hcolfunc_post1 = rt_tlateadd1col; + hcolfunc_post4 = rt_tlateadd4cols; + } + } + else + { // Colors might overflow when added + if (flags & STYLEF_ColorIsFixed) + { + colfunc = R_FillAddClampColumn; + hcolfunc_post1 = rt_addclamp1col; + hcolfunc_post4 = rt_addclamp4cols; + } + else if (dc_translation == NULL) + { + colfunc = R_DrawAddClampColumnP_C; + hcolfunc_post1 = rt_addclamp1col; + hcolfunc_post4 = rt_addclamp4cols; + } + else + { + colfunc = R_DrawAddClampTranslatedColumnP_C; + hcolfunc_post1 = rt_tlateaddclamp1col; + hcolfunc_post4 = rt_tlateaddclamp4cols; + } + } + return true; + + case STYLEOP_Sub: + if (flags & STYLEF_ColorIsFixed) + { + colfunc = R_FillSubClampColumn; + hcolfunc_post1 = rt_subclamp1col; + hcolfunc_post4 = rt_subclamp4cols; + } + else if (dc_translation == NULL) + { + colfunc = R_DrawSubClampColumnP_C; + hcolfunc_post1 = rt_subclamp1col; + hcolfunc_post4 = rt_subclamp4cols; + } + else + { + colfunc = R_DrawSubClampTranslatedColumnP_C; + hcolfunc_post1 = rt_tlatesubclamp1col; + hcolfunc_post4 = rt_tlatesubclamp4cols; + } + return true; + + case STYLEOP_RevSub: + if (fglevel == 0 && bglevel == FRACUNIT) + { + return false; + } + if (flags & STYLEF_ColorIsFixed) + { + colfunc = R_FillRevSubClampColumn; + hcolfunc_post1 = rt_subclamp1col; + hcolfunc_post4 = rt_subclamp4cols; + } + else if (dc_translation == NULL) + { + colfunc = R_DrawRevSubClampColumnP_C; + hcolfunc_post1 = rt_revsubclamp1col; + hcolfunc_post4 = rt_revsubclamp4cols; + } + else + { + colfunc = R_DrawRevSubClampTranslatedColumnP_C; + hcolfunc_post1 = rt_tlaterevsubclamp1col; + hcolfunc_post4 = rt_tlaterevsubclamp4cols; + } + return true; + + default: + return false; + } +} + +static fixed_t GetAlpha(int type, fixed_t alpha) +{ + switch (type) + { + case STYLEALPHA_Zero: return 0; + case STYLEALPHA_One: return OPAQUE; + case STYLEALPHA_Src: return alpha; + case STYLEALPHA_InvSrc: return OPAQUE - alpha; + default: return 0; + } +} + +ESPSResult R_SetPatchStyle (FRenderStyle style, fixed_t alpha, int translation, DWORD color) +{ + fixed_t fglevel, bglevel; + + style.CheckFuzz(); + + if (style.BlendOp == STYLEOP_Shadow) + { + style = LegacyRenderStyles[STYLE_TranslucentStencil]; + alpha = TRANSLUC33; + color = 0; + } + + if (style.Flags & STYLEF_TransSoulsAlpha) + { + alpha = fixed_t(transsouls * OPAQUE); + } + else if (style.Flags & STYLEF_Alpha1) + { + alpha = FRACUNIT; + } + else + { + alpha = clamp (alpha, 0, OPAQUE); + } + + dc_translation = NULL; + if (translation != 0) + { + FRemapTable *table = TranslationToTable(translation); + if (table != NULL && !table->Inactive) + { + dc_translation = table->Remap; + } + } + basecolormapsave = basecolormap; + hcolfunc_pre = R_DrawColumnHoriz; + + // Check for special modes + if (style.BlendOp == STYLEOP_Fuzz) + { + colfunc = fuzzcolfunc; + return DoDraw0; + } + else if (style == LegacyRenderStyles[STYLE_Shaded]) + { + // Shaded drawer only gets 16 levels of alpha because it saves memory. + if ((alpha >>= 12) == 0) + return DontDraw; + colfunc = R_DrawShadedColumn; + hcolfunc_post1 = rt_shaded1col; + hcolfunc_post4 = rt_shaded4cols; + dc_color = fixedcolormap ? fixedcolormap[APART(color)] : basecolormap->Maps[APART(color)]; + dc_colormap = (basecolormap = &ShadeFakeColormap[16-alpha])->Maps; + if (fixedlightlev >= 0 && fixedcolormap == NULL) + { + dc_colormap += fixedlightlev; + } + return r_columnmethod ? DoDraw1 : DoDraw0; + } + + fglevel = GetAlpha(style.SrcAlpha, alpha); + bglevel = GetAlpha(style.DestAlpha, alpha); + + if (style.Flags & STYLEF_ColorIsFixed) + { + int x = fglevel >> 10; + int r = RPART(color); + int g = GPART(color); + int b = BPART(color); + // dc_color is used by the rt_* routines. It is indexed into dc_srcblend. + dc_color = RGB32k.RGB[r>>3][g>>3][b>>3]; + if (style.Flags & STYLEF_InvertSource) + { + r = 255 - r; + g = 255 - g; + b = 255 - b; + } + // dc_srccolor is used by the R_Fill* routines. It is premultiplied + // with the alpha. + dc_srccolor = ((((r*x)>>4)<<20) | ((g*x)>>4) | ((((b)*x)>>4)<<10)) & 0x3feffbff; + hcolfunc_pre = R_FillColumnHorizP; + dc_colormap = identitymap; + } + + if (!R_SetBlendFunc (style.BlendOp, fglevel, bglevel, style.Flags)) + { + return DontDraw; + } + return r_columnmethod ? DoDraw1 : DoDraw0; +} + +void R_FinishSetPatchStyle () +{ + basecolormap = basecolormapsave; +} + +bool R_GetTransMaskDrawers (fixed_t (**tmvline1)(), void (**tmvline4)()) +{ + if (colfunc == R_DrawAddColumnP_C) + { + *tmvline1 = tmvline1_add; + *tmvline4 = tmvline4_add; + return true; + } + if (colfunc == R_DrawAddClampColumnP_C) + { + *tmvline1 = tmvline1_addclamp; + *tmvline4 = tmvline4_addclamp; + return true; + } + if (colfunc == R_DrawSubClampColumnP_C) + { + *tmvline1 = tmvline1_subclamp; + *tmvline4 = tmvline4_subclamp; + return true; + } + if (colfunc == R_DrawRevSubClampColumnP_C) + { + *tmvline1 = tmvline1_revsubclamp; + *tmvline4 = tmvline4_revsubclamp; + return true; + } + return false; +} + diff --git a/src/r_draw.h b/src/r_draw.h new file mode 100644 index 000000000..cb2f68f33 --- /dev/null +++ b/src/r_draw.h @@ -0,0 +1,296 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id:$ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This source is available for distribution and/or modification +// only under the terms of the DOOM Source Code License as +// published by id Software. All rights reserved. +// +// The source is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License +// for more details. +// +// DESCRIPTION: +// System specific interface stuff. +// +//----------------------------------------------------------------------------- + + +#ifndef __R_DRAW__ +#define __R_DRAW__ + +#include "r_defs.h" + +extern "C" int ylookup[MAXHEIGHT]; + +extern "C" int dc_pitch; // [RH] Distance between rows + +extern "C" lighttable_t*dc_colormap; +extern "C" int dc_x; +extern "C" int dc_yl; +extern "C" int dc_yh; +extern "C" fixed_t dc_iscale; +extern double dc_texturemid; +extern "C" fixed_t dc_texturefrac; +extern "C" int dc_color; // [RH] For flat colors (no texturing) +extern "C" DWORD dc_srccolor; +extern "C" DWORD *dc_srcblend; +extern "C" DWORD *dc_destblend; + +// first pixel in a column +extern "C" const BYTE* dc_source; + +extern "C" BYTE *dc_dest, *dc_destorg; +extern "C" int dc_count; + +extern "C" DWORD vplce[4]; +extern "C" DWORD vince[4]; +extern "C" BYTE* palookupoffse[4]; +extern "C" const BYTE* bufplce[4]; + +// [RH] Temporary buffer for column drawing +extern "C" BYTE *dc_temp; +extern "C" unsigned int dc_tspans[4][MAXHEIGHT]; +extern "C" unsigned int *dc_ctspan[4]; +extern "C" unsigned int horizspans[4]; + + +// [RH] Pointers to the different column and span drawers... + +// The span blitting interface. +// Hook in assembler or system specific BLT here. +extern void (*R_DrawColumn)(void); + +extern DWORD (*dovline1) (); +extern DWORD (*doprevline1) (); +#ifdef X64_ASM +#define dovline4 vlinetallasm4 +extern "C" void vlinetallasm4(); +#else +extern void (*dovline4) (); +#endif +extern void setupvline (int); + +extern DWORD (*domvline1) (); +extern void (*domvline4) (); +extern void setupmvline (int); + +extern void setuptmvline (int); + +// The Spectre/Invisibility effect. +extern void (*R_DrawFuzzColumn)(void); + +// [RH] Draw shaded column +extern void (*R_DrawShadedColumn)(void); + +// Draw with color translation tables, for player sprite rendering, +// Green/Red/Blue/Indigo shirts. +extern void (*R_DrawTranslatedColumn)(void); + +// Span drawing for rows, floor/ceiling. No Spectre effect needed. +extern void (*R_DrawSpan)(void); +void R_SetupSpanBits(FTexture *tex); +void R_SetSpanColormap(BYTE *colormap); +void R_SetSpanSource(const BYTE *pixels); + +// Span drawing for masked textures. +extern void (*R_DrawSpanMasked)(void); + +// Span drawing for translucent textures. +extern void (*R_DrawSpanTranslucent)(void); + +// Span drawing for masked, translucent textures. +extern void (*R_DrawSpanMaskedTranslucent)(void); + +// Span drawing for translucent, additive textures. +extern void (*R_DrawSpanAddClamp)(void); + +// Span drawing for masked, translucent, additive textures. +extern void (*R_DrawSpanMaskedAddClamp)(void); + +// [RH] Span blit into an interleaved intermediate buffer +extern void (*R_DrawColumnHoriz)(void); +void R_DrawMaskedColumnHoriz (const BYTE *column, const FTexture::Span *spans); + +// [RH] Initialize the above pointers +void R_InitColumnDrawers (); + +// [RH] Moves data from the temporary buffer to the screen. +extern "C" +{ +void rt_copy1col_c (int hx, int sx, int yl, int yh); +void rt_copy4cols_c (int sx, int yl, int yh); + +void rt_shaded1col (int hx, int sx, int yl, int yh); +void rt_shaded4cols_c (int sx, int yl, int yh); +void rt_shaded4cols_asm (int sx, int yl, int yh); + +void rt_map1col_c (int hx, int sx, int yl, int yh); +void rt_add1col (int hx, int sx, int yl, int yh); +void rt_addclamp1col (int hx, int sx, int yl, int yh); +void rt_subclamp1col (int hx, int sx, int yl, int yh); +void rt_revsubclamp1col (int hx, int sx, int yl, int yh); + +void rt_tlate1col (int hx, int sx, int yl, int yh); +void rt_tlateadd1col (int hx, int sx, int yl, int yh); +void rt_tlateaddclamp1col (int hx, int sx, int yl, int yh); +void rt_tlatesubclamp1col (int hx, int sx, int yl, int yh); +void rt_tlaterevsubclamp1col (int hx, int sx, int yl, int yh); + +void rt_map4cols_c (int sx, int yl, int yh); +void rt_add4cols_c (int sx, int yl, int yh); +void rt_addclamp4cols_c (int sx, int yl, int yh); +void rt_subclamp4cols (int sx, int yl, int yh); +void rt_revsubclamp4cols (int sx, int yl, int yh); + +void rt_tlate4cols (int sx, int yl, int yh); +void rt_tlateadd4cols (int sx, int yl, int yh); +void rt_tlateaddclamp4cols (int sx, int yl, int yh); +void rt_tlatesubclamp4cols (int sx, int yl, int yh); +void rt_tlaterevsubclamp4cols (int sx, int yl, int yh); + +void rt_copy1col_asm (int hx, int sx, int yl, int yh); +void rt_map1col_asm (int hx, int sx, int yl, int yh); + +void rt_copy4cols_asm (int sx, int yl, int yh); +void rt_map4cols_asm1 (int sx, int yl, int yh); +void rt_map4cols_asm2 (int sx, int yl, int yh); +void rt_add4cols_asm (int sx, int yl, int yh); +void rt_addclamp4cols_asm (int sx, int yl, int yh); +} + +extern void (*rt_map4cols)(int sx, int yl, int yh); + +#ifdef X86_ASM +#define rt_copy1col rt_copy1col_asm +#define rt_copy4cols rt_copy4cols_asm +#define rt_map1col rt_map1col_asm +#define rt_shaded4cols rt_shaded4cols_asm +#define rt_add4cols rt_add4cols_asm +#define rt_addclamp4cols rt_addclamp4cols_asm +#else +#define rt_copy1col rt_copy1col_c +#define rt_copy4cols rt_copy4cols_c +#define rt_map1col rt_map1col_c +#define rt_shaded4cols rt_shaded4cols_c +#define rt_add4cols rt_add4cols_c +#define rt_addclamp4cols rt_addclamp4cols_c +#endif + +void rt_draw4cols (int sx); + +// [RH] Preps the temporary horizontal buffer. +void rt_initcols (BYTE *buffer=NULL); + +void R_DrawFogBoundary (int x1, int x2, short *uclip, short *dclip); + + +#ifdef X86_ASM + +extern "C" void R_DrawColumnP_Unrolled (void); +extern "C" void R_DrawColumnHorizP_ASM (void); +extern "C" void R_DrawColumnP_ASM (void); +extern "C" void R_DrawFuzzColumnP_ASM (void); + void R_DrawTranslatedColumnP_C (void); + void R_DrawShadedColumnP_C (void); +extern "C" void R_DrawSpanP_ASM (void); +extern "C" void R_DrawSpanMaskedP_ASM (void); + +#else + +void R_DrawColumnHorizP_C (void); +void R_DrawColumnP_C (void); +void R_DrawFuzzColumnP_C (void); +void R_DrawTranslatedColumnP_C (void); +void R_DrawShadedColumnP_C (void); +void R_DrawSpanP_C (void); +void R_DrawSpanMaskedP_C (void); + +#endif + +void R_DrawSpanTranslucentP_C (void); +void R_DrawSpanMaskedTranslucentP_C (void); + +void R_DrawTlatedLucentColumnP_C (void); +#define R_DrawTlatedLucentColumn R_DrawTlatedLucentColumnP_C + +void R_FillColumnP (void); +void R_FillColumnHorizP (void); +void R_FillSpan (void); + +#ifdef X86_ASM +#define R_SetupDrawSlab R_SetupDrawSlabA +#define R_DrawSlab R_DrawSlabA +#else +#define R_SetupDrawSlab R_SetupDrawSlabC +#define R_DrawSlab R_DrawSlabC +#endif + +extern "C" void R_SetupDrawSlab(const BYTE *colormap); +extern "C" void R_DrawSlab(int dx, fixed_t v, int dy, fixed_t vi, const BYTE *vptr, BYTE *p); + +extern "C" int ds_y; +extern "C" int ds_x1; +extern "C" int ds_x2; + +extern "C" lighttable_t* ds_colormap; + +extern "C" dsfixed_t ds_xfrac; +extern "C" dsfixed_t ds_yfrac; +extern "C" dsfixed_t ds_xstep; +extern "C" dsfixed_t ds_ystep; +extern "C" int ds_xbits; +extern "C" int ds_ybits; +extern "C" fixed_t ds_alpha; + +// start of a 64*64 tile image +extern "C" const BYTE* ds_source; + +extern "C" int ds_color; // [RH] For flat color (no texturing) + +extern BYTE shadetables[/*NUMCOLORMAPS*16*256*/]; +extern FDynamicColormap ShadeFakeColormap[16]; +extern BYTE identitymap[256]; +extern BYTE *dc_translation; + +// [RH] Added for muliresolution support +void R_InitShadeMaps(); +void R_InitFuzzTable (int fuzzoff); + +// [RH] Consolidate column drawer selection +enum ESPSResult +{ + DontDraw, // not useful to draw this + DoDraw0, // draw this as if r_columnmethod is 0 + DoDraw1, // draw this as if r_columnmethod is 1 +}; +ESPSResult R_SetPatchStyle (FRenderStyle style, fixed_t alpha, int translation, DWORD color); +inline ESPSResult R_SetPatchStyle(FRenderStyle style, float alpha, int translation, DWORD color) +{ + return R_SetPatchStyle(style, FLOAT2FIXED(alpha), translation, color); +} + +// Call this after finished drawing the current thing, in case its +// style was STYLE_Shade +void R_FinishSetPatchStyle (); + +// transmaskwallscan calls this to find out what column drawers to use +bool R_GetTransMaskDrawers (fixed_t (**tmvline1)(), void (**tmvline4)()); + +// Retrieve column data for wallscan. Should probably be removed +// to just use the texture's GetColumn() method. It just exists +// for double-layer skies. +const BYTE *R_GetColumn (FTexture *tex, int col); +void wallscan (int x1, int x2, short *uwal, short *dwal, float *swal, fixed_t *lwal, double yrepeat, const BYTE *(*getcol)(FTexture *tex, int col)=R_GetColumn); + +// maskwallscan is exactly like wallscan but does not draw anything where the texture is color 0. +void maskwallscan (int x1, int x2, short *uwal, short *dwal, float *swal, fixed_t *lwal, double yrepeat, const BYTE *(*getcol)(FTexture *tex, int col)=R_GetColumn); + +// transmaskwallscan is like maskwallscan, but it can also blend to the background +void transmaskwallscan (int x1, int x2, short *uwal, short *dwal, float *swal, fixed_t *lwal, double yrepeat, const BYTE *(*getcol)(FTexture *tex, int col)=R_GetColumn); + +#endif diff --git a/src/r_drawt.cpp b/src/r_drawt.cpp new file mode 100644 index 000000000..e8faff0ce --- /dev/null +++ b/src/r_drawt.cpp @@ -0,0 +1,1203 @@ +/* +** r_drawt.cpp +** Faster column drawers for modern processors +** +**--------------------------------------------------------------------------- +** Copyright 1998-2006 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** These functions stretch columns into a temporary buffer and then +** map them to the screen. On modern machines, this is faster than drawing +** them directly to the screen. +** +** Will I be able to even understand any of this if I come back to it later? +** Let's hope so. :-) +*/ + +#include "templates.h" +#include "doomtype.h" +#include "doomdef.h" +#include "r_defs.h" +#include "r_draw.h" +#include "r_main.h" +#include "r_things.h" +#include "v_video.h" + +// I should have commented this stuff better. +// +// dc_temp is the buffer R_DrawColumnHoriz writes into. +// dc_tspans points into it. +// dc_ctspan points into dc_tspans. +// horizspan also points into dc_tspans. + +// dc_ctspan is advanced while drawing into dc_temp. +// horizspan is advanced up to dc_ctspan when drawing from dc_temp to the screen. + +BYTE dc_tempbuff[MAXHEIGHT*4]; +BYTE *dc_temp; +unsigned int dc_tspans[4][MAXHEIGHT]; +unsigned int *dc_ctspan[4]; +unsigned int *horizspan[4]; + +#ifdef X86_ASM +extern "C" void R_SetupShadedCol(); +extern "C" void R_SetupAddCol(); +extern "C" void R_SetupAddClampCol(); +#endif + +#ifndef X86_ASM +// Copies one span at hx to the screen at sx. +void rt_copy1col_c (int hx, int sx, int yl, int yh) +{ + BYTE *source; + BYTE *dest; + int count; + int pitch; + + count = yh-yl; + if (count < 0) + return; + count++; + + dest = ylookup[yl] + sx + dc_destorg; + source = &dc_temp[yl*4 + hx]; + pitch = dc_pitch; + + if (count & 1) { + *dest = *source; + source += 4; + dest += pitch; + } + if (count & 2) { + dest[0] = source[0]; + dest[pitch] = source[4]; + source += 8; + dest += pitch*2; + } + if (!(count >>= 2)) + return; + + do { + dest[0] = source[0]; + dest[pitch] = source[4]; + dest[pitch*2] = source[8]; + dest[pitch*3] = source[12]; + source += 16; + dest += pitch*4; + } while (--count); +} + +// Copies all four spans to the screen starting at sx. +void rt_copy4cols_c (int sx, int yl, int yh) +{ + int *source; + int *dest; + int count; + int pitch; + + count = yh-yl; + if (count < 0) + return; + count++; + + dest = (int *)(ylookup[yl] + sx + dc_destorg); + source = (int *)(&dc_temp[yl*4]); + pitch = dc_pitch/sizeof(int); + + if (count & 1) { + *dest = *source; + source += 4/sizeof(int); + dest += pitch; + } + if (!(count >>= 1)) + return; + + do { + dest[0] = source[0]; + dest[pitch] = source[4/sizeof(int)]; + source += 8/sizeof(int); + dest += pitch*2; + } while (--count); +} + +// Maps one span at hx to the screen at sx. +void rt_map1col_c (int hx, int sx, int yl, int yh) +{ + BYTE *colormap; + BYTE *source; + BYTE *dest; + int count; + int pitch; + + count = yh-yl; + if (count < 0) + return; + count++; + + colormap = dc_colormap; + dest = ylookup[yl] + sx + dc_destorg; + source = &dc_temp[yl*4 + hx]; + pitch = dc_pitch; + + if (count & 1) { + *dest = colormap[*source]; + source += 4; + dest += pitch; + } + if (!(count >>= 1)) + return; + + do { + dest[0] = colormap[source[0]]; + dest[pitch] = colormap[source[4]]; + source += 8; + dest += pitch*2; + } while (--count); +} + +// Maps all four spans to the screen starting at sx. +void rt_map4cols_c (int sx, int yl, int yh) +{ + BYTE *colormap; + BYTE *source; + BYTE *dest; + int count; + int pitch; + + count = yh-yl; + if (count < 0) + return; + count++; + + colormap = dc_colormap; + dest = ylookup[yl] + sx + dc_destorg; + source = &dc_temp[yl*4]; + pitch = dc_pitch; + + if (count & 1) { + dest[0] = colormap[source[0]]; + dest[1] = colormap[source[1]]; + dest[2] = colormap[source[2]]; + dest[3] = colormap[source[3]]; + source += 4; + dest += pitch; + } + if (!(count >>= 1)) + return; + + do { + dest[0] = colormap[source[0]]; + dest[1] = colormap[source[1]]; + dest[2] = colormap[source[2]]; + dest[3] = colormap[source[3]]; + dest[pitch] = colormap[source[4]]; + dest[pitch+1] = colormap[source[5]]; + dest[pitch+2] = colormap[source[6]]; + dest[pitch+3] = colormap[source[7]]; + source += 8; + dest += pitch*2; + } while (--count); +} +#endif + +void rt_Translate1col(const BYTE *translation, int hx, int yl, int yh) +{ + int count = yh - yl + 1; + BYTE *source = &dc_temp[yl*4 + hx]; + + // Things we do to hit the compiler's optimizer with a clue bat: + // 1. Parallelism is explicitly spelled out by using a separate + // C instruction for each assembly instruction. GCC lets me + // have four temporaries, but VC++ spills to the stack with + // more than two. Two is probably optimal, anyway. + // 2. The results of the translation lookups are explicitly + // stored in byte-sized variables. This causes the VC++ code + // to use byte mov instructions in most cases; for apparently + // random reasons, it will use movzx for some places. GCC + // ignores this and uses movzx always. + + // Do 8 rows at a time. + for (int count8 = count >> 3; count8; --count8) + { + int c0, c1; + BYTE b0, b1; + + c0 = source[0]; c1 = source[4]; + b0 = translation[c0]; b1 = translation[c1]; + source[0] = b0; source[4] = b1; + + c0 = source[8]; c1 = source[12]; + b0 = translation[c0]; b1 = translation[c1]; + source[8] = b0; source[12] = b1; + + c0 = source[16]; c1 = source[20]; + b0 = translation[c0]; b1 = translation[c1]; + source[16] = b0; source[20] = b1; + + c0 = source[24]; c1 = source[28]; + b0 = translation[c0]; b1 = translation[c1]; + source[24] = b0; source[28] = b1; + + source += 32; + } + // Finish by doing 1 row at a time. + for (count &= 7; count; --count, source += 4) + { + source[0] = translation[source[0]]; + } +} + +void rt_Translate4cols(const BYTE *translation, int yl, int yh) +{ + int count = yh - yl + 1; + BYTE *source = &dc_temp[yl*4]; + int c0, c1; + BYTE b0, b1; + + // Do 2 rows at a time. + for (int count8 = count >> 1; count8; --count8) + { + c0 = source[0]; c1 = source[1]; + b0 = translation[c0]; b1 = translation[c1]; + source[0] = b0; source[1] = b1; + + c0 = source[2]; c1 = source[3]; + b0 = translation[c0]; b1 = translation[c1]; + source[2] = b0; source[3] = b1; + + c0 = source[4]; c1 = source[5]; + b0 = translation[c0]; b1 = translation[c1]; + source[4] = b0; source[5] = b1; + + c0 = source[6]; c1 = source[7]; + b0 = translation[c0]; b1 = translation[c1]; + source[6] = b0; source[7] = b1; + + source += 8; + } + // Do the final row if count was odd. + if (count & 1) + { + c0 = source[0]; c1 = source[1]; + b0 = translation[c0]; b1 = translation[c1]; + source[0] = b0; source[1] = b1; + + c0 = source[2]; c1 = source[3]; + b0 = translation[c0]; b1 = translation[c1]; + source[2] = b0; source[3] = b1; + } +} + +// Translates one span at hx to the screen at sx. +void rt_tlate1col (int hx, int sx, int yl, int yh) +{ + rt_Translate1col(dc_translation, hx, yl, yh); + rt_map1col(hx, sx, yl, yh); +} + +// Translates all four spans to the screen starting at sx. +void rt_tlate4cols (int sx, int yl, int yh) +{ + rt_Translate4cols(dc_translation, yl, yh); + rt_map4cols(sx, yl, yh); +} + +// Adds one span at hx to the screen at sx without clamping. +void rt_add1col (int hx, int sx, int yl, int yh) +{ + BYTE *colormap; + BYTE *source; + BYTE *dest; + int count; + int pitch; + + count = yh-yl; + if (count < 0) + return; + count++; + + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + dest = ylookup[yl] + sx + dc_destorg; + source = &dc_temp[yl*4 + hx]; + pitch = dc_pitch; + colormap = dc_colormap; + + do { + DWORD fg = colormap[*source]; + DWORD bg = *dest; + + fg = fg2rgb[fg]; + bg = bg2rgb[bg]; + fg = (fg+bg) | 0x1f07c1f; + *dest = RGB32k.All[fg & (fg>>15)]; + source += 4; + dest += pitch; + } while (--count); +} + +// Adds all four spans to the screen starting at sx without clamping. +void rt_add4cols_c (int sx, int yl, int yh) +{ + BYTE *colormap; + BYTE *source; + BYTE *dest; + int count; + int pitch; + + count = yh-yl; + if (count < 0) + return; + count++; + + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + dest = ylookup[yl] + sx + dc_destorg; + source = &dc_temp[yl*4]; + pitch = dc_pitch; + colormap = dc_colormap; + + do { + DWORD fg = colormap[source[0]]; + DWORD bg = dest[0]; + fg = fg2rgb[fg]; + bg = bg2rgb[bg]; + fg = (fg+bg) | 0x1f07c1f; + dest[0] = RGB32k.All[fg & (fg>>15)]; + + fg = colormap[source[1]]; + bg = dest[1]; + fg = fg2rgb[fg]; + bg = bg2rgb[bg]; + fg = (fg+bg) | 0x1f07c1f; + dest[1] = RGB32k.All[fg & (fg>>15)]; + + + fg = colormap[source[2]]; + bg = dest[2]; + fg = fg2rgb[fg]; + bg = bg2rgb[bg]; + fg = (fg+bg) | 0x1f07c1f; + dest[2] = RGB32k.All[fg & (fg>>15)]; + + fg = colormap[source[3]]; + bg = dest[3]; + fg = fg2rgb[fg]; + bg = bg2rgb[bg]; + fg = (fg+bg) | 0x1f07c1f; + dest[3] = RGB32k.All[fg & (fg>>15)]; + + source += 4; + dest += pitch; + } while (--count); +} + +// Translates and adds one span at hx to the screen at sx without clamping. +void rt_tlateadd1col (int hx, int sx, int yl, int yh) +{ + rt_Translate1col(dc_translation, hx, yl, yh); + rt_add1col(hx, sx, yl, yh); +} + +// Translates and adds all four spans to the screen starting at sx without clamping. +void rt_tlateadd4cols (int sx, int yl, int yh) +{ + rt_Translate4cols(dc_translation, yl, yh); + rt_add4cols(sx, yl, yh); +} + +// Shades one span at hx to the screen at sx. +void rt_shaded1col (int hx, int sx, int yl, int yh) +{ + DWORD *fgstart; + BYTE *colormap; + BYTE *source; + BYTE *dest; + int count; + int pitch; + + count = yh-yl; + if (count < 0) + return; + count++; + + fgstart = &Col2RGB8[0][dc_color]; + colormap = dc_colormap; + dest = ylookup[yl] + sx + dc_destorg; + source = &dc_temp[yl*4 + hx]; + pitch = dc_pitch; + + do { + DWORD val = colormap[*source]; + DWORD fg = fgstart[val<<8]; + val = (Col2RGB8[64-val][*dest] + fg) | 0x1f07c1f; + *dest = RGB32k.All[val & (val>>15)]; + source += 4; + dest += pitch; + } while (--count); +} + +// Shades all four spans to the screen starting at sx. +void rt_shaded4cols_c (int sx, int yl, int yh) +{ + DWORD *fgstart; + BYTE *colormap; + BYTE *source; + BYTE *dest; + int count; + int pitch; + + count = yh-yl; + if (count < 0) + return; + count++; + + fgstart = &Col2RGB8[0][dc_color]; + colormap = dc_colormap; + dest = ylookup[yl] + sx + dc_destorg; + source = &dc_temp[yl*4]; + pitch = dc_pitch; + + do { + DWORD val; + + val = colormap[source[0]]; + val = (Col2RGB8[64-val][dest[0]] + fgstart[val<<8]) | 0x1f07c1f; + dest[0] = RGB32k.All[val & (val>>15)]; + + val = colormap[source[1]]; + val = (Col2RGB8[64-val][dest[1]] + fgstart[val<<8]) | 0x1f07c1f; + dest[1] = RGB32k.All[val & (val>>15)]; + + val = colormap[source[2]]; + val = (Col2RGB8[64-val][dest[2]] + fgstart[val<<8]) | 0x1f07c1f; + dest[2] = RGB32k.All[val & (val>>15)]; + + val = colormap[source[3]]; + val = (Col2RGB8[64-val][dest[3]] + fgstart[val<<8]) | 0x1f07c1f; + dest[3] = RGB32k.All[val & (val>>15)]; + + source += 4; + dest += pitch; + } while (--count); +} + +// Adds one span at hx to the screen at sx with clamping. +void rt_addclamp1col (int hx, int sx, int yl, int yh) +{ + BYTE *colormap; + BYTE *source; + BYTE *dest; + int count; + int pitch; + + count = yh-yl; + if (count < 0) + return; + count++; + + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + dest = ylookup[yl] + sx + dc_destorg; + source = &dc_temp[yl*4 + hx]; + pitch = dc_pitch; + colormap = dc_colormap; + + do { + DWORD a = fg2rgb[colormap[*source]] + bg2rgb[*dest]; + DWORD b = a; + + a |= 0x01f07c1f; + b &= 0x40100400; + a &= 0x3fffffff; + b = b - (b >> 5); + a |= b; + *dest = RGB32k.All[(a>>15) & a]; + source += 4; + dest += pitch; + } while (--count); +} + +// Adds all four spans to the screen starting at sx with clamping. +void rt_addclamp4cols_c (int sx, int yl, int yh) +{ + BYTE *colormap; + BYTE *source; + BYTE *dest; + int count; + int pitch; + + count = yh-yl; + if (count < 0) + return; + count++; + + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + dest = ylookup[yl] + sx + dc_destorg; + source = &dc_temp[yl*4]; + pitch = dc_pitch; + colormap = dc_colormap; + + do { + DWORD a = fg2rgb[colormap[source[0]]] + bg2rgb[dest[0]]; + DWORD b = a; + + a |= 0x01f07c1f; + b &= 0x40100400; + a &= 0x3fffffff; + b = b - (b >> 5); + a |= b; + dest[0] = RGB32k.All[(a>>15) & a]; + + a = fg2rgb[colormap[source[1]]] + bg2rgb[dest[1]]; + b = a; + a |= 0x01f07c1f; + b &= 0x40100400; + a &= 0x3fffffff; + b = b - (b >> 5); + a |= b; + dest[1] = RGB32k.All[(a>>15) & a]; + + a = fg2rgb[colormap[source[2]]] + bg2rgb[dest[2]]; + b = a; + a |= 0x01f07c1f; + b &= 0x40100400; + a &= 0x3fffffff; + b = b - (b >> 5); + a |= b; + dest[2] = RGB32k.All[(a>>15) & a]; + + a = fg2rgb[colormap[source[3]]] + bg2rgb[dest[3]]; + b = a; + a |= 0x01f07c1f; + b &= 0x40100400; + a &= 0x3fffffff; + b = b - (b >> 5); + a |= b; + dest[3] = RGB32k.All[(a>>15) & a]; + + source += 4; + dest += pitch; + } while (--count); +} + +// Translates and adds one span at hx to the screen at sx with clamping. +void rt_tlateaddclamp1col (int hx, int sx, int yl, int yh) +{ + rt_Translate1col(dc_translation, hx, yl, yh); + rt_addclamp1col(hx, sx, yl, yh); +} + +// Translates and adds all four spans to the screen starting at sx with clamping. +void rt_tlateaddclamp4cols (int sx, int yl, int yh) +{ + rt_Translate4cols(dc_translation, yl, yh); + rt_addclamp4cols(sx, yl, yh); +} + +// Subtracts one span at hx to the screen at sx with clamping. +void rt_subclamp1col (int hx, int sx, int yl, int yh) +{ + BYTE *colormap; + BYTE *source; + BYTE *dest; + int count; + int pitch; + + count = yh-yl; + if (count < 0) + return; + count++; + + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + dest = ylookup[yl] + sx + dc_destorg; + source = &dc_temp[yl*4 + hx]; + pitch = dc_pitch; + colormap = dc_colormap; + + do { + DWORD a = (fg2rgb[colormap[*source]] | 0x40100400) - bg2rgb[*dest]; + DWORD b = a; + + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + *dest = RGB32k.All[(a>>15) & a]; + source += 4; + dest += pitch; + } while (--count); +} + +// Subtracts all four spans to the screen starting at sx with clamping. +void rt_subclamp4cols (int sx, int yl, int yh) +{ + BYTE *colormap; + BYTE *source; + BYTE *dest; + int count; + int pitch; + + count = yh-yl; + if (count < 0) + return; + count++; + + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + dest = ylookup[yl] + sx + dc_destorg; + source = &dc_temp[yl*4]; + pitch = dc_pitch; + colormap = dc_colormap; + + do { + DWORD a = (fg2rgb[colormap[source[0]]] | 0x40100400) - bg2rgb[dest[0]]; + DWORD b = a; + + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + dest[0] = RGB32k.All[(a>>15) & a]; + + a = (fg2rgb[colormap[source[1]]] | 0x40100400) - bg2rgb[dest[1]]; + b = a; + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + dest[1] = RGB32k.All[(a>>15) & a]; + + a = (fg2rgb[colormap[source[2]]] | 0x40100400) - bg2rgb[dest[2]]; + b = a; + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + dest[2] = RGB32k.All[(a>>15) & a]; + + a = (fg2rgb[colormap[source[3]]] | 0x40100400) - bg2rgb[dest[3]]; + b = a; + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + dest[3] = RGB32k.All[(a>>15) & a]; + + source += 4; + dest += pitch; + } while (--count); +} + +// Translates and subtracts one span at hx to the screen at sx with clamping. +void rt_tlatesubclamp1col (int hx, int sx, int yl, int yh) +{ + rt_Translate1col(dc_translation, hx, yl, yh); + rt_subclamp1col(hx, sx, yl, yh); +} + +// Translates and subtracts all four spans to the screen starting at sx with clamping. +void rt_tlatesubclamp4cols (int sx, int yl, int yh) +{ + rt_Translate4cols(dc_translation, yl, yh); + rt_subclamp4cols(sx, yl, yh); +} + +// Subtracts one span at hx from the screen at sx with clamping. +void rt_revsubclamp1col (int hx, int sx, int yl, int yh) +{ + BYTE *colormap; + BYTE *source; + BYTE *dest; + int count; + int pitch; + + count = yh-yl; + if (count < 0) + return; + count++; + + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + dest = ylookup[yl] + sx + dc_destorg; + source = &dc_temp[yl*4 + hx]; + pitch = dc_pitch; + colormap = dc_colormap; + + do { + DWORD a = (bg2rgb[*dest] | 0x40100400) - fg2rgb[colormap[*source]]; + DWORD b = a; + + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + *dest = RGB32k.All[(a>>15) & a]; + source += 4; + dest += pitch; + } while (--count); +} + +// Subtracts all four spans from the screen starting at sx with clamping. +void rt_revsubclamp4cols (int sx, int yl, int yh) +{ + BYTE *colormap; + BYTE *source; + BYTE *dest; + int count; + int pitch; + + count = yh-yl; + if (count < 0) + return; + count++; + + DWORD *fg2rgb = dc_srcblend; + DWORD *bg2rgb = dc_destblend; + dest = ylookup[yl] + sx + dc_destorg; + source = &dc_temp[yl*4]; + pitch = dc_pitch; + colormap = dc_colormap; + + do { + DWORD a = (bg2rgb[dest[0]] | 0x40100400) - fg2rgb[colormap[source[0]]]; + DWORD b = a; + + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + dest[0] = RGB32k.All[(a>>15) & a]; + + a = (bg2rgb[dest[1]] | 0x40100400) - fg2rgb[colormap[source[1]]]; + b = a; + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + dest[1] = RGB32k.All[(a>>15) & a]; + + a = (bg2rgb[dest[2]] | 0x40100400) - fg2rgb[colormap[source[2]]]; + b = a; + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + dest[2] = RGB32k.All[(a>>15) & a]; + + a = (bg2rgb[dest[3]] | 0x40100400) - fg2rgb[colormap[source[3]]]; + b = a; + b &= 0x40100400; + b = b - (b >> 5); + a &= b; + a |= 0x01f07c1f; + dest[3] = RGB32k.All[(a>>15) & a]; + + source += 4; + dest += pitch; + } while (--count); +} + +// Translates and subtracts one span at hx from the screen at sx with clamping. +void rt_tlaterevsubclamp1col (int hx, int sx, int yl, int yh) +{ + rt_Translate1col(dc_translation, hx, yl, yh); + rt_revsubclamp1col(hx, sx, yl, yh); +} + +// Translates and subtracts all four spans from the screen starting at sx with clamping. +void rt_tlaterevsubclamp4cols (int sx, int yl, int yh) +{ + rt_Translate4cols(dc_translation, yl, yh); + rt_revsubclamp4cols(sx, yl, yh); +} + +// Copies all spans in all four columns to the screen starting at sx. +// sx should be dword-aligned. +void rt_draw4cols (int sx) +{ + int x, bad; + unsigned int maxtop, minbot, minnexttop; + + // Place a dummy "span" in each column. These don't get + // drawn. They're just here to avoid special cases in the + // max/min calculations below. + for (x = 0; x < 4; ++x) + { + dc_ctspan[x][0] = screen->GetHeight()+1; + dc_ctspan[x][1] = screen->GetHeight(); + } + +#ifdef X86_ASM + // Setup assembly routines for changed colormaps or other parameters. + if (hcolfunc_post4 == rt_shaded4cols) + { + R_SetupShadedCol(); + } + else if (hcolfunc_post4 == rt_addclamp4cols || hcolfunc_post4 == rt_tlateaddclamp4cols) + { + R_SetupAddClampCol(); + } + else if (hcolfunc_post4 == rt_add4cols || hcolfunc_post4 == rt_tlateadd4cols) + { + R_SetupAddCol(); + } +#endif + + for (;;) + { + // If a column is out of spans, mark it as such + bad = 0; + minnexttop = 0xffffffff; + for (x = 0; x < 4; ++x) + { + if (horizspan[x] >= dc_ctspan[x]) + { + bad |= 1 << x; + } + else if ((horizspan[x]+2)[0] < minnexttop) + { + minnexttop = (horizspan[x]+2)[0]; + } + } + // Once all columns are out of spans, we're done + if (bad == 15) + { + return; + } + + // Find the largest shared area for the spans in each column + maxtop = MAX (MAX (horizspan[0][0], horizspan[1][0]), + MAX (horizspan[2][0], horizspan[3][0])); + minbot = MIN (MIN (horizspan[0][1], horizspan[1][1]), + MIN (horizspan[2][1], horizspan[3][1])); + + // If there is no shared area with these spans, draw each span + // individually and advance to the next spans until we reach a shared area. + // However, only draw spans down to the highest span in the next set of + // spans. If we allow the entire height of a span to be drawn, it could + // prevent any more shared areas from being drawn in these four columns. + // + // Example: Suppose we have the following arrangement: + // A CD + // A CD + // B D + // B D + // aB D + // aBcD + // aBcD + // aBc + // + // If we draw the entire height of the spans, we end up drawing this first: + // A CD + // A CD + // B D + // B D + // B D + // B D + // B D + // B D + // B + // + // This leaves only the "a" and "c" columns to be drawn, and they are not + // part of a shared area, but if we can include B and D with them, we can + // get a shared area. So we cut off everything in the first set just + // above the "a" column and end up drawing this first: + // A CD + // A CD + // B D + // B D + // + // Then the next time through, we have the following arrangement with an + // easily shared area to draw: + // aB D + // aBcD + // aBcD + // aBc + if (bad != 0 || maxtop > minbot) + { + int drawcount = 0; + for (x = 0; x < 4; ++x) + { + if (!(bad & 1)) + { + if (horizspan[x][1] < minnexttop) + { + hcolfunc_post1 (x, sx+x, horizspan[x][0], horizspan[x][1]); + horizspan[x] += 2; + drawcount++; + } + else if (minnexttop > horizspan[x][0]) + { + hcolfunc_post1 (x, sx+x, horizspan[x][0], minnexttop-1); + horizspan[x][0] = minnexttop; + drawcount++; + } + } + bad >>= 1; + } + // Drawcount *should* always be non-zero. The reality is that some situations + // can make this not true. Unfortunately, I'm not sure what those situations are. + if (drawcount == 0) + { + return; + } + continue; + } + + // Draw any span fragments above the shared area. + for (x = 0; x < 4; ++x) + { + if (maxtop > horizspan[x][0]) + { + hcolfunc_post1 (x, sx+x, horizspan[x][0], maxtop-1); + } + } + + // Draw the shared area. + hcolfunc_post4 (sx, maxtop, minbot); + + // For each column, if part of the span is past the shared area, + // set its top to just below the shared area. Otherwise, advance + // to the next span in that column. + for (x = 0; x < 4; ++x) + { + if (minbot < horizspan[x][1]) + { + horizspan[x][0] = minbot+1; + } + else + { + horizspan[x] += 2; + } + } + } +} + +// Before each pass through a rendering loop that uses these routines, +// call this function to set up the span pointers. +void rt_initcols (BYTE *buff) +{ + int y; + + dc_temp = buff == NULL ? dc_tempbuff : buff; + for (y = 3; y >= 0; y--) + horizspan[y] = dc_ctspan[y] = &dc_tspans[y][0]; +} + +// Stretches a column into a temporary buffer which is later +// drawn to the screen along with up to three other columns. +void R_DrawColumnHorizP_C (void) +{ + int count = dc_count; + BYTE *dest; + fixed_t fracstep; + fixed_t frac; + + if (count <= 0) + return; + + { + int x = dc_x & 3; + unsigned int **span; + + span = &dc_ctspan[x]; + (*span)[0] = dc_yl; + (*span)[1] = dc_yh; + *span += 2; + dest = &dc_temp[x + 4*dc_yl]; + } + fracstep = dc_iscale; + frac = dc_texturefrac; + + { + const BYTE *source = dc_source; + + if (count & 1) { + *dest = source[frac>>FRACBITS]; dest += 4; frac += fracstep; + } + if (count & 2) { + dest[0] = source[frac>>FRACBITS]; frac += fracstep; + dest[4] = source[frac>>FRACBITS]; frac += fracstep; + dest += 8; + } + if (count & 4) { + dest[0] = source[frac>>FRACBITS]; frac += fracstep; + dest[4] = source[frac>>FRACBITS]; frac += fracstep; + dest[8] = source[frac>>FRACBITS]; frac += fracstep; + dest[12]= source[frac>>FRACBITS]; frac += fracstep; + dest += 16; + } + count >>= 3; + if (!count) return; + + do + { + dest[0] = source[frac>>FRACBITS]; frac += fracstep; + dest[4] = source[frac>>FRACBITS]; frac += fracstep; + dest[8] = source[frac>>FRACBITS]; frac += fracstep; + dest[12]= source[frac>>FRACBITS]; frac += fracstep; + dest[16]= source[frac>>FRACBITS]; frac += fracstep; + dest[20]= source[frac>>FRACBITS]; frac += fracstep; + dest[24]= source[frac>>FRACBITS]; frac += fracstep; + dest[28]= source[frac>>FRACBITS]; frac += fracstep; + dest += 32; + } while (--count); + } +} + +// [RH] Just fills a column with a given color +void R_FillColumnHorizP (void) +{ + int count = dc_count; + BYTE color = dc_color; + BYTE *dest; + + if (count <= 0) + return; + + { + int x = dc_x & 3; + unsigned int **span = &dc_ctspan[x]; + + (*span)[0] = dc_yl; + (*span)[1] = dc_yh; + *span += 2; + dest = &dc_temp[x + 4*dc_yl]; + } + + if (count & 1) { + *dest = color; + dest += 4; + } + if (!(count >>= 1)) + return; + do { + dest[0] = color; dest[4] = color; + dest += 8; + } while (--count); +} + +// Same as R_DrawMaskedColumn() except that it always uses R_DrawColumnHoriz(). + +void R_DrawMaskedColumnHoriz (const BYTE *column, const FTexture::Span *span) +{ + const fixed_t texturemid = FLOAT2FIXED(dc_texturemid); + while (span->Length != 0) + { + const int length = span->Length; + const int top = span->TopOffset; + + // calculate unclipped screen coordinates for post + dc_yl = xs_RoundToInt(sprtopscreen + spryscale * top); + dc_yh = xs_RoundToInt(sprtopscreen + spryscale * (top + length) - 1); + + if (sprflipvert) + { + swapvalues (dc_yl, dc_yh); + } + + if (dc_yh >= mfloorclip[dc_x]) + { + dc_yh = mfloorclip[dc_x] - 1; + } + if (dc_yl < mceilingclip[dc_x]) + { + dc_yl = mceilingclip[dc_x]; + } + + if (dc_yl <= dc_yh) + { + if (sprflipvert) + { + dc_texturefrac = (dc_yl*dc_iscale) - (top << FRACBITS) + - fixed_t(CenterY * dc_iscale) - texturemid; + const fixed_t maxfrac = length << FRACBITS; + while (dc_texturefrac >= maxfrac) + { + if (++dc_yl > dc_yh) + goto nextpost; + dc_texturefrac += dc_iscale; + } + fixed_t endfrac = dc_texturefrac + (dc_yh-dc_yl)*dc_iscale; + while (endfrac < 0) + { + if (--dc_yh < dc_yl) + goto nextpost; + endfrac -= dc_iscale; + } + } + else + { + dc_texturefrac = texturemid - (top << FRACBITS) + + (dc_yl*dc_iscale) - fixed_t((CenterY-1) * dc_iscale); + while (dc_texturefrac < 0) + { + if (++dc_yl > dc_yh) + goto nextpost; + dc_texturefrac += dc_iscale; + } + fixed_t endfrac = dc_texturefrac + (dc_yh-dc_yl)*dc_iscale; + const fixed_t maxfrac = length << FRACBITS; + if (dc_yh < mfloorclip[dc_x]-1 && endfrac < maxfrac - dc_iscale) + { + dc_yh++; + } + else while (endfrac >= maxfrac) + { + if (--dc_yh < dc_yl) + goto nextpost; + endfrac -= dc_iscale; + } + } + dc_source = column + top; + dc_dest = ylookup[dc_yl] + dc_x + dc_destorg; + dc_count = dc_yh - dc_yl + 1; + hcolfunc_pre (); + } +nextpost: + span++; + } + + if (sprflipvert) + { + unsigned int *front = horizspan[dc_x&3]; + unsigned int *back = dc_ctspan[dc_x&3] - 2; + + // Reorder the posts so that they get drawn top-to-bottom + // instead of bottom-to-top. + while (front < back) + { + swapvalues (front[0], back[0]); + swapvalues (front[1], back[1]); + front += 2; + back -= 2; + } + } +} diff --git a/src/r_local.h b/src/r_local.h new file mode 100644 index 000000000..7977e6923 --- /dev/null +++ b/src/r_local.h @@ -0,0 +1,41 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id:$ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This source is available for distribution and/or modification +// only under the terms of the DOOM Source Code License as +// published by id Software. All rights reserved. +// +// The source is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License +// for more details. +// +// DESCRIPTION: +// Refresh (R_*) module, global header. +// All the rendering/drawing stuff is here. +// +//----------------------------------------------------------------------------- + +#ifndef __R_LOCAL_H__ +#define __R_LOCAL_H__ + +// Binary Angles, sine/cosine/atan lookups. +#include "tables.h" + +// Screen size related parameters. +#include "doomdef.h" + +// Include the refresh/render data structs. + +// +// Separate header file for each module. +// +#include "r_main.h" +#include "r_things.h" +#include "r_draw.h" + +#endif // __R_LOCAL_H__ diff --git a/src/r_main.cpp b/src/r_main.cpp new file mode 100644 index 000000000..b87119b88 --- /dev/null +++ b/src/r_main.cpp @@ -0,0 +1,1112 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id:$ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This source is available for distribution and/or modification +// only under the terms of the DOOM Source Code License as +// published by id Software. All rights reserved. +// +// The source is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License +// for more details. +// +// $Log:$ +// +// DESCRIPTION: +// Rendering main loop and setup functions, +// utility functions (BSP, geometry, trigonometry). +// See tables.c, too. +// +//----------------------------------------------------------------------------- + +// HEADER FILES ------------------------------------------------------------ + +#include +#include + +#include "templates.h" +#include "doomdef.h" +#include "d_net.h" +#include "doomstat.h" +#include "m_random.h" +#include "m_bbox.h" +#include "r_local.h" +#include "r_plane.h" +#include "r_bsp.h" +#include "r_segs.h" +#include "r_3dfloors.h" +#include "r_sky.h" +#include "st_stuff.h" +#include "c_cvars.h" +#include "c_dispatch.h" +#include "v_video.h" +#include "stats.h" +#include "i_video.h" +#include "i_system.h" +#include "a_sharedglobal.h" +#include "r_data/r_translate.h" +#include "p_3dmidtex.h" +#include "r_data/r_interpolate.h" +#include "v_palette.h" +#include "po_man.h" +#include "p_effect.h" +#include "st_start.h" +#include "v_font.h" +#include "r_data/colormaps.h" +#include "farchive.h" + +// MACROS ------------------------------------------------------------------ + +#if 0 +#define TEST_X 32343794 +#define TEST_Y 111387517 +#define TEST_Z 2164524 +#define TEST_ANGLE 2468347904 +#endif + + +// TYPES ------------------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +void R_SpanInitData (); +void R_DeinitSprites(); + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +static void R_ShutdownRenderer(); + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +extern short *openings; +extern bool r_fakingunderwater; +extern "C" int fuzzviewheight; +extern subsector_t *InSubsector; +extern bool r_showviewer; + + +// PRIVATE DATA DECLARATIONS ----------------------------------------------- + +static double CurrentVisibility = 8.f; +static double MaxVisForWall; +static double MaxVisForFloor; +bool r_dontmaplines; + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +CVAR (String, r_viewsize, "", CVAR_NOSET) +CVAR (Bool, r_shadercolormaps, true, CVAR_ARCHIVE) + +double r_BaseVisibility; +double r_WallVisibility; +double r_FloorVisibility; +float r_TiltVisibility; +double r_SpriteVisibility; +double r_ParticleVisibility; + +double GlobVis; +fixed_t viewingrangerecip; +double FocalLengthX; +double FocalLengthY; +FDynamicColormap*basecolormap; // [RH] colormap currently drawing with +int fixedlightlev; +lighttable_t *fixedcolormap; +FSpecialColormap *realfixedcolormap; +double WallTMapScale2; + + +bool bRenderingToCanvas; // [RH] True if rendering to a special canvas +double globaluclip, globaldclip; +double CenterX, CenterY; +double YaspectMul; +double BaseYaspectMul; // yaspectmul without a forced aspect ratio +double IYaspectMul; +double InvZtoScale; + +// just for profiling purposes +int linecount; +int loopcount; + + +// +// precalculated math tables +// + +// The xtoviewangleangle[] table maps a screen pixel +// to the lowest viewangle that maps back to x ranges +// from clipangle to -clipangle. +angle_t xtoviewangle[MAXWIDTH+1]; + +bool foggy; // [RH] ignore extralight and fullbright? +int r_actualextralight; + +void (*colfunc) (void); +void (*basecolfunc) (void); +void (*fuzzcolfunc) (void); +void (*transcolfunc) (void); +void (*spanfunc) (void); + +void (*hcolfunc_pre) (void); +void (*hcolfunc_post1) (int hx, int sx, int yl, int yh); +void (*hcolfunc_post2) (int hx, int sx, int yl, int yh); +void (*hcolfunc_post4) (int sx, int yl, int yh); + +cycle_t WallCycles, PlaneCycles, MaskedCycles, WallScanCycles; + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +static int lastcenteryfrac; + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// viewangletox +// +// Used solely for construction the xtoviewangle table. +// +//========================================================================== + +static inline int viewangletox(int i) +{ + if (finetangent[i] > FRACUNIT*2) + { + return -1; + } + else if (finetangent[i] < -FRACUNIT*2) + { + return viewwidth+1; + } + else + { + double t = FIXED2DBL(finetangent[i]) * FocalLengthX; + return clamp(xs_CeilToInt(CenterX - t), -1, viewwidth+1); + } +} + +//========================================================================== +// +// R_InitTextureMapping +// +//========================================================================== + +void R_InitTextureMapping () +{ + int i, x; + + // Calc focallength so FieldOfView fineangles covers viewwidth. + FocalLengthX = CenterX / FocalTangent; + FocalLengthY = FocalLengthX * YaspectMul; + + // This is 1/FocalTangent before the widescreen extension of FOV. + viewingrangerecip = DivScale32(1, finetangent[FINEANGLES/4+(FieldOfView/2)]); + + // [RH] Do not generate viewangletox, because texture mapping is no + // longer done with trig, so it's not needed. + + // Now generate xtoviewangle for sky texture mapping. + // We do this with a hybrid approach: The center 90 degree span is + // constructed as per the original code: + // Scan xtoviewangle to find the smallest view angle that maps to x. + // (viewangletox is sorted in non-increasing order.) + // This reduces the chances of "doubling-up" of texture columns in + // the drawn sky texture. + // The remaining arcs are done with tantoangle instead. + + const int t1 = MAX(int(CenterX - FocalLengthX), 0); + const int t2 = MIN(int(CenterX + FocalLengthX), viewwidth); + const fixed_t dfocus = FLOAT2FIXED(FocalLengthX) >> DBITS; + + for (i = 0, x = t2; x >= t1; --x) + { + while (viewangletox(i) > x) + { + ++i; + } + xtoviewangle[x] = (i << ANGLETOFINESHIFT) - ANGLE_90; + } + for (x = t2 + 1; x <= viewwidth; ++x) + { + xtoviewangle[x] = ANGLE_270 + tantoangle[dfocus / (x - centerx)]; + } + for (x = 0; x < t1; ++x) + { + xtoviewangle[x] = (angle_t)(-(signed)xtoviewangle[viewwidth - x]); + } +} + +//========================================================================== +// +// R_SetVisibility +// +// Changes how rapidly things get dark with distance +// +//========================================================================== + +void R_SetVisibility(double vis) +{ + // Allow negative visibilities, just for novelty's sake + vis = clamp(vis, -204.7, 204.7); // (205 and larger do not work in 5:4 aspect ratio) + + CurrentVisibility = vis; + + if (FocalTangent == 0 || FocalLengthY == 0) + { // If r_visibility is called before the renderer is all set up, don't + // divide by zero. This will be called again later, and the proper + // values can be initialized then. + return; + } + + r_BaseVisibility = vis; + + // Prevent overflow on walls + if (r_BaseVisibility < 0 && r_BaseVisibility < -MaxVisForWall) + r_WallVisibility = -MaxVisForWall; + else if (r_BaseVisibility > 0 && r_BaseVisibility > MaxVisForWall) + r_WallVisibility = MaxVisForWall; + else + r_WallVisibility = r_BaseVisibility; + + r_WallVisibility = (InvZtoScale * SCREENWIDTH*BaseRatioSizes[WidescreenRatio][1] / + (viewwidth*SCREENHEIGHT*3)) * (r_WallVisibility * FocalTangent); + + // Prevent overflow on floors/ceilings. Note that the calculation of + // MaxVisForFloor means that planes less than two units from the player's + // view could still overflow, but there is no way to totally eliminate + // that while still using fixed point math. + if (r_BaseVisibility < 0 && r_BaseVisibility < -MaxVisForFloor) + r_FloorVisibility = -MaxVisForFloor; + else if (r_BaseVisibility > 0 && r_BaseVisibility > MaxVisForFloor) + r_FloorVisibility = MaxVisForFloor; + else + r_FloorVisibility = r_BaseVisibility; + + r_FloorVisibility = 160.0 * r_FloorVisibility / FocalLengthY; + + r_TiltVisibility = float(vis * FocalTangent * (16.f * 320.f) / viewwidth); + r_SpriteVisibility = r_WallVisibility; +} + +//========================================================================== +// +// R_GetVisibility +// +//========================================================================== + +double R_GetVisibility() +{ + return CurrentVisibility; +} + +//========================================================================== +// +// CCMD r_visibility +// +// Controls how quickly light ramps across a 1/z range. Set this, and it +// sets all the r_*Visibility variables (except r_SkyVisibilily, which is +// currently unused). +// +//========================================================================== + +CCMD (r_visibility) +{ + if (argv.argc() < 2) + { + Printf ("Visibility is %g\n", R_GetVisibility()); + } + else if (!netgame) + { + R_SetVisibility(atof(argv[1])); + } + else + { + Printf ("Visibility cannot be changed in net games.\n"); + } +} + +//========================================================================== +// +// R_SetWindow +// +//========================================================================== + +void R_SWRSetWindow(int windowSize, int fullWidth, int fullHeight, int stHeight, int trueratio) +{ + int virtheight, virtwidth, virtwidth2, virtheight2; + + if (!bRenderingToCanvas) + { // Set r_viewsize cvar to reflect the current view size + UCVarValue value; + char temp[16]; + + mysnprintf (temp, countof(temp), "%d x %d", viewwidth, viewheight); + value.String = temp; + r_viewsize.ForceSet (value, CVAR_String); + } + + fuzzviewheight = viewheight - 2; // Maximum row the fuzzer can draw to + halfviewwidth = (viewwidth >> 1) - 1; + + lastcenteryfrac = 1<<30; + CenterX = centerx; + CenterY = centery; + + virtwidth = virtwidth2 = fullWidth; + virtheight = virtheight2 = fullHeight; + + if (Is54Aspect(trueratio)) + { + virtheight2 = virtheight2 * BaseRatioSizes[trueratio][3] / 48; + } + else + { + virtwidth2 = virtwidth2 * BaseRatioSizes[trueratio][3] / 48; + } + + if (Is54Aspect(WidescreenRatio)) + { + virtheight = virtheight * BaseRatioSizes[WidescreenRatio][3] / 48; + } + else + { + virtwidth = virtwidth * BaseRatioSizes[WidescreenRatio][3] / 48; + } + + BaseYaspectMul = 320.0 * virtheight2 / (r_Yaspect * virtwidth2); + YaspectMul = 320.0 * virtheight / (r_Yaspect * virtwidth); + IYaspectMul = (double)virtwidth * r_Yaspect / 320.0 / virtheight; + InvZtoScale = YaspectMul * CenterX; + + WallTMapScale2 = IYaspectMul / CenterX; + + // psprite scales + pspritexscale = centerxwide / 160.0; + pspriteyscale = pspritexscale * YaspectMul; + pspritexiscale = 1 / pspritexscale; + + // thing clipping + clearbufshort (screenheightarray, viewwidth, (short)viewheight); + + R_InitTextureMapping (); + + MaxVisForWall = (InvZtoScale * (SCREENWIDTH*r_Yaspect) / + (viewwidth*SCREENHEIGHT * FocalTangent)); + MaxVisForWall = 32767.0 / MaxVisForWall; + MaxVisForFloor = 32767.0 / (viewheight * FocalLengthY / 160); + + // Reset r_*Visibility vars + R_SetVisibility(R_GetVisibility()); +} + +//========================================================================== +// +// CVAR r_columnmethod +// +// Selects which version of the seg renderers to use. +// +//========================================================================== + +CUSTOM_CVAR (Int, r_columnmethod, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self != 0 && self != 1) + { + self = 1; + } + else + { // Trigger the change + setsizeneeded = true; + } +} + +//========================================================================== +// +// R_Init +// +//========================================================================== + +void R_InitRenderer() +{ + atterm(R_ShutdownRenderer); + // viewwidth / viewheight are set by the defaults + clearbufshort (zeroarray, MAXWIDTH, 0); + + R_InitPlanes (); + R_InitShadeMaps(); + R_InitColumnDrawers (); + + colfunc = basecolfunc = R_DrawColumn; + fuzzcolfunc = R_DrawFuzzColumn; + transcolfunc = R_DrawTranslatedColumn; + spanfunc = R_DrawSpan; + + // [RH] Horizontal column drawers + hcolfunc_pre = R_DrawColumnHoriz; + hcolfunc_post1 = rt_map1col; + hcolfunc_post4 = rt_map4cols; +} + +//========================================================================== +// +// R_ShutdownRenderer +// +//========================================================================== + +static void R_ShutdownRenderer() +{ + R_DeinitSprites(); + R_DeinitPlanes(); + // Free openings + if (openings != NULL) + { + M_Free (openings); + openings = NULL; + } + + // Free drawsegs + if (drawsegs != NULL) + { + M_Free (drawsegs); + drawsegs = NULL; + } +} + +//========================================================================== +// +// R_CopyStackedViewParameters +// +//========================================================================== + +void R_CopyStackedViewParameters() +{ + stacked_viewpos = ViewPos; + stacked_angle = ViewAngle; + stacked_extralight = extralight; + stacked_visibility = R_GetVisibility(); +} + +//========================================================================== +// +// R_SetupColormap +// +// Sets up special fixed colormaps +// +//========================================================================== + +void R_SetupColormap(player_t *player) +{ + realfixedcolormap = NULL; + fixedcolormap = NULL; + fixedlightlev = -1; + + if (player != NULL && camera == player->mo) + { + if (player->fixedcolormap >= 0 && player->fixedcolormap < (int)SpecialColormaps.Size()) + { + realfixedcolormap = &SpecialColormaps[player->fixedcolormap]; + if (RenderTarget == screen && (DFrameBuffer *)screen->Accel2D && r_shadercolormaps) + { + // Render everything fullbright. The copy to video memory will + // apply the special colormap, so it won't be restricted to the + // palette. + fixedcolormap = realcolormaps; + } + else + { + fixedcolormap = SpecialColormaps[player->fixedcolormap].Colormap; + } + } + else if (player->fixedlightlevel >= 0 && player->fixedlightlevel < NUMCOLORMAPS) + { + fixedlightlev = player->fixedlightlevel * 256; + } + } + // [RH] Inverse light for shooting the Sigil + if (fixedcolormap == NULL && extralight == INT_MIN) + { + fixedcolormap = SpecialColormaps[INVERSECOLORMAP].Colormap; + extralight = 0; + } +} + +//========================================================================== +// +// R_SetupFreelook +// +// [RH] freelook stuff +// +//========================================================================== + +void R_SetupFreelook() +{ + { + double dy; + + if (camera != NULL) + { + dy = FocalLengthY * (-ViewPitch).Tan(); + } + else + { + dy = 0; + } + + CenterY = (viewheight / 2.0) + dy; + centery = xs_ToInt(CenterY); + globaluclip = -CenterY / InvZtoScale; + globaldclip = (viewheight - CenterY) / InvZtoScale; + + //centeryfrac &= 0xffff0000; + int e, i; + + i = 0; + e = viewheight; + float focus = float(FocalLengthY); + float den; + float cy = float(CenterY); + if (i < centery) + { + den = cy - i - 0.5f; + if (e <= centery) + { + do { + yslope[i] = FLOAT2FIXED(focus / den); + den -= 1; + } while (++i < e); + } + else + { + do { + yslope[i] = FLOAT2FIXED(focus / den); + den -= 1; + } while (++i < centery); + den = i - cy + 0.5f; + do { + yslope[i] = FLOAT2FIXED(focus / den); + den += 1; + } while (++i < e); + } + } + else + { + den = i - cy + 0.5f; + do { + yslope[i] = FLOAT2FIXED(focus / den); + den += 1; + } while (++i < e); + } + } +} + +//========================================================================== +// +// R_EnterPortal +// +// [RH] Draw the reflection inside a mirror +// [ZZ] Merged with portal code, originally called R_EnterMirror +// +//========================================================================== + +CVAR(Int, r_portal_recursions, 4, CVAR_ARCHIVE) +CVAR(Bool, r_highlight_portals, false, CVAR_ARCHIVE) + +void R_HighlightPortal (PortalDrawseg* pds) +{ + // [ZZ] NO OVERFLOW CHECKS HERE + // I believe it won't break. if it does, blame me. :( + + BYTE color = (BYTE)BestColor((DWORD *)GPalette.BaseColors, 255, 0, 0, 0, 255); + + BYTE* pixels = RenderTarget->GetBuffer(); + // top edge + for (int x = pds->x1; x < pds->x2; x++) + { + if (x < 0 || x >= RenderTarget->GetWidth()) + continue; + + int p = x - pds->x1; + int Ytop = pds->ceilingclip[p]; + int Ybottom = pds->floorclip[p]; + + if (x == pds->x1 || x == pds->x2-1) + { + RenderTarget->DrawLine(x, Ytop, x, Ybottom+1, color, 0); + continue; + } + + int YtopPrev = pds->ceilingclip[p-1]; + int YbottomPrev = pds->floorclip[p-1]; + + if (abs(Ytop-YtopPrev) > 1) + RenderTarget->DrawLine(x, YtopPrev, x, Ytop, color, 0); + else *(pixels + Ytop * RenderTarget->GetPitch() + x) = color; + + if (abs(Ybottom-YbottomPrev) > 1) + RenderTarget->DrawLine(x, YbottomPrev, x, Ybottom, color, 0); + else *(pixels + Ybottom * RenderTarget->GetPitch() + x) = color; + } +} + +void R_EnterPortal (PortalDrawseg* pds, int depth) +{ + // [ZZ] check depth. fill portal with black if it's exceeding the visual recursion limit, and continue like nothing happened. + if (depth >= r_portal_recursions) + { + BYTE color = (BYTE)BestColor((DWORD *)GPalette.BaseColors, 0, 0, 0, 0, 255); + int spacing = RenderTarget->GetPitch(); + for (int x = pds->x1; x < pds->x2; x++) + { + if (x < 0 || x >= RenderTarget->GetWidth()) + continue; + + int Ytop = pds->ceilingclip[x-pds->x1]; + int Ybottom = pds->floorclip[x-pds->x1]; + + BYTE *dest = RenderTarget->GetBuffer() + x + Ytop * spacing; + + for (int y = Ytop; y <= Ybottom; y++) + { + *dest = color; + dest += spacing; + } + } + + if (r_highlight_portals) + R_HighlightPortal(pds); + + return; + } + + DAngle startang = ViewAngle; + DVector3 startpos = ViewPos; + DVector3 savedpath[2] = { ViewPath[0], ViewPath[1] }; + ActorRenderFlags savedvisibility = camera? camera->renderflags & RF_INVISIBLE : ActorRenderFlags::FromInt(0); + + CurrentPortalUniq++; + + unsigned int portalsAtStart = WallPortals.Size (); + + if (pds->mirror) + { + //vertex_t *v1 = ds->curline->v1; + vertex_t *v1 = pds->src->v1; + + // Reflect the current view behind the mirror. + if (pds->src->Delta().X == 0) + { // vertical mirror + ViewPos.X = v1->fX() - startpos.X + v1->fX(); + } + else if (pds->src->Delta().Y == 0) + { // horizontal mirror + ViewPos.Y = v1->fY() - startpos.Y + v1->fY(); + } + else + { // any mirror + vertex_t *v2 = pds->src->v2; + + double dx = v2->fX() - v1->fX(); + double dy = v2->fY() - v1->fY(); + double x1 = v1->fX(); + double y1 = v1->fY(); + double x = startpos.X; + double y = startpos.Y; + + // the above two cases catch len == 0 + double r = ((x - x1)*dx + (y - y1)*dy) / (dx*dx + dy*dy); + + ViewPos.X = (x1 + r * dx)*2 - x; + ViewPos.Y = (y1 + r * dy)*2 - y; + } + ViewAngle = pds->src->Delta().Angle() - startang; + } + else + { + P_TranslatePortalXY(pds->src, ViewPos.X, ViewPos.Y); + P_TranslatePortalZ(pds->src, ViewPos.Z); + P_TranslatePortalAngle(pds->src, ViewAngle); + P_TranslatePortalXY(pds->src, ViewPath[0].X, ViewPath[0].Y); + P_TranslatePortalXY(pds->src, ViewPath[1].X, ViewPath[1].Y); + + if (!r_showviewer && camera) + { + double distp = (ViewPath[0] - ViewPath[1]).Length(); + if (distp > EQUAL_EPSILON) + { + double dist1 = (ViewPos - ViewPath[0]).Length(); + double dist2 = (ViewPos - ViewPath[1]).Length(); + + if (dist1 + dist2 < distp + 1) + { + camera->renderflags |= RF_INVISIBLE; + } + } + } + } + + ViewSin = ViewAngle.Sin(); + ViewCos = ViewAngle.Cos(); + + ViewTanSin = FocalTangent * ViewSin; + ViewTanCos = FocalTangent * ViewCos; + + R_CopyStackedViewParameters(); + + validcount++; + PortalDrawseg* prevpds = CurrentPortal; + CurrentPortal = pds; + + R_ClearPlanes (false); + R_ClearClipSegs (pds->x1, pds->x2); + + WindowLeft = pds->x1; + WindowRight = pds->x2; + + // RF_XFLIP should be removed before calling the root function + int prevmf = MirrorFlags; + if (pds->mirror) + { + if (MirrorFlags & RF_XFLIP) + MirrorFlags &= ~RF_XFLIP; + else MirrorFlags |= RF_XFLIP; + } + + // some portals have height differences, account for this here + R_3D_EnterSkybox(); // push 3D floor height map + CurrentPortalInSkybox = false; // first portal in a skybox should set this variable to false for proper clipping in skyboxes. + + // first pass, set clipping + memcpy (ceilingclip + pds->x1, &pds->ceilingclip[0], pds->len*sizeof(*ceilingclip)); + memcpy (floorclip + pds->x1, &pds->floorclip[0], pds->len*sizeof(*floorclip)); + + InSubsector = NULL; + R_RenderBSPNode (nodes + numnodes - 1); + R_3D_ResetClip(); // reset clips (floor/ceiling) + if (!savedvisibility && camera) camera->renderflags &= ~RF_INVISIBLE; + + PlaneCycles.Clock(); + R_DrawPlanes (); + R_DrawPortals (); + PlaneCycles.Unclock(); + + double vzp = ViewPos.Z; + + int prevuniq = CurrentPortalUniq; + // depth check is in another place right now + unsigned int portalsAtEnd = WallPortals.Size (); + for (; portalsAtStart < portalsAtEnd; portalsAtStart++) + { + R_EnterPortal (&WallPortals[portalsAtStart], depth + 1); + } + int prevuniq2 = CurrentPortalUniq; + CurrentPortalUniq = prevuniq; + + NetUpdate(); + + MaskedCycles.Clock(); // [ZZ] count sprites in portals/mirrors along with normal ones. + R_DrawMasked (); // this is required since with portals there often will be cases when more than 80% of the view is inside a portal. + MaskedCycles.Unclock(); + + NetUpdate(); + + R_3D_LeaveSkybox(); // pop 3D floor height map + CurrentPortalUniq = prevuniq2; + + // draw a red line around a portal if it's being highlighted + if (r_highlight_portals) + R_HighlightPortal(pds); + + CurrentPortal = prevpds; + MirrorFlags = prevmf; + ViewAngle = startang; + ViewPos = startpos; + ViewPath[0] = savedpath[0]; + ViewPath[1] = savedpath[1]; +} + +//========================================================================== +// +// R_SetupBuffer +// +// Precalculate all row offsets and fuzz table. +// +//========================================================================== + +void R_SetupBuffer () +{ + static BYTE *lastbuff = NULL; + + int pitch = RenderTarget->GetPitch(); + BYTE *lineptr = RenderTarget->GetBuffer() + viewwindowy*pitch + viewwindowx; + + if (dc_pitch != pitch || lineptr != lastbuff) + { + if (dc_pitch != pitch) + { + dc_pitch = pitch; + R_InitFuzzTable (pitch); +#if defined(X86_ASM) || defined(X64_ASM) + ASM_PatchPitch (); +#endif + } + dc_destorg = lineptr; + for (int i = 0; i < RenderTarget->GetHeight(); i++) + { + ylookup[i] = i * pitch; + } + } +} + +//========================================================================== +// +// R_RenderActorView +// +//========================================================================== + +void R_RenderActorView (AActor *actor, bool dontmaplines) +{ + WallCycles.Reset(); + PlaneCycles.Reset(); + MaskedCycles.Reset(); + WallScanCycles.Reset(); + + fakeActive = 0; // kg3D - reset fake floor indicator + R_3D_ResetClip(); // reset clips (floor/ceiling) + + R_SetupBuffer (); + R_SetupFrame (actor); + + // Clear buffers. + R_ClearClipSegs (0, viewwidth); + R_ClearDrawSegs (); + R_ClearPlanes (true); + R_ClearSprites (); + + NetUpdate (); + + // [RH] Show off segs if r_drawflat is 1 + if (r_drawflat) + { + hcolfunc_pre = R_FillColumnHorizP; + hcolfunc_post1 = rt_copy1col; + hcolfunc_post4 = rt_copy4cols; + colfunc = R_FillColumnP; + spanfunc = R_FillSpan; + } + else + { + hcolfunc_pre = R_DrawColumnHoriz; + hcolfunc_post1 = rt_map1col; + hcolfunc_post4 = rt_map4cols; + colfunc = basecolfunc; + spanfunc = R_DrawSpan; + } + + WindowLeft = 0; + WindowRight = viewwidth; + MirrorFlags = 0; + CurrentPortal = NULL; + CurrentPortalUniq = 0; + + r_dontmaplines = dontmaplines; + + // [RH] Hack to make windows into underwater areas possible + r_fakingunderwater = false; + + // [RH] Setup particles for this frame + P_FindParticleSubsectors (); + + WallCycles.Clock(); + ActorRenderFlags savedflags = camera->renderflags; + // Never draw the player unless in chasecam mode + if (!r_showviewer) + { + camera->renderflags |= RF_INVISIBLE; + } + // Link the polyobjects right before drawing the scene to reduce the amounts of calls to this function + PO_LinkToSubsectors(); + InSubsector = NULL; + R_RenderBSPNode (nodes + numnodes - 1); // The head node is the last node output. + R_3D_ResetClip(); // reset clips (floor/ceiling) + camera->renderflags = savedflags; + WallCycles.Unclock(); + + NetUpdate (); + + if (viewactive) + { + PlaneCycles.Clock(); + R_DrawPlanes (); + R_DrawPortals (); + PlaneCycles.Unclock(); + + // [RH] Walk through mirrors + // [ZZ] Merged with portals + size_t lastportal = WallPortals.Size(); + for (unsigned int i = 0; i < lastportal; i++) + { + R_EnterPortal(&WallPortals[i], 0); + } + + CurrentPortal = NULL; + CurrentPortalUniq = 0; + + NetUpdate (); + + MaskedCycles.Clock(); + R_DrawMasked (); + MaskedCycles.Unclock(); + + NetUpdate (); + } + WallPortals.Clear (); + interpolator.RestoreInterpolations (); + R_SetupBuffer (); + + // If we don't want shadered colormaps, NULL it now so that the + // copy to the screen does not use a special colormap shader. + if (!r_shadercolormaps) + { + realfixedcolormap = NULL; + } +} + +//========================================================================== +// +// R_RenderViewToCanvas +// +// Pre: Canvas is already locked. +// +//========================================================================== + +void R_RenderViewToCanvas (AActor *actor, DCanvas *canvas, + int x, int y, int width, int height, bool dontmaplines) +{ + const bool savedviewactive = viewactive; + + viewwidth = width; + RenderTarget = canvas; + bRenderingToCanvas = true; + + R_SetWindow (12, width, height, height); + viewwindowx = x; + viewwindowy = y; + viewactive = true; + + R_RenderActorView (actor, dontmaplines); + + RenderTarget = screen; + bRenderingToCanvas = false; + R_ExecuteSetViewSize (); + screen->Lock (true); + R_SetupBuffer (); + screen->Unlock (); + viewactive = savedviewactive; +} + +//========================================================================== +// +// R_MultiresInit +// +// Called from V_SetResolution() +// +//========================================================================== + +void R_MultiresInit () +{ + R_PlaneInitData (); +} + + +//========================================================================== +// +// STAT fps +// +// Displays statistics about rendering times +// +//========================================================================== +extern cycle_t WallCycles, PlaneCycles, MaskedCycles, WallScanCycles; +extern cycle_t FrameCycles; + +ADD_STAT (fps) +{ + FString out; + out.Format("frame=%04.1f ms walls=%04.1f ms planes=%04.1f ms masked=%04.1f ms", + FrameCycles.TimeMS(), WallCycles.TimeMS(), PlaneCycles.TimeMS(), MaskedCycles.TimeMS()); + return out; +} + + +static double f_acc, w_acc,p_acc,m_acc; +static int acc_c; + +ADD_STAT (fps_accumulated) +{ + f_acc += FrameCycles.TimeMS(); + w_acc += WallCycles.TimeMS(); + p_acc += PlaneCycles.TimeMS(); + m_acc += MaskedCycles.TimeMS(); + acc_c++; + FString out; + out.Format("frame=%04.1f ms walls=%04.1f ms planes=%04.1f ms masked=%04.1f ms %d counts", + f_acc/acc_c, w_acc/acc_c, p_acc/acc_c, m_acc/acc_c, acc_c); + Printf(PRINT_LOG, "%s\n", out.GetChars()); + return out; +} + +//========================================================================== +// +// STAT wallcycles +// +// Displays the minimum number of cycles spent drawing walls +// +//========================================================================== + +static double bestwallcycles = HUGE_VAL; + +ADD_STAT (wallcycles) +{ + FString out; + double cycles = WallCycles.Time(); + if (cycles && cycles < bestwallcycles) + bestwallcycles = cycles; + out.Format ("%g", bestwallcycles); + return out; +} + +//========================================================================== +// +// CCMD clearwallcycles +// +// Resets the count of minimum wall drawing cycles +// +//========================================================================== + +CCMD (clearwallcycles) +{ + bestwallcycles = HUGE_VAL; +} + +#if 1 +// To use these, also uncomment the clock/unclock in wallscan +static double bestscancycles = HUGE_VAL; + +ADD_STAT (scancycles) +{ + FString out; + double scancycles = WallScanCycles.Time(); + if (scancycles && scancycles < bestscancycles) + bestscancycles = scancycles; + out.Format ("%g", bestscancycles); + return out; +} + +CCMD (clearscancycles) +{ + bestscancycles = HUGE_VAL; +} +#endif diff --git a/src/r_main.h b/src/r_main.h new file mode 100644 index 000000000..24103393d --- /dev/null +++ b/src/r_main.h @@ -0,0 +1,146 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id:$ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This source is available for distribution and/or modification +// only under the terms of the DOOM Source Code License as +// published by id Software. All rights reserved. +// +// The source is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License +// for more details. +// +// DESCRIPTION: +// System specific interface stuff. +// +//----------------------------------------------------------------------------- + + +#ifndef __R_MAIN_H__ +#define __R_MAIN_H__ + +#include "r_utility.h" +#include "d_player.h" +#include "v_palette.h" +#include "r_data/colormaps.h" + + +typedef BYTE lighttable_t; // This could be wider for >8 bit display. + +// +// POV related. +// +extern bool bRenderingToCanvas; +extern double ViewCos; +extern double ViewSin; +extern fixed_t viewingrangerecip; +extern double FocalLengthX, FocalLengthY; +extern double InvZtoScale; + +extern double WallTMapScale2; + +extern int viewwindowx; +extern int viewwindowy; + +extern double CenterX; +extern double CenterY; +extern double YaspectMul; +extern double IYaspectMul; + +extern FDynamicColormap*basecolormap; // [RH] Colormap for sector currently being drawn + +extern int linecount; +extern int loopcount; + +extern bool r_dontmaplines; + +// +// Lighting. +// +// [RH] This has changed significantly from Doom, which used lookup +// tables based on 1/z for walls and z for flats and only recognized +// 16 discrete light levels. The terminology I use is borrowed from Build. +// + +// The size of a single colormap, in bits +#define COLORMAPSHIFT 8 + +// Convert a light level into an unbounded colormap index (shade). Result is +// fixed point. Why the +12? I wish I knew, but experimentation indicates it +// is necessary in order to best reproduce Doom's original lighting. +#define LIGHT2SHADE(l) ((NUMCOLORMAPS*2*FRACUNIT)-(((l)+12)*(FRACUNIT*NUMCOLORMAPS/128))) + +// MAXLIGHTSCALE from original DOOM, divided by 2. +#define MAXLIGHTVIS (24.0) + +// Convert a shade and visibility to a clamped colormap index. +// Result is not fixed point. +// Change R_CalcTiltedLighting() when this changes. +#define GETPALOOKUP(vis,shade) (clamp (((shade)-FLOAT2FIXED(MIN(MAXLIGHTVIS,double(vis))))>>FRACBITS, 0, NUMCOLORMAPS-1)) + +extern double GlobVis; + +void R_SetVisibility(double visibility); +double R_GetVisibility(); + +extern double r_BaseVisibility; +extern double r_WallVisibility; +extern double r_FloorVisibility; +extern float r_TiltVisibility; +extern double r_SpriteVisibility; + +extern int r_actualextralight; +extern bool foggy; +extern int fixedlightlev; +extern lighttable_t* fixedcolormap; +extern FSpecialColormap*realfixedcolormap; + + +// +// Function pointers to switch refresh/drawing functions. +// Used to select shadow mode etc. +// +extern void (*colfunc) (void); +extern void (*basecolfunc) (void); +extern void (*fuzzcolfunc) (void); +extern void (*transcolfunc) (void); +// No shadow effects on floors. +extern void (*spanfunc) (void); + +// [RH] Function pointers for the horizontal column drawers. +extern void (*hcolfunc_pre) (void); +extern void (*hcolfunc_post1) (int hx, int sx, int yl, int yh); +extern void (*hcolfunc_post2) (int hx, int sx, int yl, int yh); +extern void (*hcolfunc_post4) (int sx, int yl, int yh); + + +void R_InitTextureMapping (); + + +// +// REFRESH - the actual rendering functions. +// + +// Called by G_Drawer. +void R_RenderActorView (AActor *actor, bool dontmaplines = false); +void R_SetupBuffer (); + +void R_RenderViewToCanvas (AActor *actor, DCanvas *canvas, int x, int y, int width, int height, bool dontmaplines = false); + +// [RH] Initialize multires stuff for renderer +void R_MultiresInit (void); + + +extern int stacked_extralight; +extern double stacked_visibility; +extern DVector3 stacked_viewpos; +extern DAngle stacked_angle; + +extern void R_CopyStackedViewParameters(); + + +#endif // __R_MAIN_H__ diff --git a/src/r_plane.cpp b/src/r_plane.cpp index 62730e731..790bb9309 100644 --- a/src/r_plane.cpp +++ b/src/r_plane.cpp @@ -58,21 +58,13 @@ #include "r_3dfloors.h" #include "v_palette.h" #include "r_data/colormaps.h" -#include "g_levellocals.h" -#include "events.h" #ifdef _MSC_VER #pragma warning(disable:4244) #endif -CVAR(Bool, tilt, false, 0); -CVAR(Bool, r_skyboxes, true, 0) - -EXTERN_CVAR(Int, r_skymode) - -namespace swrenderer -{ - using namespace drawerargs; +//EXTERN_CVAR (Int, tx) +//EXTERN_CVAR (Int, ty) extern subsector_t *InSubsector; @@ -138,21 +130,30 @@ extern "C" { // spanend holds the end of a plane span in each screen row // short spanend[MAXHEIGHT]; +BYTE *tiltlighting[MAXWIDTH]; int planeshade; FVector3 plane_sz, plane_su, plane_sv; float planelightfloat; bool plane_shade; fixed_t pviewx, pviewy; + +void R_DrawTiltedPlane_ASM (int y, int x1); } -float yslope[MAXHEIGHT]; +fixed_t yslope[MAXHEIGHT]; static fixed_t xscale, yscale; -static double xstepscale, ystepscale; -static double basexfrac, baseyfrac; +static DWORD xstepscale, ystepscale; +static DWORD basexfrac, baseyfrac; +#ifdef X86_ASM +extern "C" void R_SetSpanSource_ASM (const BYTE *flat); +extern "C" void R_SetSpanSize_ASM (int xbits, int ybits); +extern "C" void R_SetSpanColormap_ASM (BYTE *colormap); +extern "C" void R_SetTiltedSpanSource_ASM (const BYTE *flat); +extern "C" BYTE *ds_curcolormap, *ds_cursource, *ds_curtiltedsource; +#endif void R_DrawSinglePlane (visplane_t *, fixed_t alpha, bool additive, bool masked); -void R_DrawSkySegment(visplane_t *vis, short *uwal, short *dwal, float *swal, fixed_t *lwal, double yrepeat, const uint8_t *(*getcol)(FTexture *tex, int col)); //========================================================================== // @@ -204,7 +205,7 @@ void R_DeinitPlanes () void R_MapPlane (int y, int x1) { int x2 = spanend[y]; - double distance; + fixed_t distance; #ifdef RANGECHECK if (x2 < x1 || x1<0 || x2>=viewwidth || (unsigned)y>=(unsigned)viewheight) @@ -216,35 +217,25 @@ void R_MapPlane (int y, int x1) // [RH] Notice that I dumped the caching scheme used by Doom. // It did not offer any appreciable speedup. - distance = planeheight * yslope[y]; + distance = xs_ToInt(planeheight * yslope[y]); - if (ds_xbits != 0) - { - ds_xstep = xs_ToFixed(32 - ds_xbits, distance * xstepscale); - ds_xfrac = xs_ToFixed(32 - ds_xbits, distance * basexfrac) + pviewx; - } - else - { - ds_xstep = 0; - ds_xfrac = 0; - } - if (ds_ybits != 0) - { - ds_ystep = xs_ToFixed(32 - ds_ybits, distance * ystepscale); - ds_yfrac = xs_ToFixed(32 - ds_ybits, distance * baseyfrac) + pviewy; - } - else - { - ds_ystep = 0; - ds_yfrac = 0; - } + ds_xstep = FixedMul (distance, xstepscale); + ds_ystep = FixedMul (distance, ystepscale); + ds_xfrac = FixedMul (distance, basexfrac) + pviewx; + ds_yfrac = FixedMul (distance, baseyfrac) + pviewy; if (plane_shade) { // Determine lighting based on the span's distance from the viewer. - R_SetDSColorMapLight(basecolormap, GlobVis * fabs(CenterY - y), planeshade); + ds_colormap = basecolormap->Maps + (GETPALOOKUP ( + GlobVis * fabs(CenterY - y), planeshade) << COLORMAPSHIFT); } +#ifdef X86_ASM + if (ds_colormap != ds_curcolormap) + R_SetSpanColormap_ASM (ds_colormap); +#endif + ds_y = y; ds_x1 = x1; ds_x2 = x2; @@ -252,6 +243,112 @@ void R_MapPlane (int y, int x1) spanfunc (); } +//========================================================================== +// +// R_CalcTiltedLighting +// +// Calculates the lighting for one row of a tilted plane. If the definition +// of GETPALOOKUP changes, this needs to change, too. +// +//========================================================================== + +extern "C" { +void R_CalcTiltedLighting (double lval, double lend, int width) +{ + double lstep; + BYTE *lightfiller; + BYTE *basecolormapdata = basecolormap->Maps; + int i = 0; + + if (width == 0 || lval == lend) + { // Constant lighting + lightfiller = basecolormapdata + (GETPALOOKUP(lval, planeshade) << COLORMAPSHIFT); + } + else + { + lstep = (lend - lval) / width; + if (lval >= MAXLIGHTVIS) + { // lval starts "too bright". + lightfiller = basecolormapdata + (GETPALOOKUP(lval, planeshade) << COLORMAPSHIFT); + for (; i <= width && lval >= MAXLIGHTVIS; ++i) + { + tiltlighting[i] = lightfiller; + lval += lstep; + } + } + if (lend >= MAXLIGHTVIS) + { // lend ends "too bright". + lightfiller = basecolormapdata + (GETPALOOKUP(lend, planeshade) << COLORMAPSHIFT); + for (; width > i && lend >= MAXLIGHTVIS; --width) + { + tiltlighting[width] = lightfiller; + lend -= lstep; + } + } + if (width > 0) + { + lval = planeshade - lval; + lend = planeshade - lend; + lstep = (lend - lval) / width; + if (lstep < 0) + { // Going from dark to light + if (lval < FRACUNIT) + { // All bright + lightfiller = basecolormapdata; + } + else + { + if (lval >= NUMCOLORMAPS*FRACUNIT) + { // Starts beyond the dark end + BYTE *clight = basecolormapdata + ((NUMCOLORMAPS-1) << COLORMAPSHIFT); + while (lval >= NUMCOLORMAPS*FRACUNIT && i <= width) + { + tiltlighting[i++] = clight; + lval += lstep; + } + if (i > width) + return; + } + while (i <= width && lval >= 0) + { + tiltlighting[i++] = basecolormapdata + (xs_ToInt(lval) << COLORMAPSHIFT); + lval += lstep; + } + lightfiller = basecolormapdata; + } + } + else + { // Going from light to dark + if (lval >= (NUMCOLORMAPS-1)*FRACUNIT) + { // All dark + lightfiller = basecolormapdata + ((NUMCOLORMAPS-1) << COLORMAPSHIFT); + } + else + { + while (lval < 0 && i <= width) + { + tiltlighting[i++] = basecolormapdata; + lval += lstep; + } + if (i > width) + return; + while (i <= width && lval < (NUMCOLORMAPS-1)*FRACUNIT) + { + tiltlighting[i++] = basecolormapdata + (xs_ToInt(lval) << COLORMAPSHIFT); + lval += lstep; + } + lightfiller = basecolormapdata + ((NUMCOLORMAPS-1) << COLORMAPSHIFT); + } + } + } + } + for (; i <= width; i++) + { + tiltlighting[i] = lightfiller; + } +} +} // extern "C" + //========================================================================== // // R_MapTiltedPlane @@ -260,7 +357,125 @@ void R_MapPlane (int y, int x1) void R_MapTiltedPlane (int y, int x1) { - R_DrawTiltedSpan(y, x1, spanend[y], plane_sz, plane_su, plane_sv, plane_shade, planeshade, planelightfloat, pviewx, pviewy); + int x2 = spanend[y]; + int width = x2 - x1; + double iz, uz, vz; + BYTE *fb; + DWORD u, v; + int i; + + iz = plane_sz[2] + plane_sz[1]*(centery-y) + plane_sz[0]*(x1-centerx); + + // Lighting is simple. It's just linear interpolation from start to end + if (plane_shade) + { + uz = (iz + plane_sz[0]*width) * planelightfloat; + vz = iz * planelightfloat; + R_CalcTiltedLighting (vz, uz, width); + } + + uz = plane_su[2] + plane_su[1]*(centery-y) + plane_su[0]*(x1-centerx); + vz = plane_sv[2] + plane_sv[1]*(centery-y) + plane_sv[0]*(x1-centerx); + + fb = ylookup[y] + x1 + dc_destorg; + + BYTE vshift = 32 - ds_ybits; + BYTE ushift = vshift - ds_xbits; + int umask = ((1 << ds_xbits) - 1) << ds_ybits; + +#if 0 // The "perfect" reference version of this routine. Pretty slow. + // Use it only to see how things are supposed to look. + i = 0; + do + { + double z = 1.f/iz; + + u = SQWORD(uz*z) + pviewx; + v = SQWORD(vz*z) + pviewy; + ds_colormap = tiltlighting[i]; + fb[i++] = ds_colormap[ds_source[(v >> vshift) | ((u >> ushift) & umask)]]; + iz += plane_sz[0]; + uz += plane_su[0]; + vz += plane_sv[0]; + } while (--width >= 0); +#else +//#define SPANSIZE 32 +//#define INVSPAN 0.03125f +//#define SPANSIZE 8 +//#define INVSPAN 0.125f +#define SPANSIZE 16 +#define INVSPAN 0.0625f + + double startz = 1.f/iz; + double startu = uz*startz; + double startv = vz*startz; + double izstep, uzstep, vzstep; + + izstep = plane_sz[0] * SPANSIZE; + uzstep = plane_su[0] * SPANSIZE; + vzstep = plane_sv[0] * SPANSIZE; + x1 = 0; + width++; + + while (width >= SPANSIZE) + { + iz += izstep; + uz += uzstep; + vz += vzstep; + + double endz = 1.f/iz; + double endu = uz*endz; + double endv = vz*endz; + DWORD stepu = SQWORD((endu - startu) * INVSPAN); + DWORD stepv = SQWORD((endv - startv) * INVSPAN); + u = SQWORD(startu) + pviewx; + v = SQWORD(startv) + pviewy; + + for (i = SPANSIZE-1; i >= 0; i--) + { + fb[x1] = *(tiltlighting[x1] + ds_source[(v >> vshift) | ((u >> ushift) & umask)]); + x1++; + u += stepu; + v += stepv; + } + startu = endu; + startv = endv; + width -= SPANSIZE; + } + if (width > 0) + { + if (width == 1) + { + u = SQWORD(startu); + v = SQWORD(startv); + fb[x1] = *(tiltlighting[x1] + ds_source[(v >> vshift) | ((u >> ushift) & umask)]); + } + else + { + double left = width; + iz += plane_sz[0] * left; + uz += plane_su[0] * left; + vz += plane_sv[0] * left; + + double endz = 1.f/iz; + double endu = uz*endz; + double endv = vz*endz; + left = 1.f/left; + DWORD stepu = SQWORD((endu - startu) * left); + DWORD stepv = SQWORD((endv - startv) * left); + u = SQWORD(startu) + pviewx; + v = SQWORD(startv) + pviewy; + + for (; width != 0; width--) + { + fb[x1] = *(tiltlighting[x1] + ds_source[(v >> vshift) | ((u >> ushift) & umask)]); + x1++; + u += stepu; + v += stepv; + } + } + } +#endif } //========================================================================== @@ -269,9 +484,9 @@ void R_MapTiltedPlane (int y, int x1) // //========================================================================== -void R_MapColoredPlane(int y, int x1) +void R_MapColoredPlane (int y, int x1) { - R_DrawColoredSpan(y, x1, spanend[y]); + memset (ylookup[y] + x1 + dc_destorg, ds_color, spanend[y] - x1 + 1); } //========================================================================== @@ -319,9 +534,9 @@ void R_ClearPlanes (bool fullclear) } // opening / clipping determination - fillshort (floorclip, viewwidth, viewheight); + clearbufshort (floorclip, viewwidth, viewheight); // [RH] clip ceiling to console bottom - fillshort (ceilingclip, viewwidth, + clearbufshort (ceilingclip, viewwidth, !screen->Accel2D && ConBottom > viewwindowy && !bRenderingToCanvas ? (ConBottom - viewwindowy) : 0); @@ -367,26 +582,29 @@ static visplane_t *new_visplane (unsigned hash) //========================================================================== visplane_t *R_FindPlane (const secplane_t &height, FTextureID picnum, int lightlevel, double Alpha, bool additive, - const FTransform &xxform, + const FTransform &xform, int sky, FSectorPortal *portal) { secplane_t plane; visplane_t *check; unsigned hash; // killough bool isskybox; - const FTransform *xform = &xxform; + fixed_t xoffs = FLOAT2FIXED(xform.xOffs); + fixed_t yoffs = FLOAT2FIXED(xform.yOffs + xform.baseyOffs); + fixed_t xscale = FLOAT2FIXED(xform.xScale); + fixed_t yscale = FLOAT2FIXED(xform.yScale); fixed_t alpha = FLOAT2FIXED(Alpha); - //angle_t angle = (xform.Angle + xform.baseAngle).BAMs(); - - FTransform nulltransform; + angle_t angle = (xform.Angle + xform.baseAngle).BAMs(); if (picnum == skyflatnum) // killough 10/98 { // most skies map together lightlevel = 0; - xform = &nulltransform; - nulltransform.xOffs = nulltransform.yOffs = nulltransform.baseyOffs = 0; - nulltransform.xScale = nulltransform.yScale = 1; - nulltransform.Angle = nulltransform.baseAngle = 0.0; + xoffs = 0; + yoffs = 0; + xscale = 0; + yscale = 0; + angle = 0; + alpha = 0; additive = false; // [RH] Map floor skies and ceiling skies to separate visplanes. This isn't // always necessary, but it is needed if a floor and ceiling sky are in the @@ -438,8 +656,13 @@ visplane_t *R_FindPlane (const secplane_t &height, FTextureID picnum, int lightl (plane == check->height && picnum == check->picnum && lightlevel == check->lightlevel && + + xoffs == check->xoffs && // killough 2/28/98: Add offset checks + yoffs == check->yoffs && basecolormap == check->colormap && // [RH] Add more checks - *xform == check->xform + xscale == check->xscale && + yscale == check->yscale && + angle == check->angle ) ) && check->viewangle == stacked_angle @@ -460,8 +683,12 @@ visplane_t *R_FindPlane (const secplane_t &height, FTextureID picnum, int lightl if (plane == check->height && picnum == check->picnum && lightlevel == check->lightlevel && + xoffs == check->xoffs && // killough 2/28/98: Add offset checks + yoffs == check->yoffs && basecolormap == check->colormap && // [RH] Add more checks - *xform == check->xform && + xscale == check->xscale && + yscale == check->yscale && + angle == check->angle && sky == check->sky && CurrentPortalUniq == check->CurrentPortalUniq && MirrorFlags == check->MirrorFlags && @@ -478,7 +705,11 @@ visplane_t *R_FindPlane (const secplane_t &height, FTextureID picnum, int lightl check->height = plane; check->picnum = picnum; check->lightlevel = lightlevel; - check->xform = *xform; + check->xoffs = xoffs; // killough 2/28/98: Save offsets + check->yoffs = yoffs; + check->xscale = xscale; + check->yscale = yscale; + check->angle = angle; check->colormap = basecolormap; // [RH] Save colormap check->sky = sky; check->portal = portal; @@ -494,7 +725,7 @@ visplane_t *R_FindPlane (const secplane_t &height, FTextureID picnum, int lightl check->MirrorFlags = MirrorFlags; check->CurrentSkybox = CurrentSkybox; - fillshort (check->top, viewwidth, 0x7fff); + clearbufshort (check->top, viewwidth, 0x7fff); return check; } @@ -563,7 +794,11 @@ visplane_t *R_CheckPlane (visplane_t *pl, int start, int stop) new_pl->height = pl->height; new_pl->picnum = pl->picnum; new_pl->lightlevel = pl->lightlevel; - new_pl->xform = pl->xform; + new_pl->xoffs = pl->xoffs; // killough 2/28/98 + new_pl->yoffs = pl->yoffs; + new_pl->xscale = pl->xscale; // [RH] copy these, too + new_pl->yscale = pl->yscale; + new_pl->angle = pl->angle; new_pl->colormap = pl->colormap; new_pl->portal = pl->portal; new_pl->extralight = pl->extralight; @@ -579,7 +814,7 @@ visplane_t *R_CheckPlane (visplane_t *pl, int start, int stop) pl = new_pl; pl->left = start; pl->right = stop; - fillshort (pl->top, viewwidth, 0x7fff); + clearbufshort (pl->top, viewwidth, 0x7fff); } return pl; } @@ -626,7 +861,7 @@ extern FTexture *rw_pic; // Allow for layer skies up to 512 pixels tall. This is overkill, // since the most anyone can ever see of the sky is 500 pixels. -// We need 4 skybufs because R_DrawSkySegment can draw up to 4 columns at a time. +// We need 4 skybufs because wallscan can draw up to 4 columns at a time. static BYTE skybuf[4][512]; static DWORD lastskycol[4]; static int skycolplace; @@ -684,171 +919,8 @@ static const BYTE *R_GetTwoSkyColumns (FTexture *fronttex, int x) return composite; } -static void R_DrawSkyColumnStripe(int start_x, int y1, int y2, int columns, double scale, double texturemid, double yrepeat) -{ - uint32_t height = frontskytex->GetHeight(); - - for (int i = 0; i < columns; i++) - { - double uv_stepd = skyiscale * yrepeat; - double v = (texturemid + uv_stepd * (y1 - CenterY + 0.5)) / height; - double v_step = uv_stepd / height; - - uint32_t uv_pos = (uint32_t)(v * 0x01000000); - uint32_t uv_step = (uint32_t)(v_step * 0x01000000); - - int x = start_x + i; - if (MirrorFlags & RF_XFLIP) - x = (viewwidth - x); - - DWORD ang, angle1, angle2; - - ang = (skyangle + xtoviewangle[x]) ^ skyflip; - angle1 = (DWORD)((UMulScale16(ang, frontcyl) + frontpos) >> FRACBITS); - angle2 = (DWORD)((UMulScale16(ang, backcyl) + backpos) >> FRACBITS); - - dc_wall_source[i] = (const BYTE *)frontskytex->GetColumn(angle1, nullptr); - dc_wall_source2[i] = backskytex ? (const BYTE *)backskytex->GetColumn(angle2, nullptr) : nullptr; - - dc_wall_iscale[i] = uv_step; - dc_wall_texturefrac[i] = uv_pos; - } - - dc_wall_sourceheight[0] = height; - dc_wall_sourceheight[1] = backskytex ? backskytex->GetHeight() : height; - dc_dest = (ylookup[y1] + start_x) + dc_destorg; - dc_count = y2 - y1; - - uint32_t solid_top = frontskytex->GetSkyCapColor(false); - uint32_t solid_bottom = frontskytex->GetSkyCapColor(true); - - if (columns == 4) - if (!backskytex) - R_DrawSingleSkyCol4(solid_top, solid_bottom); - else - R_DrawDoubleSkyCol4(solid_top, solid_bottom); - else - if (!backskytex) - R_DrawSingleSkyCol1(solid_top, solid_bottom); - else - R_DrawDoubleSkyCol1(solid_top, solid_bottom); -} - -static void R_DrawSkyColumn(int start_x, int y1, int y2, int columns) -{ - if (1 << frontskytex->HeightBits == frontskytex->GetHeight()) - { - double texturemid = skymid * frontskytex->Scale.Y + frontskytex->GetHeight(); - R_DrawSkyColumnStripe(start_x, y1, y2, columns, frontskytex->Scale.Y, texturemid, frontskytex->Scale.Y); - } - else - { - double yrepeat = frontskytex->Scale.Y; - double scale = frontskytex->Scale.Y * skyscale; - double iscale = 1 / scale; - short drawheight = short(frontskytex->GetHeight() * scale); - double topfrac = fmod(skymid + iscale * (1 - CenterY), frontskytex->GetHeight()); - if (topfrac < 0) topfrac += frontskytex->GetHeight(); - double texturemid = topfrac - iscale * (1 - CenterY); - R_DrawSkyColumnStripe(start_x, y1, y2, columns, scale, texturemid, yrepeat); - } -} - -static void R_DrawCapSky(visplane_t *pl) -{ - int x1 = pl->left; - int x2 = pl->right; - short *uwal = (short *)pl->top; - short *dwal = (short *)pl->bottom; - - // Calculate where 4 column alignment begins and ends: - int aligned_x1 = clamp((x1 + 3) / 4 * 4, x1, x2); - int aligned_x2 = clamp(x2 / 4 * 4, x1, x2); - - // First unaligned columns: - for (int x = x1; x < aligned_x1; x++) - { - int y1 = uwal[x]; - int y2 = dwal[x]; - if (y2 <= y1) - continue; - - R_DrawSkyColumn(x, y1, y2, 1); - } - - // The aligned columns - for (int x = aligned_x1; x < aligned_x2; x += 4) - { - // Find y1, y2, light and uv values for four columns: - int y1[4] = { uwal[x], uwal[x + 1], uwal[x + 2], uwal[x + 3] }; - int y2[4] = { dwal[x], dwal[x + 1], dwal[x + 2], dwal[x + 3] }; - - // Figure out where we vertically can start and stop drawing 4 columns in one go - int middle_y1 = y1[0]; - int middle_y2 = y2[0]; - for (int i = 1; i < 4; i++) - { - middle_y1 = MAX(y1[i], middle_y1); - middle_y2 = MIN(y2[i], middle_y2); - } - - // If we got an empty column in our set we cannot draw 4 columns in one go: - bool empty_column_in_set = false; - for (int i = 0; i < 4; i++) - { - if (y2[i] <= y1[i]) - empty_column_in_set = true; - } - if (empty_column_in_set || middle_y2 <= middle_y1) - { - for (int i = 0; i < 4; i++) - { - if (y2[i] <= y1[i]) - continue; - - R_DrawSkyColumn(x + i, y1[i], y2[i], 1); - } - continue; - } - - // Draw the first rows where not all 4 columns are active - for (int i = 0; i < 4; i++) - { - if (y1[i] < middle_y1) - R_DrawSkyColumn(x + i, y1[i], middle_y1, 1); - } - - // Draw the area where all 4 columns are active - R_DrawSkyColumn(x, middle_y1, middle_y2, 4); - - // Draw the last rows where not all 4 columns are active - for (int i = 0; i < 4; i++) - { - if (middle_y2 < y2[i]) - R_DrawSkyColumn(x + i, middle_y2, y2[i], 1); - } - } - - // The last unaligned columns: - for (int x = aligned_x2; x < x2; x++) - { - int y1 = uwal[x]; - int y2 = dwal[x]; - if (y2 <= y1) - continue; - - R_DrawSkyColumn(x, y1, y2, 1); - } -} - static void R_DrawSky (visplane_t *pl) { - if (r_skymode == 2 && !(level.flags & LEVEL_FORCETILEDSKY)) - { - R_DrawCapSky(pl); - return; - } - int x; float swal; @@ -893,7 +965,7 @@ static void R_DrawSky (visplane_t *pl) { lastskycol[x] = 0xffffffff; } - R_DrawSkySegment (pl, (short *)pl->top, (short *)pl->bottom, swall, lwall, + wallscan (pl->left, pl->right, (short *)pl->top, (short *)pl->bottom, swall, lwall, frontyScale, backskytex == NULL ? R_GetOneSkyColumn : R_GetTwoSkyColumns); } else @@ -930,7 +1002,7 @@ static void R_DrawSkyStriped (visplane_t *pl) { lastskycol[x] = 0xffffffff; } - R_DrawSkySegment (pl, top, bot, swall, lwall, rw_pic->Scale.Y, + wallscan (pl->left, pl->right, top, bot, swall, lwall, rw_pic->Scale.Y, backskytex == NULL ? R_GetOneSkyColumn : R_GetTwoSkyColumns); yl = yh; yh += drawheight; @@ -946,6 +1018,9 @@ static void R_DrawSkyStriped (visplane_t *pl) // //========================================================================== +CVAR (Bool, tilt, false, 0); +//CVAR (Int, pa, 0, 0) + int R_DrawPlanes () { visplane_t *pl; @@ -1042,20 +1117,20 @@ void R_DrawSinglePlane (visplane_t *pl, fixed_t alpha, bool additive, bool maske masked = false; } R_SetupSpanBits(tex); - double xscale = pl->xform.xScale * tex->Scale.X; - double yscale = pl->xform.yScale * tex->Scale.Y; - R_SetSpanSource(tex); + pl->xscale = fixed_t(pl->xscale * tex->Scale.X); + pl->yscale = fixed_t(pl->yscale * tex->Scale.Y); + ds_source = tex->GetPixels (); basecolormap = pl->colormap; planeshade = LIGHT2SHADE(pl->lightlevel); if (r_drawflat || (!pl->height.isSlope() && !tilt)) { - R_DrawNormalPlane(pl, xscale, yscale, alpha, additive, masked); + R_DrawNormalPlane (pl, alpha, additive, masked); } else { - R_DrawTiltedPlane(pl, xscale, yscale, alpha, additive, masked); + R_DrawTiltedPlane (pl, alpha, additive, masked); } } NetUpdate (); @@ -1081,6 +1156,7 @@ void R_DrawSinglePlane (visplane_t *pl, fixed_t alpha, bool additive, bool maske // 9. Put the camera back where it was to begin with. // //========================================================================== +CVAR (Bool, r_skyboxes, true, 0) static int numskyboxes; void R_DrawPortals () @@ -1137,7 +1213,7 @@ void R_DrawPortals () case PORTS_SKYVIEWPOINT: { // Don't let gun flashes brighten the sky box - AActor *sky = port->mSkybox; + ASkyViewpoint *sky = barrier_cast(port->mSkybox); extralight = 0; R_SetVisibility(sky->args[0] * 0.25f); @@ -1172,7 +1248,7 @@ void R_DrawPortals () } port->mFlags |= PORTSF_INSKYBOX; - if (port->mPartner > 0) level.sectorPortals[port->mPartner].mFlags |= PORTSF_INSKYBOX; + if (port->mPartner > 0) sectorPortals[port->mPartner].mFlags |= PORTSF_INSKYBOX; camera = NULL; viewsector = port->mDestination; assert(viewsector != NULL); @@ -1235,7 +1311,7 @@ void R_DrawPortals () R_DrawPlanes (); port->mFlags &= ~PORTSF_INSKYBOX; - if (port->mPartner > 0) level.sectorPortals[port->mPartner].mFlags &= ~PORTSF_INSKYBOX; + if (port->mPartner > 0) sectorPortals[port->mPartner].mFlags &= ~PORTSF_INSKYBOX; } // Draw all the masked textures in a second pass, in the reverse order they @@ -1259,7 +1335,7 @@ void R_DrawPortals () vissprite_p = firstvissprite; visplaneStack.Pop (pl); - if (pl->Alpha > 0 && pl->picnum != skyflatnum) + if (pl->Alpha > 0) { R_DrawSinglePlane (pl, pl->Alpha, pl->Additive, true); } @@ -1349,7 +1425,7 @@ void R_DrawSkyPlane (visplane_t *pl) else { // MBF's linedef-controlled skies // Sky Linedef - const line_t *l = &level.lines[(pl->sky & ~PL_SKYFLAT)-1]; + const line_t *l = &lines[(pl->sky & ~PL_SKYFLAT)-1]; // Sky transferred from first sidedef const side_t *s = l->sidedef[0]; @@ -1406,13 +1482,12 @@ void R_DrawSkyPlane (visplane_t *pl) bool fakefixed = false; if (fixedcolormap) { - R_SetColorMapLight(fixedcolormap, 0, 0); + dc_colormap = fixedcolormap; } else { fakefixed = true; - fixedcolormap = NormalLight.Maps; - R_SetColorMapLight(fixedcolormap, 0, 0); + fixedcolormap = dc_colormap = NormalLight.Maps; } R_DrawSky (pl); @@ -1427,80 +1502,67 @@ void R_DrawSkyPlane (visplane_t *pl) // //========================================================================== -void R_DrawNormalPlane (visplane_t *pl, double _xscale, double _yscale, fixed_t alpha, bool additive, bool masked) +void R_DrawNormalPlane (visplane_t *pl, fixed_t alpha, bool additive, bool masked) { +#ifdef X86_ASM + if (ds_source != ds_cursource) + { + R_SetSpanSource_ASM (ds_source); + } +#endif + if (alpha <= 0) { return; } - double planeang = (pl->xform.Angle + pl->xform.baseAngle).Radians(); - double xstep, ystep, leftxfrac, leftyfrac, rightxfrac, rightyfrac; - double x; - - xscale = xs_ToFixed(32 - ds_xbits, _xscale); - yscale = xs_ToFixed(32 - ds_ybits, _yscale); + angle_t planeang = pl->angle; + xscale = pl->xscale << (16 - ds_xbits); + yscale = pl->yscale << (16 - ds_ybits); if (planeang != 0) { - double cosine = cos(planeang), sine = sin(planeang); - pviewx = FLOAT2FIXED(pl->xform.xOffs + ViewPos.X * cosine - ViewPos.Y * sine); - pviewy = FLOAT2FIXED(pl->xform.yOffs - ViewPos.X * sine - ViewPos.Y * cosine); + double rad = planeang * (M_PI / ANGLE_180); + double cosine = cos(rad), sine = sin(rad); + + pviewx = xs_RoundToInt(pl->xoffs + FLOAT2FIXED(ViewPos.X * cosine - ViewPos.Y * sine)); + pviewy = xs_RoundToInt(pl->yoffs - FLOAT2FIXED(ViewPos.X * sine - ViewPos.Y * cosine)); } else { - pviewx = FLOAT2FIXED(pl->xform.xOffs + ViewPos.X); - pviewy = FLOAT2FIXED(pl->xform.yOffs - ViewPos.Y); + pviewx = pl->xoffs + FLOAT2FIXED(ViewPos.X); + pviewy = pl->yoffs - FLOAT2FIXED(ViewPos.Y); } pviewx = FixedMul (xscale, pviewx); pviewy = FixedMul (yscale, pviewy); // left to right mapping - planeang += (ViewAngle - 90).Radians(); - + planeang = (ViewAngle.BAMs() - ANG90 + planeang) >> ANGLETOFINESHIFT; // Scale will be unit scale at FocalLengthX (normally SCREENWIDTH/2) distance - xstep = cos(planeang) / FocalLengthX; - ystep = -sin(planeang) / FocalLengthX; + xstepscale = fixed_t(FixedMul(xscale, finecosine[planeang]) / FocalLengthX); + ystepscale = fixed_t(FixedMul(yscale, -finesine[planeang]) / FocalLengthX); // [RH] flip for mirrors if (MirrorFlags & RF_XFLIP) { - xstep = -xstep; - ystep = -ystep; + xstepscale = (DWORD)(-(SDWORD)xstepscale); + ystepscale = (DWORD)(-(SDWORD)ystepscale); } - planeang += M_PI/2; - double cosine = cos(planeang), sine = -sin(planeang); - x = pl->right - centerx - 0.5; - rightxfrac = _xscale * (cosine + x * xstep); - rightyfrac = _yscale * (sine + x * ystep); - x = pl->left - centerx - 0.5; - leftxfrac = _xscale * (cosine + x * xstep); - leftyfrac = _yscale * (sine + x * ystep); - - basexfrac = rightxfrac; - baseyfrac = rightyfrac; - xstepscale = (rightxfrac - leftxfrac) / (pl->right - pl->left); - ystepscale = (rightyfrac - leftyfrac) / (pl->right - pl->left); + int x = pl->right - halfviewwidth - 1; + planeang = (planeang + (ANG90 >> ANGLETOFINESHIFT)) & FINEMASK; + basexfrac = FixedMul (xscale, finecosine[planeang]) + x*xstepscale; + baseyfrac = FixedMul (yscale, -finesine[planeang]) + x*ystepscale; planeheight = fabs(pl->height.Zat0() - ViewPos.Z); GlobVis = r_FloorVisibility / planeheight; - ds_light = 0; if (fixedlightlev >= 0) - { - R_SetDSColorMapLight(basecolormap, 0, FIXEDLIGHT2SHADE(fixedlightlev)); - plane_shade = false; - } + ds_colormap = basecolormap->Maps + fixedlightlev, plane_shade = false; else if (fixedcolormap) - { - R_SetDSColorMapLight(fixedcolormap, 0, 0); - plane_shade = false; - } + ds_colormap = fixedcolormap, plane_shade = false; else - { plane_shade = true; - } if (spanfunc != R_FillSpan) { @@ -1558,7 +1620,7 @@ void R_DrawNormalPlane (visplane_t *pl, double _xscale, double _yscale, fixed_t // //========================================================================== -void R_DrawTiltedPlane (visplane_t *pl, double _xscale, double _yscale, fixed_t alpha, bool additive, bool masked) +void R_DrawTiltedPlane (visplane_t *pl, fixed_t alpha, bool additive, bool masked) { static const float ifloatpow2[16] = { @@ -1572,7 +1634,7 @@ void R_DrawTiltedPlane (visplane_t *pl, double _xscale, double _yscale, fixed_t double lxscale, lyscale; double xscale, yscale; FVector3 p, m, n; - double ang, planeang, cosine, sine; + double ang; double zeroheight; if (alpha <= 0) @@ -1580,52 +1642,44 @@ void R_DrawTiltedPlane (visplane_t *pl, double _xscale, double _yscale, fixed_t return; } - lxscale = _xscale * ifloatpow2[ds_xbits]; - lyscale = _yscale * ifloatpow2[ds_ybits]; + lxscale = FIXED2DBL(pl->xscale) * ifloatpow2[ds_xbits]; + lyscale = FIXED2DBL(pl->yscale) * ifloatpow2[ds_ybits]; xscale = 64.f / lxscale; yscale = 64.f / lyscale; zeroheight = pl->height.ZatPoint(ViewPos); - pviewx = xs_ToFixed(32 - ds_xbits, pl->xform.xOffs * pl->xform.xScale); - pviewy = xs_ToFixed(32 - ds_ybits, pl->xform.yOffs * pl->xform.yScale); - planeang = (pl->xform.Angle + pl->xform.baseAngle).Radians(); + pviewx = MulScale (pl->xoffs, pl->xscale, ds_xbits); + pviewy = MulScale (pl->yoffs, pl->yscale, ds_ybits); // p is the texture origin in view space // Don't add in the offsets at this stage, because doing so can result in // errors if the flat is rotated. - ang = M_PI*3/2 - ViewAngle.Radians(); - cosine = cos(ang), sine = sin(ang); - p[0] = ViewPos.X * cosine - ViewPos.Y * sine; - p[2] = ViewPos.X * sine + ViewPos.Y * cosine; + ang = (DAngle(270.) - ViewAngle).Radians(); + p[0] = ViewPos.X * cos(ang) - ViewPos.Y * sin(ang); + p[2] = ViewPos.X * sin(ang) + ViewPos.Y * cos(ang); p[1] = pl->height.ZatPoint(0.0, 0.0) - ViewPos.Z; // m is the v direction vector in view space - ang = ang - M_PI / 2 - planeang; - cosine = cos(ang), sine = sin(ang); - m[0] = yscale * cosine; - m[2] = yscale * sine; + ang = (DAngle(180.) - ViewAngle).Radians(); + m[0] = yscale * cos(ang); + m[2] = yscale * sin(ang); // m[1] = pl->height.ZatPointF (0, iyscale) - pl->height.ZatPointF (0,0)); // VectorScale2 (m, 64.f/VectorLength(m)); // n is the u direction vector in view space -#if 0 - //let's use the sin/cosine we already know instead of computing new ones - ang += M_PI/2 + ang += PI/2; n[0] = -xscale * cos(ang); n[2] = -xscale * sin(ang); -#else - n[0] = xscale * sine; - n[2] = -xscale * cosine; -#endif // n[1] = pl->height.ZatPointF (ixscale, 0) - pl->height.ZatPointF (0,0)); // VectorScale2 (n, 64.f/VectorLength(n)); // This code keeps the texture coordinates constant across the x,y plane no matter // how much you slope the surface. Use the commented-out code above instead to keep // the textures a constant size across the surface's plane instead. - cosine = cos(planeang), sine = sin(planeang); - m[1] = pl->height.ZatPoint(ViewPos.X + yscale * sine, ViewPos.Y + yscale * cosine) - zeroheight; - n[1] = -(pl->height.ZatPoint(ViewPos.X - xscale * cosine, ViewPos.Y + xscale * sine) - zeroheight); + ang = pl->angle * (M_PI / ANGLE_180); + m[1] = pl->height.ZatPoint(ViewPos.X + yscale * sin(ang), ViewPos.Y + yscale * cos(ang)) - zeroheight; + ang += PI/2; + n[1] = pl->height.ZatPoint(ViewPos.X + xscale * sin(ang), ViewPos.Y + xscale * cos(ang)) - zeroheight; plane_su = p ^ m; plane_sv = p ^ n; @@ -1656,32 +1710,27 @@ void R_DrawTiltedPlane (visplane_t *pl, double _xscale, double _yscale, fixed_t planelightfloat = -planelightfloat; if (fixedlightlev >= 0) - { - R_SetDSColorMapLight(basecolormap, 0, FIXEDLIGHT2SHADE(fixedlightlev)); - plane_shade = false; - } + ds_colormap = basecolormap->Maps + fixedlightlev, plane_shade = false; else if (fixedcolormap) - { - R_SetDSColorMapLight(fixedcolormap, 0, 0); - plane_shade = false; - } + ds_colormap = fixedcolormap, plane_shade = false; else + ds_colormap = basecolormap->Maps, plane_shade = true; + + if (!plane_shade) { - R_SetDSColorMapLight(basecolormap, 0, 0); - plane_shade = true; - } - - // Hack in support for 1 x Z and Z x 1 texture sizes - if (ds_ybits == 0) - { - plane_sv[2] = plane_sv[1] = plane_sv[0] = 0; - } - if (ds_xbits == 0) - { - plane_su[2] = plane_su[1] = plane_su[0] = 0; + for (int i = 0; i < viewwidth; ++i) + { + tiltlighting[i] = ds_colormap; + } } +#if defined(X86_ASM) + if (ds_source != ds_curtiltedsource) + R_SetTiltedSpanSource_ASM (ds_source); + R_MapVisPlane (pl, R_DrawTiltedPlane_ASM); +#else R_MapVisPlane (pl, R_MapTiltedPlane); +#endif } //========================================================================== @@ -1702,7 +1751,7 @@ void R_MapVisPlane (visplane_t *pl, void (*mapfunc)(int y, int x1)) if (b2 > t2) { - fillshort (spanend+t2, b2-t2, x); + clearbufshort (spanend+t2, b2-t2, x); } for (--x; x >= pl->left; --x) @@ -1785,5 +1834,3 @@ bool R_PlaneInitData () return true; } - -} diff --git a/src/r_plane.h b/src/r_plane.h new file mode 100644 index 000000000..25e46de98 --- /dev/null +++ b/src/r_plane.h @@ -0,0 +1,117 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id:$ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This source is available for distribution and/or modification +// only under the terms of the DOOM Source Code License as +// published by id Software. All rights reserved. +// +// The source is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License +// for more details. +// +// DESCRIPTION: +// Refresh, visplane stuff (floor, ceilings). +// +//----------------------------------------------------------------------------- + + +#ifndef __R_PLANE_H__ +#define __R_PLANE_H__ + +#include + +class ASkyViewpoint; + +// +// The infamous visplane +// +struct visplane_s +{ + struct visplane_s *next; // Next visplane in hash chain -- killough + + secplane_t height; + FTextureID picnum; + int lightlevel; + fixed_t xoffs, yoffs; // killough 2/28/98: Support scrolling flats + int left, right; + FDynamicColormap *colormap; // [RH] Support multiple colormaps + fixed_t xscale, yscale; // [RH] Support flat scaling + angle_t angle; // [RH] Support flat rotation + int sky; + FSectorPortal *portal; // [RH] Support sky boxes + + // [RH] This set of variables copies information from the time when the + // visplane is created. They are only used by stacks so that you can + // have stacked sectors inside a skybox. If the visplane is not for a + // stack, then they are unused. + int extralight; + double visibility; + DVector3 viewpos; + DAngle viewangle; + fixed_t Alpha; + bool Additive; + + // kg3D - keep track of mirror and skybox owner + int CurrentSkybox; + int CurrentPortalUniq; // mirror counter, counts all of them + int MirrorFlags; // this is not related to CurrentMirror + + unsigned short *bottom; // [RH] bottom and top arrays are dynamically + unsigned short pad; // allocated immediately after the + unsigned short top[]; // visplane. +}; +typedef struct visplane_s visplane_t; + + + +// Visplane related. +extern ptrdiff_t lastopening; // type short + + +typedef void (*planefunction_t) (int top, int bottom); + +extern planefunction_t floorfunc; +extern planefunction_t ceilingfunc_t; + +extern short floorclip[MAXWIDTH]; +extern short ceilingclip[MAXWIDTH]; + +extern fixed_t yslope[MAXHEIGHT]; + +void R_InitPlanes (); +void R_DeinitPlanes (); +void R_ClearPlanes (bool fullclear); + +int R_DrawPlanes (); +void R_DrawPortals (); +void R_DrawSkyPlane (visplane_t *pl); +void R_DrawNormalPlane (visplane_t *pl, fixed_t alpha, bool additive, bool masked); +void R_DrawTiltedPlane (visplane_t *pl, fixed_t alpha, bool additive, bool masked); +void R_MapVisPlane (visplane_t *pl, void (*mapfunc)(int y, int x1)); + +visplane_t *R_FindPlane +( const secplane_t &height, + FTextureID picnum, + int lightlevel, + double alpha, + bool additive, + const FTransform &xform, + int sky, + FSectorPortal *portal); + +visplane_t *R_CheckPlane (visplane_t *pl, int start, int stop); + + +// [RH] Added for multires support +bool R_PlaneInitData (void); + + +extern visplane_t* floorplane; +extern visplane_t* ceilingplane; + +#endif // __R_PLANE_H__ diff --git a/src/r_segs.cpp b/src/r_segs.cpp index 0873cb373..696e30b7c 100644 --- a/src/r_segs.cpp +++ b/src/r_segs.cpp @@ -17,6 +17,12 @@ // DESCRIPTION: // All the clipping: columns, horizontal spans, sky columns. // +// This file contains some code from the Build Engine. +// +// "Build Engine & Tools" Copyright (c) 1993-1997 Ken Silverman +// Ken Silverman's official web site: "http://www.advsys.net/ken" +// See the included license file "BUILDLIC.TXT" for license info. +// //----------------------------------------------------------------------------- #include @@ -44,23 +50,16 @@ #include "r_plane.h" #include "r_segs.h" #include "r_3dfloors.h" -#include "r_draw.h" #include "v_palette.h" #include "r_data/colormaps.h" #define WALLYREPEAT 8 -CVAR(Bool, r_fogboundary, true, 0) -CVAR(Bool, r_drawmirrors, true, 0) -EXTERN_CVAR(Bool, r_fullbrightignoresectorcolor); +CVAR(Bool, r_np2, true, 0) -namespace swrenderer -{ - using namespace drawerargs; - - void R_DrawWallSegment(FTexture *rw_pic, int x1, int x2, short *walltop, short *wallbottom, float *swall, fixed_t *lwall, double yscale, double top, double bottom, bool mask); - void R_DrawDrawSeg(drawseg_t *ds, int x1, int x2, short *uwal, short *dwal, float *swal, fixed_t *lwal, double yrepeat); +//CVAR (Int, ty, 8, 0) +//CVAR (Int, tx, 8, 0) #define HEIGHTBITS 12 #define HEIGHTSHIFT (FRACBITS-HEIGHTBITS) @@ -137,6 +136,19 @@ static fixed_t *maskedtexturecol; static void R_RenderDecal (side_t *wall, DBaseDecal *first, drawseg_t *clipper, int pass); static void WallSpriteColumn (void (*drawfunc)(const BYTE *column, const FTexture::Span *spans)); +void wallscan_np2(int x1, int x2, short *uwal, short *dwal, float *swal, fixed_t *lwal, double yrepeat, double top, double bot, bool mask); +static void wallscan_np2_ds(drawseg_t *ds, int x1, int x2, short *uwal, short *dwal, float *swal, fixed_t *lwal, double yrepeat); +static void call_wallscan(int x1, int x2, short *uwal, short *dwal, float *swal, fixed_t *lwal, double yrepeat, bool mask); + +//============================================================================= +// +// CVAR r_fogboundary +// +// If true, makes fog look more "real" by shading the walls separating two +// sectors with different fog. +//============================================================================= + +CVAR(Bool, r_fogboundary, true, 0) inline bool IsFogBoundary (sector_t *front, sector_t *back) { @@ -145,6 +157,14 @@ inline bool IsFogBoundary (sector_t *front, sector_t *back) (front->GetTexture(sector_t::ceiling) != skyflatnum || back->GetTexture(sector_t::ceiling) != skyflatnum); } +//============================================================================= +// +// CVAR r_drawmirrors +// +// Set to false to disable rendering of mirrors +//============================================================================= + +CVAR(Bool, r_drawmirrors, true, 0) // // R_RenderMaskedSegRange @@ -152,12 +172,12 @@ inline bool IsFogBoundary (sector_t *front, sector_t *back) float *MaskedSWall; float MaskedScaleY; -static void BlastMaskedColumn (FTexture *tex, bool useRt) +static void BlastMaskedColumn (void (*blastfunc)(const BYTE *pixels, const FTexture::Span *spans), FTexture *tex) { // calculate lighting if (fixedcolormap == NULL && fixedlightlev < 0) { - R_SetColorMapLight(basecolormap, rw_light, wallshade); + dc_colormap = basecolormap->Maps + (GETPALOOKUP (rw_light, wallshade) << COLORMAPSHIFT); } dc_iscale = xs_Fix<16>::ToFix(MaskedSWall[dc_x] * MaskedScaleY); @@ -175,7 +195,9 @@ static void BlastMaskedColumn (FTexture *tex, bool useRt) // when forming multipatched textures (see r_data.c). // draw the texture - R_DrawMaskedColumn(tex, maskedtexturecol[dc_x], useRt); + const FTexture::Span *spans; + const BYTE *pixels = tex->GetColumn (maskedtexturecol[dc_x] >> FRACBITS, &spans); + blastfunc (pixels, spans); rw_light += rw_lightstep; spryscale += rw_scalestep; } @@ -185,13 +207,13 @@ void ClipMidtex(int x1, int x2) { short most[MAXWIDTH]; - R_CreateWallSegmentYSloped(most, curline->frontsector->ceilingplane, &WallC); + WallMost(most, curline->frontsector->ceilingplane, &WallC); for (int i = x1; i < x2; ++i) { if (wallupper[i] < most[i]) wallupper[i] = most[i]; } - R_CreateWallSegmentYSloped(most, curline->frontsector->floorplane, &WallC); + WallMost(most, curline->frontsector->floorplane, &WallC); for (int i = x1; i < x2; ++i) { if (walllower[i] > most[i]) @@ -209,7 +231,6 @@ void R_RenderMaskedSegRange (drawseg_t *ds, int x1, int x2) double texheight, texheightscale; bool notrelevant = false; double rowoffset; - bool wrap = false; const sector_t *sec; @@ -291,9 +312,9 @@ void R_RenderMaskedSegRange (drawseg_t *ds, int x1, int x2) rw_scalestep = ds->iscalestep; if (fixedlightlev >= 0) - R_SetColorMapLight((r_fullbrightignoresectorcolor) ? &FullNormalLight : basecolormap, 0, FIXEDLIGHT2SHADE(fixedlightlev)); + dc_colormap = basecolormap->Maps + fixedlightlev; else if (fixedcolormap != NULL) - R_SetColorMapLight(fixedcolormap, 0, 0); + dc_colormap = fixedcolormap; // find positioning texheight = tex->GetScaledHeightDouble(); @@ -313,8 +334,8 @@ void R_RenderMaskedSegRange (drawseg_t *ds, int x1, int x2) rowoffset = curline->sidedef->GetTextureYOffset(side_t::mid); - wrap = (curline->linedef->flags & ML_WRAP_MIDTEX) || (curline->sidedef->Flags & WALLF_WRAP_MIDTEX); - if (!wrap) + if (!(curline->linedef->flags & ML_WRAP_MIDTEX) && + !(curline->sidedef->Flags & WALLF_WRAP_MIDTEX)) { // Texture does not wrap vertically. double textop; @@ -373,19 +394,19 @@ void R_RenderMaskedSegRange (drawseg_t *ds, int x1, int x2) if (fake3D & FAKE3D_CLIPTOP) { - R_CreateWallSegmentY(wallupper, textop < sclipTop - ViewPos.Z ? textop : sclipTop - ViewPos.Z, &WallC); + OWallMost(wallupper, textop < sclipTop - ViewPos.Z ? textop : sclipTop - ViewPos.Z, &WallC); } else { - R_CreateWallSegmentY(wallupper, textop, &WallC); + OWallMost(wallupper, textop, &WallC); } if (fake3D & FAKE3D_CLIPBOTTOM) { - R_CreateWallSegmentY(walllower, textop - texheight > sclipBottom - ViewPos.Z ? textop - texheight : sclipBottom - ViewPos.Z, &WallC); + OWallMost(walllower, textop - texheight > sclipBottom - ViewPos.Z ? textop - texheight : sclipBottom - ViewPos.Z, &WallC); } else { - R_CreateWallSegmentY(walllower, textop - texheight, &WallC); + OWallMost(walllower, textop - texheight, &WallC); } for (i = x1; i < x2; i++) @@ -418,7 +439,7 @@ void R_RenderMaskedSegRange (drawseg_t *ds, int x1, int x2) { for (dc_x = x1; dc_x < x2; ++dc_x) { - BlastMaskedColumn (tex, false); + BlastMaskedColumn (R_DrawMaskedColumn, tex); } } else @@ -433,24 +454,24 @@ void R_RenderMaskedSegRange (drawseg_t *ds, int x1, int x2) while ((dc_x < stop) && (dc_x & 3)) { - BlastMaskedColumn (tex, false); + BlastMaskedColumn (R_DrawMaskedColumn, tex); dc_x++; } while (dc_x < stop) { - rt_initcols(nullptr); - BlastMaskedColumn (tex, true); dc_x++; - BlastMaskedColumn (tex, true); dc_x++; - BlastMaskedColumn (tex, true); dc_x++; - BlastMaskedColumn (tex, true); + rt_initcols(); + BlastMaskedColumn (R_DrawMaskedColumnHoriz, tex); dc_x++; + BlastMaskedColumn (R_DrawMaskedColumnHoriz, tex); dc_x++; + BlastMaskedColumn (R_DrawMaskedColumnHoriz, tex); dc_x++; + BlastMaskedColumn (R_DrawMaskedColumnHoriz, tex); rt_draw4cols (dc_x - 3); dc_x++; } while (dc_x < x2) { - BlastMaskedColumn (tex, false); + BlastMaskedColumn (R_DrawMaskedColumn, tex); dc_x++; } } @@ -461,7 +482,7 @@ void R_RenderMaskedSegRange (drawseg_t *ds, int x1, int x2) { // rowoffset is added before the multiply so that the masked texture will // still be positioned in world units rather than texels. - dc_texturemid = (dc_texturemid - ViewPos.Z + rowoffset) * MaskedScaleY; + dc_texturemid = (dc_texturemid + rowoffset - ViewPos.Z) * MaskedScaleY; } else { @@ -488,7 +509,7 @@ void R_RenderMaskedSegRange (drawseg_t *ds, int x1, int x2) if (fake3D & FAKE3D_CLIPTOP) { - R_CreateWallSegmentY(wallupper, sclipTop - ViewPos.Z, &WallC); + OWallMost(wallupper, sclipTop - ViewPos.Z, &WallC); for (i = x1; i < x2; i++) { if (wallupper[i] < mceilingclip[i]) @@ -498,7 +519,7 @@ void R_RenderMaskedSegRange (drawseg_t *ds, int x1, int x2) } if (fake3D & FAKE3D_CLIPBOTTOM) { - R_CreateWallSegmentY(walllower, sclipBottom - ViewPos.Z, &WallC); + OWallMost(walllower, sclipBottom - ViewPos.Z, &WallC); for (i = x1; i < x2; i++) { if (walllower[i] > mfloorclip[i]) @@ -509,7 +530,7 @@ void R_RenderMaskedSegRange (drawseg_t *ds, int x1, int x2) rw_offset = 0; rw_pic = tex; - R_DrawDrawSeg(ds, x1, x2, mceilingclip, mfloorclip, MaskedSWall, maskedtexturecol, ds->yscale); + wallscan_np2_ds(ds, x1, x2, mceilingclip, mfloorclip, MaskedSWall, maskedtexturecol, ds->yscale); } clearfog: @@ -522,15 +543,12 @@ clearfog: { if (fake3D & FAKE3D_REFRESHCLIP) { - if (!wrap) - { - assert(ds->bkup >= 0); - memcpy(openings + ds->sprtopclip, openings + ds->bkup, (ds->x2 - ds->x1) * 2); - } + assert(ds->bkup >= 0); + memcpy(openings + ds->sprtopclip, openings + ds->bkup, (ds->x2 - ds->x1) * 2); } else { - fillshort(openings + ds->sprtopclip - ds->x1 + x1, x2 - x1, viewheight); + clearbufshort(openings + ds->sprtopclip - ds->x1 + x1, x2 - x1, viewheight); } } return; @@ -540,7 +558,7 @@ clearfog: void R_RenderFakeWall(drawseg_t *ds, int x1, int x2, F3DFloor *rover) { int i; - double xscale; + fixed_t xscale; double yscale; fixed_t Alpha = Scale(rover->alpha, OPAQUE, 255); @@ -581,7 +599,7 @@ void R_RenderFakeWall(drawseg_t *ds, int x1, int x2, F3DFloor *rover) scaledside = rover->master->sidedef[0]; scaledpart = side_t::mid; } - xscale = rw_pic->Scale.X * scaledside->GetTextureXScale(scaledpart); + xscale = FLOAT2FIXED(rw_pic->Scale.X * scaledside->GetTextureXScale(scaledpart)); yscale = rw_pic->Scale.Y * scaledside->GetTextureYScale(scaledpart); double rowoffset = curline->sidedef->GetTextureYOffset(side_t::mid) + rover->master->sidedef[0]->GetTextureYOffset(side_t::mid); @@ -598,7 +616,7 @@ void R_RenderFakeWall(drawseg_t *ds, int x1, int x2, F3DFloor *rover) // still be positioned in world units rather than texels. dc_texturemid = dc_texturemid + rowoffset * yscale; - rw_offset = xs_RoundToInt(rw_offset * xscale); + rw_offset = MulScale16 (rw_offset, xscale); } else { @@ -608,9 +626,9 @@ void R_RenderFakeWall(drawseg_t *ds, int x1, int x2, F3DFloor *rover) } if (fixedlightlev >= 0) - R_SetColorMapLight((r_fullbrightignoresectorcolor) ? &FullNormalLight : basecolormap, 0, FIXEDLIGHT2SHADE(fixedlightlev)); + dc_colormap = basecolormap->Maps + fixedlightlev; else if (fixedcolormap != NULL) - R_SetColorMapLight(fixedcolormap, 0, 0); + dc_colormap = fixedcolormap; WallC.sz1 = ds->sz1; WallC.sz2 = ds->sz2; @@ -622,8 +640,8 @@ void R_RenderFakeWall(drawseg_t *ds, int x1, int x2, F3DFloor *rover) WallC.tright.Y = ds->cy + ds->cdy; WallT = ds->tmapvals; - R_CreateWallSegmentY(wallupper, sclipTop - ViewPos.Z, &WallC); - R_CreateWallSegmentY(walllower, sclipBottom - ViewPos.Z, &WallC); + OWallMost(wallupper, sclipTop - ViewPos.Z, &WallC); + OWallMost(walllower, sclipBottom - ViewPos.Z, &WallC); for (i = x1; i < x2; i++) { @@ -637,7 +655,7 @@ void R_RenderFakeWall(drawseg_t *ds, int x1, int x2, F3DFloor *rover) } PrepLWall (lwall, curline->sidedef->TexelLength*xscale, ds->sx1, ds->sx2); - R_DrawDrawSeg(ds, x1, x2, wallupper, walllower, MaskedSWall, lwall, yscale); + wallscan_np2_ds(ds, x1, x2, wallupper, walllower, MaskedSWall, lwall, yscale); R_FinishSetPatchStyle(); } @@ -648,8 +666,8 @@ void R_RenderFakeWallRange (drawseg_t *ds, int x1, int x2) int i,j; F3DFloor *rover, *fover = NULL; int passed, last; - double floorHeight; - double ceilingHeight; + fixed_t floorheight; + fixed_t ceilingheight; sprflipvert = false; curline = ds->curline; @@ -668,16 +686,16 @@ void R_RenderFakeWallRange (drawseg_t *ds, int x1, int x2) frontsector = sec; } - floorHeight = backsector->CenterFloor(); - ceilingHeight = backsector->CenterCeiling(); + floorheight = FLOAT2FIXED(backsector->CenterFloor()); + ceilingheight = FLOAT2FIXED(backsector->CenterCeiling()); // maybe fix clipheights - if (!(fake3D & FAKE3D_CLIPBOTTOM)) sclipBottom = floorHeight; - if (!(fake3D & FAKE3D_CLIPTOP)) sclipTop = ceilingHeight; + if (!(fake3D & FAKE3D_CLIPBOTTOM)) sclipBottom = floorheight; + if (!(fake3D & FAKE3D_CLIPTOP)) sclipTop = ceilingheight; // maybe not visible - if (sclipBottom >= frontsector->CenterCeiling()) return; - if (sclipTop <= frontsector->CenterFloor()) return; + if (sclipBottom >= FLOAT2FIXED(frontsector->CenterCeiling())) return; + if (sclipTop <= FLOAT2FIXED(frontsector->CenterFloor())) return; if (fake3D & FAKE3D_DOWN2UP) { // bottom to viewz @@ -691,8 +709,8 @@ void R_RenderFakeWallRange (drawseg_t *ds, int x1, int x2) passed = 0; if (!(rover->flags & FF_RENDERSIDES) || rover->top.plane->isSlope() || rover->bottom.plane->isSlope() || rover->top.plane->Zat0() <= sclipBottom || - rover->bottom.plane->Zat0() >= ceilingHeight || - rover->top.plane->Zat0() <= floorHeight) + rover->bottom.plane->Zat0() >= ceilingheight || + rover->top.plane->Zat0() <= floorheight) { if (!i) { @@ -874,8 +892,8 @@ void R_RenderFakeWallRange (drawseg_t *ds, int x1, int x2) if (!(rover->flags & FF_RENDERSIDES) || rover->top.plane->isSlope() || rover->bottom.plane->isSlope() || rover->bottom.plane->Zat0() >= sclipTop || - rover->top.plane->Zat0() <= floorHeight || - rover->bottom.plane->Zat0() >= ceilingHeight) + rover->top.plane->Zat0() <= floorheight || + rover->bottom.plane->Zat0() >= ceilingheight) { if ((unsigned)i == backsector->e->XFloor.ffloors.Size() - 1) { @@ -1043,12 +1061,718 @@ void R_RenderFakeWallRange (drawseg_t *ds, int x1, int x2) return; } +// prevlineasm1 is like vlineasm1 but skips the loop if only drawing one pixel +inline fixed_t prevline1 (fixed_t vince, BYTE *colormap, int count, fixed_t vplce, const BYTE *bufplce, BYTE *dest) +{ + dc_iscale = vince; + dc_colormap = colormap; + dc_count = count; + dc_texturefrac = vplce; + dc_source = bufplce; + dc_dest = dest; + return doprevline1 (); +} + +void wallscan (int x1, int x2, short *uwal, short *dwal, float *swal, fixed_t *lwal, + double yrepeat, const BYTE *(*getcol)(FTexture *tex, int x)) +{ + int x, fracbits; + int y1ve[4], y2ve[4], u4, d4, z; + char bad; + float light = rw_light - rw_lightstep; + SDWORD xoffset; + BYTE *basecolormapdata; + double iscale; + + // This function also gets used to draw skies. Unlike BUILD, skies are + // drawn by visplane instead of by bunch, so these checks are invalid. + //if ((uwal[x1] > viewheight) && (uwal[x2] > viewheight)) return; + //if ((dwal[x1] < 0) && (dwal[x2] < 0)) return; + + if (rw_pic->UseType == FTexture::TEX_Null) + { + return; + } + +//extern cycle_t WallScanCycles; +//clock (WallScanCycles); + + rw_pic->GetHeight(); // Make sure texture size is loaded + fracbits = 32 - rw_pic->HeightBits; + setupvline(fracbits); + xoffset = rw_offset; + basecolormapdata = basecolormap->Maps; + + x = x1; + //while ((umost[x] > dmost[x]) && (x < x2)) x++; + + bool fixed = (fixedcolormap != NULL || fixedlightlev >= 0); + if (fixed) + { + palookupoffse[0] = dc_colormap; + palookupoffse[1] = dc_colormap; + palookupoffse[2] = dc_colormap; + palookupoffse[3] = dc_colormap; + } + + for(; (x < x2) && (x & 3); ++x) + { + light += rw_lightstep; + y1ve[0] = uwal[x];//max(uwal[x],umost[x]); + y2ve[0] = dwal[x];//min(dwal[x],dmost[x]); + if (y2ve[0] <= y1ve[0]) continue; + assert (y1ve[0] < viewheight); + assert (y2ve[0] <= viewheight); + + if (!fixed) + { // calculate lighting + dc_colormap = basecolormapdata + (GETPALOOKUP (light, wallshade) << COLORMAPSHIFT); + } + + dc_source = getcol (rw_pic, (lwal[x] + xoffset) >> FRACBITS); + dc_dest = ylookup[y1ve[0]] + x + dc_destorg; + dc_count = y2ve[0] - y1ve[0]; + iscale = swal[x] * yrepeat; + dc_iscale = xs_ToFixed(fracbits, iscale); + dc_texturefrac = xs_ToFixed(fracbits, dc_texturemid + iscale * (y1ve[0] - CenterY + 1)); + + dovline1(); + } + + for(; x < x2-3; x += 4) + { + bad = 0; + for (z = 3; z>= 0; --z) + { + y1ve[z] = uwal[x+z];//max(uwal[x+z],umost[x+z]); + y2ve[z] = dwal[x+z];//min(dwal[x+z],dmost[x+z])-1; + if (y2ve[z] <= y1ve[z]) { bad += 1<> FRACBITS); + iscale = swal[x + z] * yrepeat; + vince[z] = xs_ToFixed(fracbits, iscale); + vplce[z] = xs_ToFixed(fracbits, dc_texturemid + iscale * (y1ve[z] - CenterY + 1)); + } + if (bad == 15) + { + light += rw_lightstep * 4; + continue; + } + + if (!fixed) + { + for (z = 0; z < 4; ++z) + { + light += rw_lightstep; + palookupoffse[z] = basecolormapdata + (GETPALOOKUP (light, wallshade) << COLORMAPSHIFT); + } + } + + u4 = MAX(MAX(y1ve[0],y1ve[1]),MAX(y1ve[2],y1ve[3])); + d4 = MIN(MIN(y2ve[0],y2ve[1]),MIN(y2ve[2],y2ve[3])); + + if ((bad != 0) || (u4 >= d4)) + { + for (z = 0; z < 4; ++z) + { + if (!(bad & 1)) + { + prevline1(vince[z],palookupoffse[z],y2ve[z]-y1ve[z],vplce[z],bufplce[z],ylookup[y1ve[z]]+x+z+dc_destorg); + } + bad >>= 1; + } + continue; + } + + for (z = 0; z < 4; ++z) + { + if (u4 > y1ve[z]) + { + vplce[z] = prevline1(vince[z],palookupoffse[z],u4-y1ve[z],vplce[z],bufplce[z],ylookup[y1ve[z]]+x+z+dc_destorg); + } + } + + if (d4 > u4) + { + dc_count = d4-u4; + dc_dest = ylookup[u4]+x+dc_destorg; + dovline4(); + } + + BYTE *i = x+ylookup[d4]+dc_destorg; + for (z = 0; z < 4; ++z) + { + if (y2ve[z] > d4) + { + prevline1(vince[z],palookupoffse[0],y2ve[z]-d4,vplce[z],bufplce[z],i+z); + } + } + } + for(;x> FRACBITS); + dc_dest = ylookup[y1ve[0]] + x + dc_destorg; + dc_count = y2ve[0] - y1ve[0]; + iscale = swal[x] * yrepeat; + dc_iscale = xs_ToFixed(fracbits, iscale); + dc_texturefrac = xs_ToFixed(fracbits, dc_texturemid + iscale * (y1ve[0] - CenterY + 1)); + + dovline1(); + } + +//unclock (WallScanCycles); + + NetUpdate (); +} + +void wallscan_striped (int x1, int x2, short *uwal, short *dwal, float *swal, fixed_t *lwal, double yrepeat) +{ + FDynamicColormap *startcolormap = basecolormap; + int startshade = wallshade; + bool fogginess = foggy; + + short most1[MAXWIDTH], most2[MAXWIDTH], most3[MAXWIDTH]; + short *up, *down; + + up = uwal; + down = most1; + + assert(WallC.sx1 <= x1); + assert(WallC.sx2 >= x2); + + // kg3D - fake floors instead of zdoom light list + for (unsigned int i = 0; i < frontsector->e->XFloor.lightlist.Size(); i++) + { + int j = WallMost (most3, frontsector->e->XFloor.lightlist[i].plane, &WallC); + if (j != 3) + { + for (int j = x1; j < x2; ++j) + { + down[j] = clamp (most3[j], up[j], dwal[j]); + } + wallscan (x1, x2, up, down, swal, lwal, yrepeat); + up = down; + down = (down == most1) ? most2 : most1; + } + + lightlist_t *lit = &frontsector->e->XFloor.lightlist[i]; + basecolormap = lit->extra_colormap; + wallshade = LIGHT2SHADE(curline->sidedef->GetLightLevel(fogginess, + *lit->p_lightlevel, lit->lightsource != NULL) + r_actualextralight); + } + + wallscan (x1, x2, up, dwal, swal, lwal, yrepeat); + basecolormap = startcolormap; + wallshade = startshade; +} + +static void call_wallscan(int x1, int x2, short *uwal, short *dwal, float *swal, fixed_t *lwal, double yrepeat, bool mask) +{ + if (mask) + { + if (colfunc == basecolfunc) + { + maskwallscan(x1, x2, uwal, dwal, swal, lwal, yrepeat); + } + else + { + transmaskwallscan(x1, x2, uwal, dwal, swal, lwal, yrepeat); + } + } + else + { + if (fixedcolormap != NULL || fixedlightlev >= 0 || !(frontsector->e && frontsector->e->XFloor.lightlist.Size())) + { + wallscan(x1, x2, uwal, dwal, swal, lwal, yrepeat); + } + else + { + wallscan_striped(x1, x2, uwal, dwal, swal, lwal, yrepeat); + } + } +} + +//============================================================================= +// +// wallscan_np2 +// +// This is a wrapper around wallscan that helps it tile textures whose heights +// are not powers of 2. It divides the wall into texture-sized strips and calls +// wallscan for each of those. Since only one repetition of the texture fits +// in each strip, wallscan will not tile. +// +//============================================================================= + +void wallscan_np2(int x1, int x2, short *uwal, short *dwal, float *swal, fixed_t *lwal, double yrepeat, double top, double bot, bool mask) +{ + if (!r_np2) + { + call_wallscan(x1, x2, uwal, dwal, swal, lwal, yrepeat, mask); + } + else + { + short most1[MAXWIDTH], most2[MAXWIDTH], most3[MAXWIDTH]; + short *up, *down; + double texheight = rw_pic->GetHeight(); + double partition; + double scaledtexheight = texheight / yrepeat; + + if (yrepeat >= 0) + { // normal orientation: draw strips from top to bottom + partition = top - fmod(top - dc_texturemid / yrepeat - ViewPos.Z, scaledtexheight); + if (partition == top) + { + partition -= scaledtexheight; + } + up = uwal; + down = most1; + dc_texturemid = (partition - ViewPos.Z) * yrepeat + texheight; + while (partition > bot) + { + int j = OWallMost(most3, partition - ViewPos.Z, &WallC); + if (j != 3) + { + for (int j = x1; j < x2; ++j) + { + down[j] = clamp(most3[j], up[j], dwal[j]); + } + call_wallscan(x1, x2, up, down, swal, lwal, yrepeat, mask); + up = down; + down = (down == most1) ? most2 : most1; + } + partition -= scaledtexheight; + dc_texturemid -= texheight; + } + call_wallscan(x1, x2, up, dwal, swal, lwal, yrepeat, mask); + } + else + { // upside down: draw strips from bottom to top + partition = bot - fmod(bot - dc_texturemid / yrepeat - ViewPos.Z, scaledtexheight); + up = most1; + down = dwal; + dc_texturemid = (partition - ViewPos.Z) * yrepeat + texheight; + while (partition < top) + { + int j = OWallMost(most3, partition - ViewPos.Z, &WallC); + if (j != 12) + { + for (int j = x1; j < x2; ++j) + { + up[j] = clamp(most3[j], uwal[j], down[j]); + } + call_wallscan(x1, x2, up, down, swal, lwal, yrepeat, mask); + down = up; + up = (up == most1) ? most2 : most1; + } + partition -= scaledtexheight; + dc_texturemid -= texheight; + } + call_wallscan(x1, x2, uwal, down, swal, lwal, yrepeat, mask); + } + } +} + +static void wallscan_np2_ds(drawseg_t *ds, int x1, int x2, short *uwal, short *dwal, float *swal, fixed_t *lwal, double yrepeat) +{ + if (rw_pic->GetHeight() != 1 << rw_pic->HeightBits) + { + double frontcz1 = ds->curline->frontsector->ceilingplane.ZatPoint(ds->curline->v1); + double frontfz1 = ds->curline->frontsector->floorplane.ZatPoint(ds->curline->v1); + double frontcz2 = ds->curline->frontsector->ceilingplane.ZatPoint(ds->curline->v2); + double frontfz2 = ds->curline->frontsector->floorplane.ZatPoint(ds->curline->v2); + double top = MAX(frontcz1, frontcz2); + double bot = MIN(frontfz1, frontfz2); + if (fake3D & FAKE3D_CLIPTOP) + { + top = MIN(top, sclipTop); + } + if (fake3D & FAKE3D_CLIPBOTTOM) + { + bot = MAX(bot, sclipBottom); + } + wallscan_np2(x1, x2, uwal, dwal, swal, lwal, yrepeat, top, bot, true); + } + else + { + call_wallscan(x1, x2, uwal, dwal, swal, lwal, yrepeat, true); + } +} + +inline fixed_t mvline1 (fixed_t vince, BYTE *colormap, int count, fixed_t vplce, const BYTE *bufplce, BYTE *dest) +{ + dc_iscale = vince; + dc_colormap = colormap; + dc_count = count; + dc_texturefrac = vplce; + dc_source = bufplce; + dc_dest = dest; + return domvline1 (); +} + +void maskwallscan (int x1, int x2, short *uwal, short *dwal, float *swal, fixed_t *lwal, + double yrepeat, const BYTE *(*getcol)(FTexture *tex, int x)) +{ + int x, fracbits; + BYTE *p; + int y1ve[4], y2ve[4], u4, d4, startx, dax, z; + char bad; + float light = rw_light - rw_lightstep; + SDWORD xoffset; + BYTE *basecolormapdata; + double iscale; + + if (rw_pic->UseType == FTexture::TEX_Null) + { + return; + } + + if (!rw_pic->bMasked) + { // Textures that aren't masked can use the faster wallscan. + wallscan (x1, x2, uwal, dwal, swal, lwal, yrepeat, getcol); + return; + } + +//extern cycle_t WallScanCycles; +//clock (WallScanCycles); + + rw_pic->GetHeight(); // Make sure texture size is loaded + fracbits = 32- rw_pic->HeightBits; + setupmvline(fracbits); + xoffset = rw_offset; + basecolormapdata = basecolormap->Maps; + + x = startx = x1; + p = x + dc_destorg; + + bool fixed = (fixedcolormap != NULL || fixedlightlev >= 0); + if (fixed) + { + palookupoffse[0] = dc_colormap; + palookupoffse[1] = dc_colormap; + palookupoffse[2] = dc_colormap; + palookupoffse[3] = dc_colormap; + } + + for(; (x < x2) && ((size_t)p & 3); ++x, ++p) + { + light += rw_lightstep; + y1ve[0] = uwal[x];//max(uwal[x],umost[x]); + y2ve[0] = dwal[x];//min(dwal[x],dmost[x]); + if (y2ve[0] <= y1ve[0]) continue; + + if (!fixed) + { // calculate lighting + dc_colormap = basecolormapdata + (GETPALOOKUP (light, wallshade) << COLORMAPSHIFT); + } + + dc_source = getcol (rw_pic, (lwal[x] + xoffset) >> FRACBITS); + dc_dest = ylookup[y1ve[0]] + p; + dc_count = y2ve[0] - y1ve[0]; + iscale = swal[x] * yrepeat; + dc_iscale = xs_ToFixed(fracbits, iscale); + dc_texturefrac = xs_ToFixed(fracbits, dc_texturemid + iscale * (y1ve[0] - CenterY + 1)); + + domvline1(); + } + + for(; x < x2-3; x += 4, p+= 4) + { + bad = 0; + for (z = 3, dax = x+3; z >= 0; --z, --dax) + { + y1ve[z] = uwal[dax]; + y2ve[z] = dwal[dax]; + if (y2ve[z] <= y1ve[z]) { bad += 1<> FRACBITS); + iscale = swal[dax] * yrepeat; + vince[z] = xs_ToFixed(fracbits, iscale); + vplce[z] = xs_ToFixed(fracbits, dc_texturemid + iscale * (y1ve[z] - CenterY + 1)); + } + if (bad == 15) + { + light += rw_lightstep * 4; + continue; + } + + if (!fixed) + { + for (z = 0; z < 4; ++z) + { + light += rw_lightstep; + palookupoffse[z] = basecolormapdata + (GETPALOOKUP (light, wallshade) << COLORMAPSHIFT); + } + } + + u4 = MAX(MAX(y1ve[0],y1ve[1]),MAX(y1ve[2],y1ve[3])); + d4 = MIN(MIN(y2ve[0],y2ve[1]),MIN(y2ve[2],y2ve[3])); + + if ((bad != 0) || (u4 >= d4)) + { + for (z = 0; z < 4; ++z) + { + if (!(bad & 1)) + { + mvline1(vince[z],palookupoffse[z],y2ve[z]-y1ve[z],vplce[z],bufplce[z],ylookup[y1ve[z]]+p+z); + } + bad >>= 1; + } + continue; + } + + for (z = 0; z < 4; ++z) + { + if (u4 > y1ve[z]) + { + vplce[z] = mvline1(vince[z],palookupoffse[z],u4-y1ve[z],vplce[z],bufplce[z],ylookup[y1ve[z]]+p+z); + } + } + + if (d4 > u4) + { + dc_count = d4-u4; + dc_dest = ylookup[u4]+p; + domvline4(); + } + + BYTE *i = p+ylookup[d4]; + for (z = 0; z < 4; ++z) + { + if (y2ve[z] > d4) + { + mvline1(vince[z],palookupoffse[0],y2ve[z]-d4,vplce[z],bufplce[z],i+z); + } + } + } + for(; x < x2; ++x, ++p) + { + light += rw_lightstep; + y1ve[0] = uwal[x]; + y2ve[0] = dwal[x]; + if (y2ve[0] <= y1ve[0]) continue; + + if (!fixed) + { // calculate lighting + dc_colormap = basecolormapdata + (GETPALOOKUP (light, wallshade) << COLORMAPSHIFT); + } + + dc_source = getcol (rw_pic, (lwal[x] + xoffset) >> FRACBITS); + dc_dest = ylookup[y1ve[0]] + p; + dc_count = y2ve[0] - y1ve[0]; + iscale = swal[x] * yrepeat; + dc_iscale = xs_ToFixed(fracbits, iscale); + dc_texturefrac = xs_ToFixed(fracbits, dc_texturemid + iscale * (y1ve[0] - CenterY + 1)); + + domvline1(); + } + +//unclock(WallScanCycles); + + NetUpdate (); +} + +inline void preptmvline1 (fixed_t vince, BYTE *colormap, int count, fixed_t vplce, const BYTE *bufplce, BYTE *dest) +{ + dc_iscale = vince; + dc_colormap = colormap; + dc_count = count; + dc_texturefrac = vplce; + dc_source = bufplce; + dc_dest = dest; +} + +void transmaskwallscan (int x1, int x2, short *uwal, short *dwal, float *swal, fixed_t *lwal, + double yrepeat, const BYTE *(*getcol)(FTexture *tex, int x)) +{ + fixed_t (*tmvline1)(); + void (*tmvline4)(); + int x, fracbits; + BYTE *p; + int y1ve[4], y2ve[4], u4, d4, startx, dax, z; + char bad; + float light = rw_light - rw_lightstep; + SDWORD xoffset; + BYTE *basecolormapdata; + double iscale; + + if (rw_pic->UseType == FTexture::TEX_Null) + { + return; + } + + if (!R_GetTransMaskDrawers (&tmvline1, &tmvline4)) + { + // The current translucency is unsupported, so draw with regular maskwallscan instead. + maskwallscan (x1, x2, uwal, dwal, swal, lwal, yrepeat, getcol); + return; + } + +//extern cycle_t WallScanCycles; +//clock (WallScanCycles); + + rw_pic->GetHeight(); // Make sure texture size is loaded + fracbits = 32 - rw_pic->HeightBits; + setuptmvline(fracbits); + xoffset = rw_offset; + basecolormapdata = basecolormap->Maps; + fixed_t centeryfrac = FLOAT2FIXED(CenterY); + + x = startx = x1; + p = x + dc_destorg; + + bool fixed = (fixedcolormap != NULL || fixedlightlev >= 0); + if (fixed) + { + palookupoffse[0] = dc_colormap; + palookupoffse[1] = dc_colormap; + palookupoffse[2] = dc_colormap; + palookupoffse[3] = dc_colormap; + } + + for(; (x < x2) && ((size_t)p & 3); ++x, ++p) + { + light += rw_lightstep; + y1ve[0] = uwal[x];//max(uwal[x],umost[x]); + y2ve[0] = dwal[x];//min(dwal[x],dmost[x]); + if (y2ve[0] <= y1ve[0]) continue; + + if (!fixed) + { // calculate lighting + dc_colormap = basecolormapdata + (GETPALOOKUP (light, wallshade) << COLORMAPSHIFT); + } + + dc_source = getcol (rw_pic, (lwal[x] + xoffset) >> FRACBITS); + dc_dest = ylookup[y1ve[0]] + p; + dc_count = y2ve[0] - y1ve[0]; + iscale = swal[x] * yrepeat; + dc_iscale = xs_ToFixed(fracbits, iscale); + dc_texturefrac = xs_ToFixed(fracbits, dc_texturemid + iscale * (y1ve[0] - CenterY + 1)); + + tmvline1(); + } + + for(; x < x2-3; x += 4, p+= 4) + { + bad = 0; + for (z = 3, dax = x+3; z >= 0; --z, --dax) + { + y1ve[z] = uwal[dax]; + y2ve[z] = dwal[dax]; + if (y2ve[z] <= y1ve[z]) { bad += 1<> FRACBITS); + iscale = swal[dax] * yrepeat; + vince[z] = xs_ToFixed(fracbits, iscale); + vplce[z] = xs_ToFixed(fracbits, dc_texturemid + vince[z] * (y1ve[z] - CenterY + 1)); + } + if (bad == 15) + { + light += rw_lightstep * 4; + continue; + } + + if (!fixed) + { + for (z = 0; z < 4; ++z) + { + light += rw_lightstep; + palookupoffse[z] = basecolormapdata + (GETPALOOKUP (light, wallshade) << COLORMAPSHIFT); + } + } + + u4 = MAX(MAX(y1ve[0],y1ve[1]),MAX(y1ve[2],y1ve[3])); + d4 = MIN(MIN(y2ve[0],y2ve[1]),MIN(y2ve[2],y2ve[3])); + + if ((bad != 0) || (u4 >= d4)) + { + for (z = 0; z < 4; ++z) + { + if (!(bad & 1)) + { + preptmvline1(vince[z],palookupoffse[z],y2ve[z]-y1ve[z],vplce[z],bufplce[z],ylookup[y1ve[z]]+p+z); + tmvline1(); + } + bad >>= 1; + } + continue; + } + + for (z = 0; z < 4; ++z) + { + if (u4 > y1ve[z]) + { + preptmvline1(vince[z],palookupoffse[z],u4-y1ve[z],vplce[z],bufplce[z],ylookup[y1ve[z]]+p+z); + vplce[z] = tmvline1(); + } + } + + if (d4 > u4) + { + dc_count = d4-u4; + dc_dest = ylookup[u4]+p; + tmvline4(); + } + + BYTE *i = p+ylookup[d4]; + for (z = 0; z < 4; ++z) + { + if (y2ve[z] > d4) + { + preptmvline1(vince[z],palookupoffse[0],y2ve[z]-d4,vplce[z],bufplce[z],i+z); + tmvline1(); + } + } + } + for(; x < x2; ++x, ++p) + { + light += rw_lightstep; + y1ve[0] = uwal[x]; + y2ve[0] = dwal[x]; + if (y2ve[0] <= y1ve[0]) continue; + + if (!fixed) + { // calculate lighting + dc_colormap = basecolormapdata + (GETPALOOKUP (light, wallshade) << COLORMAPSHIFT); + } + + dc_source = getcol (rw_pic, (lwal[x] + xoffset) >> FRACBITS); + dc_dest = ylookup[y1ve[0]] + p; + dc_count = y2ve[0] - y1ve[0]; + iscale = swal[x] * yrepeat; + dc_iscale = xs_ToFixed(fracbits, iscale); + dc_texturefrac = xs_ToFixed(fracbits, dc_texturemid + iscale * (y1ve[0] - CenterY + 1)); + + tmvline1(); + } + +//unclock(WallScanCycles); + + NetUpdate (); +} + // // R_RenderSegLoop // Draws zero, one, or two textures for walls. // Can draw or mark the starting pixel of floor and ceiling textures. // CALLED: CORE LOOPING ROUTINE. // +// [RH] Rewrote this to use Build's wallscan, so it's quite far +// removed from the original Doom routine. +// void R_RenderSegLoop () { @@ -1060,9 +1784,9 @@ void R_RenderSegLoop () fixed_t xoffset = rw_offset; if (fixedlightlev >= 0) - R_SetColorMapLight((r_fullbrightignoresectorcolor) ? &FullNormalLight : basecolormap, 0, FIXEDLIGHT2SHADE(fixedlightlev)); + dc_colormap = basecolormap->Maps + fixedlightlev; else if (fixedcolormap != NULL) - R_SetColorMapLight(fixedcolormap, 0, 0); + dc_colormap = fixedcolormap; // clip wall to the floor and ceiling for (x = x1; x < x2; ++x) @@ -1164,10 +1888,17 @@ void R_RenderSegLoop () { rw_offset = -rw_offset; } - R_DrawWallSegment(rw_pic, x1, x2, walltop, wallbottom, swall, lwall, yscale, MAX(rw_frontcz1, rw_frontcz2), MIN(rw_frontfz1, rw_frontfz2), false); + if (rw_pic->GetHeight() != 1 << rw_pic->HeightBits) + { + wallscan_np2(x1, x2, walltop, wallbottom, swall, lwall, yscale, MAX(rw_frontcz1, rw_frontcz2), MIN(rw_frontfz1, rw_frontfz2), false); + } + else + { + call_wallscan(x1, x2, walltop, wallbottom, swall, lwall, yscale, false); + } } - fillshort (ceilingclip+x1, x2-x1, viewheight); - fillshort (floorclip+x1, x2-x1, 0xffff); + clearbufshort (ceilingclip+x1, x2-x1, viewheight); + clearbufshort (floorclip+x1, x2-x1, 0xffff); } else { // two sided line @@ -1200,7 +1931,14 @@ void R_RenderSegLoop () { rw_offset = -rw_offset; } - R_DrawWallSegment(rw_pic, x1, x2, walltop, wallupper, swall, lwall, yscale, MAX(rw_frontcz1, rw_frontcz2), MIN(rw_backcz1, rw_backcz2), false); + if (rw_pic->GetHeight() != 1 << rw_pic->HeightBits) + { + wallscan_np2(x1, x2, walltop, wallupper, swall, lwall, yscale, MAX(rw_frontcz1, rw_frontcz2), MIN(rw_backcz1, rw_backcz2), false); + } + else + { + call_wallscan(x1, x2, walltop, wallupper, swall, lwall, yscale, false); + } } memcpy (ceilingclip+x1, wallupper+x1, (x2-x1)*sizeof(short)); } @@ -1239,7 +1977,14 @@ void R_RenderSegLoop () { rw_offset = -rw_offset; } - R_DrawWallSegment(rw_pic, x1, x2, walllower, wallbottom, swall, lwall, yscale, MAX(rw_backfz1, rw_backfz2), MIN(rw_frontfz1, rw_frontfz2), false); + if (rw_pic->GetHeight() != 1 << rw_pic->HeightBits) + { + wallscan_np2(x1, x2, walllower, wallbottom, swall, lwall, yscale, MAX(rw_backfz1, rw_backfz2), MIN(rw_frontfz1, rw_frontfz2), false); + } + else + { + call_wallscan(x1, x2, walllower, wallbottom, swall, lwall, yscale, false); + } } memcpy (floorclip+x1, walllower+x1, (x2-x1)*sizeof(short)); } @@ -1267,9 +2012,9 @@ void R_NewWall (bool needlights) midtexture = toptexture = bottomtexture = 0; if (sidedef == linedef->sidedef[0] && - (linedef->special == Line_Mirror && r_drawmirrors)) // [ZZ] compatibility with r_drawmirrors cvar that existed way before portals + (linedef->isVisualPortal() || (linedef->special == Line_Mirror && r_drawmirrors))) // [ZZ] compatibility with r_drawmirrors cvar that existed way before portals { - markfloor = markceiling = true; // act like a one-sided wall here (todo: check how does this work with transparency) + markfloor = markceiling = true; // act like an one-sided wall here (todo: check how does this work with transparency) rw_markportal = true; } else if (backsector == NULL) @@ -1278,11 +2023,7 @@ void R_NewWall (bool needlights) // a single sided line is terminal, so it must mark ends markfloor = markceiling = true; // [RH] Horizon lines do not need to be textured - if (linedef->isVisualPortal()) - { - rw_markportal = true; - } - else if (linedef->special != Line_Horizon) + if (linedef->special != Line_Horizon) { midtexture = TexMan(sidedef->GetTexture(side_t::mid), true); rw_offset_mid = FLOAT2FIXED(sidedef->GetTextureXOffset(side_t::mid)); @@ -1350,19 +2091,15 @@ void R_NewWall (bool needlights) // wall but nothing to draw for it. // Recalculate walltop so that the wall is clipped by the back sector's // ceiling instead of the front sector's ceiling. - R_CreateWallSegmentYSloped (walltop, backsector->ceilingplane, &WallC); + WallMost (walltop, backsector->ceilingplane, &WallC); } // Putting sky ceilings on the front and back of a line alters the way unpegged // positioning works. rw_frontlowertop = backsector->GetPlaneTexZ(sector_t::ceiling); } - if (linedef->isVisualPortal()) - { - markceiling = markfloor = true; - } - else if ((rw_backcz1 <= rw_frontfz1 && rw_backcz2 <= rw_frontfz2) || - (rw_backfz1 >= rw_frontcz1 && rw_backfz2 >= rw_frontcz2)) + if ((rw_backcz1 <= rw_frontfz1 && rw_backcz2 <= rw_frontfz2) || + (rw_backfz1 >= rw_frontcz1 && rw_backfz2 >= rw_frontcz2)) { // closed door markceiling = markfloor = true; @@ -1519,7 +2256,6 @@ void R_NewWall (bool needlights) rw_bottomtexturemid += rowoffset; } } - rw_markportal = linedef->isVisualPortal(); } // if a floor / ceiling plane is on the wrong side of the view plane, @@ -1592,7 +2328,7 @@ void R_CheckDrawSegs () firstdrawseg = drawsegs + firstofs; ds_p = drawsegs + MaxDrawSegs; MaxDrawSegs = newdrawsegs; - DPrintf (DMSG_NOTIFY, "MaxDrawSegs increased to %zu\n", MaxDrawSegs); + DPrintf ("MaxDrawSegs increased to %zu\n", MaxDrawSegs); } } @@ -1603,7 +2339,6 @@ void R_CheckDrawSegs () ptrdiff_t R_NewOpening (ptrdiff_t len) { ptrdiff_t res = lastopening; - len = (len + 1) & ~1; // only return DWORD aligned addresses because some code stores fixed_t's and floats in openings... lastopening += len; if ((size_t)lastopening > maxopenings) { @@ -1611,7 +2346,7 @@ ptrdiff_t R_NewOpening (ptrdiff_t len) maxopenings = maxopenings ? maxopenings*2 : 16384; while ((size_t)lastopening > maxopenings); openings = (short *)M_Realloc (openings, maxopenings * sizeof(*openings)); - DPrintf (DMSG_NOTIFY, "MaxOpenings increased to %zu\n", maxopenings); + DPrintf ("MaxOpenings increased to %zu\n", maxopenings); } return res; } @@ -1676,7 +2411,7 @@ void R_StoreWallRange (int start, int stop) { ds_p->sprtopclip = R_NewOpening (stop - start); ds_p->sprbottomclip = R_NewOpening (stop - start); - fillshort (openings + ds_p->sprtopclip, stop-start, viewheight); + clearbufshort (openings + ds_p->sprtopclip, stop-start, viewheight); memset (openings + ds_p->sprbottomclip, -1, (stop-start)*sizeof(short)); ds_p->silhouette = SIL_BOTH; } @@ -1716,7 +2451,7 @@ void R_StoreWallRange (int start, int stop) if (doorclosed || (rw_backfz1 >= rw_frontcz1 && rw_backfz2 >= rw_frontcz2)) { // killough 1/17/98, 2/8/98 ds_p->sprtopclip = R_NewOpening (stop - start); - fillshort (openings + ds_p->sprtopclip, stop - start, viewheight); + clearbufshort (openings + ds_p->sprtopclip, stop - start, viewheight); ds_p->silhouette |= SIL_TOP; } } @@ -1764,14 +2499,13 @@ void R_StoreWallRange (int start, int stop) if(sidedef->GetTexture(side_t::mid).isValid()) ds_p->bFakeBoundary |= 4; // it is also mid texture - // note: This should never have used the openings array to store its data! ds_p->maskedtexturecol = R_NewOpening ((stop - start) * 2); ds_p->swall = R_NewOpening ((stop - start) * 2); lwal = (fixed_t *)(openings + ds_p->maskedtexturecol); swal = (float *)(openings + ds_p->swall); FTexture *pic = TexMan(sidedef->GetTexture(side_t::mid), true); - double yscale = pic->Scale.Y * sidedef->GetTextureYScale(side_t::mid); + double yscale = pic->Scale.X * sidedef->GetTextureYScale(side_t::mid); fixed_t xoffset = FLOAT2FIXED(sidedef->GetTextureXOffset(side_t::mid)); if (pic->bWorldPanning) @@ -1925,191 +2659,360 @@ void R_StoreWallRange (int start, int stop) ds_p++; } -int R_CreateWallSegmentY(short *outbuf, double z1, double z2, const FWallCoords *wallc) +int OWallMost (short *mostbuf, double z, const FWallCoords *wallc) { - float y1 = (float)(CenterY - z1 * InvZtoScale / wallc->sz1); - float y2 = (float)(CenterY - z2 * InvZtoScale / wallc->sz2); + int bad, ix1, ix2; + double y, iy1, iy2; + double s1, s2, s3, s4; - if (y1 < 0 && y2 < 0) // entire line is above screen + z = -z; + s1 = globaluclip * wallc->sz1; s2 = globaluclip * wallc->sz2; + s3 = globaldclip * wallc->sz1; s4 = globaldclip * wallc->sz2; + bad = (zs3)<<2)+((z>s4)<<3); + +#if 1 + if ((bad&3) == 3) { - memset(&outbuf[wallc->sx1], 0, (wallc->sx2 - wallc->sx1) * sizeof(outbuf[0])); - return 3; - } - else if (y1 > viewheight && y2 > viewheight) // entire line is below screen - { - fillshort(&outbuf[wallc->sx1], wallc->sx2 - wallc->sx1, viewheight); - return 12; + memset (&mostbuf[wallc->sx1], 0, (wallc->sx2 - wallc->sx1)*sizeof(mostbuf[0])); + return bad; } - if (wallc->sx2 <= wallc->sx1) - return 0; - - float rcp_delta = 1.0f / (wallc->sx2 - wallc->sx1); - if (y1 >= 0.0f && y2 >= 0.0f && xs_RoundToInt(y1) <= viewheight && xs_RoundToInt(y2) <= viewheight) + if ((bad&12) == 12) { - for (int x = wallc->sx1; x < wallc->sx2; x++) + clearbufshort (&mostbuf[wallc->sx1], wallc->sx2 - wallc->sx1, viewheight); + return bad; + } +#endif + ix1 = wallc->sx1; iy1 = wallc->sz1; + ix2 = wallc->sx2; iy2 = wallc->sz2; +#if 1 + if (bad & 3) + { + double t = (z-s1) / (s2-s1); + double inty = wallc->sz1 + t * (wallc->sz2 - wallc->sz1); + int xcross = xs_RoundToInt(wallc->sx1 + (t * wallc->sz2 * (wallc->sx2 - wallc->sx1)) / inty); + + if ((bad & 3) == 2) { - float t = (x - wallc->sx1) * rcp_delta; - float y = y1 * (1.0f - t) + y2 * t; - outbuf[x] = (short)xs_RoundToInt(y); - } - } - else - { - for (int x = wallc->sx1; x < wallc->sx2; x++) - { - float t = (x - wallc->sx1) * rcp_delta; - float y = y1 * (1.0f - t) + y2 * t; - outbuf[x] = (short)clamp(xs_RoundToInt(y), 0, viewheight); - } - } - - return 0; -} - -int R_CreateWallSegmentYSloped(short *outbuf, const secplane_t &plane, const FWallCoords *wallc) -{ - if (!plane.isSlope()) - { - return R_CreateWallSegmentY(outbuf, plane.Zat0() - ViewPos.Z, wallc); - } - else - { - // Get Z coordinates at both ends of the line - double x, y, den, z1, z2; - if (MirrorFlags & RF_XFLIP) - { - x = curline->v2->fX(); - y = curline->v2->fY(); - if (wallc->sx1 == 0 && 0 != (den = wallc->tleft.X - wallc->tright.X + wallc->tleft.Y - wallc->tright.Y)) - { - double frac = (wallc->tleft.Y + wallc->tleft.X) / den; - x -= frac * (x - curline->v1->fX()); - y -= frac * (y - curline->v1->fY()); - } - z1 = plane.ZatPoint(x, y) - ViewPos.Z; - - if (wallc->sx2 > wallc->sx1 + 1) - { - x = curline->v1->fX(); - y = curline->v1->fY(); - if (wallc->sx2 == viewwidth && 0 != (den = wallc->tleft.X - wallc->tright.X - wallc->tleft.Y + wallc->tright.Y)) - { - double frac = (wallc->tright.Y - wallc->tright.X) / den; - x += frac * (curline->v2->fX() - x); - y += frac * (curline->v2->fY() - y); - } - z2 = plane.ZatPoint(x, y) - ViewPos.Z; - } - else - { - z2 = z1; - } + if (wallc->sx1 <= xcross) { iy2 = inty; ix2 = xcross; } + if (wallc->sx2 > xcross) memset (&mostbuf[xcross], 0, (wallc->sx2-xcross)*sizeof(mostbuf[0])); } else + { + if (xcross <= wallc->sx2) { iy1 = inty; ix1 = xcross; } + if (xcross > wallc->sx1) memset (&mostbuf[wallc->sx1], 0, (xcross-wallc->sx1)*sizeof(mostbuf[0])); + } + } + + if (bad & 12) + { + double t = (z-s3) / (s4-s3); + double inty = wallc->sz1 + t * (wallc->sz2 - wallc->sz1); + int xcross = xs_RoundToInt(wallc->sx1 + (t * wallc->sz2 * (wallc->sx2 - wallc->sx1)) / inty); + + if ((bad & 12) == 8) + { + if (wallc->sx1 <= xcross) { iy2 = inty; ix2 = xcross; } + if (wallc->sx2 > xcross) clearbufshort (&mostbuf[xcross], wallc->sx2 - xcross, viewheight); + } + else + { + if (xcross <= wallc->sx2) { iy1 = inty; ix1 = xcross; } + if (xcross > wallc->sx1) clearbufshort (&mostbuf[wallc->sx1], xcross - wallc->sx1, viewheight); + } + } + + y = z * InvZtoScale / iy1; + if (ix2 == ix1) + { + mostbuf[ix1] = (short)xs_RoundToInt(y + CenterY); + } + else + { + fixed_t yinc = FLOAT2FIXED(((z * InvZtoScale / iy2) - y) / (ix2 - ix1)); + qinterpolatedown16short (&mostbuf[ix1], ix2-ix1, FLOAT2FIXED(y + CenterY), yinc); + } +#else + double max = viewheight; + double zz = z / 65536.0; +#if 0 + double z1 = zz * InvZtoScale / wallc->sz1; + double z2 = zz * InvZtoScale / wallc->sz2 - z1; + z2 /= (wallc->sx2 - wallc->sx1); + z1 += centeryfrac / 65536.0; + + for (int x = wallc->sx1; x < wallc->sx2; ++x) + { + mostbuf[x] = xs_RoundToInt(clamp(z1, 0.0, max)); + z1 += z2; + } +#else + double top, bot, i; + + i = wallc->sx1 - centerx; + top = WallT.UoverZorg + WallT.UoverZstep * i; + bot = WallT.InvZorg + WallT.InvZstep * i; + double cy = centeryfrac / 65536.0; + + for (int x = wallc->sx1; x < wallc->sx2; x++) + { + double frac = top / bot; + double scale = frac * WallT.DepthScale + WallT.DepthOrg; + mostbuf[x] = xs_RoundToInt(clamp(zz / scale + cy, 0.0, max)); + top += WallT.UoverZstep; + bot += WallT.InvZstep; + } +#endif +#endif + return bad; +} + +int WallMost (short *mostbuf, const secplane_t &plane, const FWallCoords *wallc) +{ + if (!plane.isSlope()) + { + return OWallMost(mostbuf, plane.Zat0() - ViewPos.Z, wallc); + } + + double x, y, den, z1, z2, oz1, oz2; + double s1, s2, s3, s4; + int bad, ix1, ix2; + double iy1, iy2; + + if (MirrorFlags & RF_XFLIP) + { + x = curline->v2->fX(); + y = curline->v2->fY(); + if (wallc->sx1 == 0 && 0 != (den = wallc->tleft.X - wallc->tright.X + wallc->tleft.Y - wallc->tright.Y)) + { + double frac = (wallc->tleft.Y + wallc->tleft.X) / den; + x -= frac * (x - curline->v1->fX()); + y -= frac * (y - curline->v1->fY()); + } + z1 = ViewPos.Z - plane.ZatPoint(x, y); + + if (wallc->sx2 > wallc->sx1 + 1) { x = curline->v1->fX(); y = curline->v1->fY(); - if (wallc->sx1 == 0 && 0 != (den = wallc->tleft.X - wallc->tright.X + wallc->tleft.Y - wallc->tright.Y)) + if (wallc->sx2 == viewwidth && 0 != (den = wallc->tleft.X - wallc->tright.X - wallc->tleft.Y + wallc->tright.Y)) { - double frac = (wallc->tleft.Y + wallc->tleft.X) / den; + double frac = (wallc->tright.Y - wallc->tright.X) / den; x += frac * (curline->v2->fX() - x); y += frac * (curline->v2->fY() - y); } - z1 = plane.ZatPoint(x, y) - ViewPos.Z; + z2 = ViewPos.Z - plane.ZatPoint(x, y); + } + else + { + z2 = z1; + } + } + else + { + x = curline->v1->fX(); + y = curline->v1->fY(); + if (wallc->sx1 == 0 && 0 != (den = wallc->tleft.X - wallc->tright.X + wallc->tleft.Y - wallc->tright.Y)) + { + double frac = (wallc->tleft.Y + wallc->tleft.X) / den; + x += frac * (curline->v2->fX() - x); + y += frac * (curline->v2->fY() - y); + } + z1 = ViewPos.Z - plane.ZatPoint(x, y); - if (wallc->sx2 > wallc->sx1 + 1) + if (wallc->sx2 > wallc->sx1 + 1) + { + x = curline->v2->fX(); + y = curline->v2->fY(); + if (wallc->sx2 == viewwidth && 0 != (den = wallc->tleft.X - wallc->tright.X - wallc->tleft.Y + wallc->tright.Y)) { - x = curline->v2->fX(); - y = curline->v2->fY(); - if (wallc->sx2 == viewwidth && 0 != (den = wallc->tleft.X - wallc->tright.X - wallc->tleft.Y + wallc->tright.Y)) - { - double frac = (wallc->tright.Y - wallc->tright.X) / den; - x -= frac * (x - curline->v1->fX()); - y -= frac * (y - curline->v1->fY()); - } - z2 = plane.ZatPoint(x, y) - ViewPos.Z; + double frac = (wallc->tright.Y - wallc->tright.X) / den; + x -= frac * (x - curline->v1->fX()); + y -= frac * (y - curline->v1->fY()); + } + z2 = ViewPos.Z - plane.ZatPoint(x, y); + } + else + { + z2 = z1; + } + } + + s1 = globaluclip * wallc->sz1; s2 = globaluclip * wallc->sz2; + s3 = globaldclip * wallc->sz1; s4 = globaldclip * wallc->sz2; + bad = (z1s3)<<2)+((z2>s4)<<3); + + ix1 = wallc->sx1; ix2 = wallc->sx2; + iy1 = wallc->sz1; iy2 = wallc->sz2; + oz1 = z1; oz2 = z2; + + if ((bad&3) == 3) + { + memset (&mostbuf[ix1], -1, (ix2-ix1)*sizeof(mostbuf[0])); + return bad; + } + + if ((bad&12) == 12) + { + clearbufshort (&mostbuf[ix1], ix2-ix1, viewheight); + return bad; + + } + + if (bad&3) + { + //inty = intz / (globaluclip>>16) + double t = (oz1-s1) / (s2-s1+oz1-oz2); + double inty = wallc->sz1 + t * (wallc->sz2-wallc->sz1); + double intz = oz1 + t * (oz2-oz1); + int xcross = wallc->sx1 + xs_RoundToInt((t * wallc->sz2 * (wallc->sx2-wallc->sx1)) / inty); + + //t = divscale30((x1<<4)-xcross*yb1[w],xcross*(yb2[w]-yb1[w])-((x2-x1)<<4)); + //inty = yb1[w] + mulscale30(yb2[w]-yb1[w],t); + //intz = z1 + mulscale30(z2-z1,t); + + if ((bad&3) == 2) + { + if (wallc->sx1 <= xcross) { z2 = intz; iy2 = inty; ix2 = xcross; } + memset (&mostbuf[xcross], 0, (wallc->sx2-xcross)*sizeof(mostbuf[0])); + } + else + { + if (xcross <= wallc->sx2) { z1 = intz; iy1 = inty; ix1 = xcross; } + memset (&mostbuf[wallc->sx1], 0, (xcross-wallc->sx1)*sizeof(mostbuf[0])); + } + } + + if (bad&12) + { + //inty = intz / (globaldclip>>16) + double t = (oz1-s3) / (s4-s3+oz1-oz2); + double inty = wallc->sz1 + t * (wallc->sz2-wallc->sz1); + double intz = oz1 + t * (oz2-oz1); + int xcross = wallc->sx1 + xs_RoundToInt((t * wallc->sz2 * (wallc->sx2-wallc->sx1)) / inty); + + //t = divscale30((x1<<4)-xcross*yb1[w],xcross*(yb2[w]-yb1[w])-((x2-x1)<<4)); + //inty = yb1[w] + mulscale30(yb2[w]-yb1[w],t); + //intz = z1 + mulscale30(z2-z1,t); + + if ((bad&12) == 8) + { + if (wallc->sx1 <= xcross) { z2 = intz; iy2 = inty; ix2 = xcross; } + if (wallc->sx2 > xcross) clearbufshort (&mostbuf[xcross], wallc->sx2-xcross, viewheight); + } + else + { + if (xcross <= wallc->sx2) { z1 = intz; iy1 = inty; ix1 = xcross; } + if (xcross > wallc->sx1) clearbufshort (&mostbuf[wallc->sx1], xcross-wallc->sx1, viewheight); + } + } + + y = z1 * InvZtoScale / iy1; + if (ix2 == ix1) + { + mostbuf[ix1] = (short)xs_RoundToInt(y/65536.0 + CenterY); + } + else + { + fixed_t yinc = FLOAT2FIXED(((z2 * InvZtoScale / iy2) - y) / (ix2-ix1)); + qinterpolatedown16short (&mostbuf[ix1], ix2-ix1, FLOAT2FIXED(y/65536.0 + CenterY), yinc); + } + + return bad; +} + +static void PrepWallRoundFix(fixed_t *lwall, fixed_t walxrepeat, int x1, int x2) +{ + // fix for rounding errors + walxrepeat = abs(walxrepeat); + fixed_t fix = (MirrorFlags & RF_XFLIP) ? walxrepeat-1 : 0; + int x; + + if (x1 > 0) + { + for (x = x1; x < x2; x++) + { + if ((unsigned)lwall[x] >= (unsigned)walxrepeat) + { + lwall[x] = fix; } else { - z2 = z1; + break; } } - - return R_CreateWallSegmentY(outbuf, z1, z2, wallc); } -} - -void PrepWall(float *vstep, fixed_t *upos, double walxrepeat, int x1, int x2) -{ - float uOverZ = WallT.UoverZorg + WallT.UoverZstep * (float)(x1 + 0.5 - CenterX); - float invZ = WallT.InvZorg + WallT.InvZstep * (float)(x1 + 0.5 - CenterX); - float uGradient = WallT.UoverZstep; - float zGradient = WallT.InvZstep; - float xrepeat = (float)fabs(walxrepeat); - float depthScale = (float)(WallT.InvZstep * WallTMapScale2); - float depthOrg = (float)(-WallT.UoverZstep * WallTMapScale2); - - if (walxrepeat < 0.0) + fix = walxrepeat - 1 - fix; + for (x = x2-1; x >= x1; x--) { - for (int x = x1; x < x2; x++) + if ((unsigned)lwall[x] >= (unsigned)walxrepeat) { - float u = uOverZ / invZ; - - upos[x] = (fixed_t)((xrepeat - u * xrepeat) * FRACUNIT); - vstep[x] = depthOrg + u * depthScale; - - uOverZ += uGradient; - invZ += zGradient; + lwall[x] = fix; } - } - else - { - for (int x = x1; x < x2; x++) + else { - float u = uOverZ / invZ; - - upos[x] = (fixed_t)(u * xrepeat * FRACUNIT); - vstep[x] = depthOrg + u * depthScale; - - uOverZ += uGradient; - invZ += zGradient; + break; } } } -void PrepLWall(fixed_t *upos, double walxrepeat, int x1, int x2) -{ - float uOverZ = WallT.UoverZorg + WallT.UoverZstep * (float)(x1 + 0.5 - CenterX); - float invZ = WallT.InvZorg + WallT.InvZstep * (float)(x1 + 0.5 - CenterX); - float uGradient = WallT.UoverZstep; - float zGradient = WallT.InvZstep; - float xrepeat = (float)fabs(walxrepeat); +void PrepWall (float *swall, fixed_t *lwall, double walxrepeat, int x1, int x2) +{ // swall = scale, lwall = texturecolumn + double top, bot, i; + double xrepeat = fabs(walxrepeat * 65536); + double depth_scale = WallT.InvZstep * WallTMapScale2; + double depth_org = -WallT.UoverZstep * WallTMapScale2; - if (walxrepeat < 0.0f) + i = x1 - centerx; + top = WallT.UoverZorg + WallT.UoverZstep * i; + bot = WallT.InvZorg + WallT.InvZstep * i; + + for (int x = x1; x < x2; x++) { - for (int x = x1; x < x2; x++) + double frac = top / bot; + if (walxrepeat < 0) { - float u = uOverZ / invZ * xrepeat - xrepeat; - - upos[x] = (fixed_t)(u * FRACUNIT); - - uOverZ += uGradient; - invZ += zGradient; + lwall[x] = xs_RoundToInt(xrepeat - frac * xrepeat); } + else + { + lwall[x] = xs_RoundToInt(frac * xrepeat); + } + swall[x] = float(frac * depth_scale + depth_org); + top += WallT.UoverZstep; + bot += WallT.InvZstep; } - else + PrepWallRoundFix(lwall, FLOAT2FIXED(walxrepeat), x1, x2); +} + +void PrepLWall (fixed_t *lwall, double walxrepeat, int x1, int x2) +{ // lwall = texturecolumn + double top, bot, i; + double xrepeat = fabs(walxrepeat * 65536); + double topstep, botstep; + + i = x1 - centerx; + top = WallT.UoverZorg + WallT.UoverZstep * i; + bot = WallT.InvZorg + WallT.InvZstep * i; + + top *= xrepeat; + topstep = WallT.UoverZstep * xrepeat; + botstep = WallT.InvZstep; + + for (int x = x1; x < x2; x++) { - for (int x = x1; x < x2; x++) + if (walxrepeat < 0) { - float u = uOverZ / invZ * xrepeat; - - upos[x] = (fixed_t)(u * FRACUNIT); - - uOverZ += uGradient; - invZ += zGradient; + lwall[x] = xs_RoundToInt(xrepeat - top / bot); } + else + { + lwall[x] = xs_RoundToInt(top / bot); + } + top += topstep; + bot += botstep; } + PrepWallRoundFix(lwall, FLOAT2FIXED(walxrepeat), x1, x2); } // pass = 0: when seg is first drawn @@ -2301,13 +3204,13 @@ static void R_RenderDecal (side_t *wall, DBaseDecal *decal, drawseg_t *clipper, rereadcolormap = false; } - rw_light = rw_lightleft + (x1 - savecoord.sx1) * rw_lightstep; + rw_light = rw_lightleft + (x1 - WallC.sx1) * rw_lightstep; if (fixedlightlev >= 0) - R_SetColorMapLight((r_fullbrightignoresectorcolor) ? &FullNormalLight : usecolormap, 0, FIXEDLIGHT2SHADE(fixedlightlev)); + dc_colormap = usecolormap->Maps + fixedlightlev; else if (fixedcolormap != NULL) - R_SetColorMapLight(fixedcolormap, 0, 0); + dc_colormap = fixedcolormap; else if (!foggy && (decal->RenderFlags & RF_FULLBRIGHT)) - R_SetColorMapLight((r_fullbrightignoresectorcolor) ? &FullNormalLight : usecolormap, 0, 0); + dc_colormap = usecolormap->Maps; else calclighting = true; @@ -2358,9 +3261,9 @@ static void R_RenderDecal (side_t *wall, DBaseDecal *decal, drawseg_t *clipper, { if (calclighting) { // calculate lighting - R_SetColorMapLight(usecolormap, rw_light, wallshade); + dc_colormap = usecolormap->Maps + (GETPALOOKUP (rw_light, wallshade) << COLORMAPSHIFT); } - R_WallSpriteColumn (false); + R_WallSpriteColumn (R_DrawMaskedColumn); dc_x++; } @@ -2368,12 +3271,12 @@ static void R_RenderDecal (side_t *wall, DBaseDecal *decal, drawseg_t *clipper, { if (calclighting) { // calculate lighting - R_SetColorMapLight(usecolormap, rw_light, wallshade); + dc_colormap = usecolormap->Maps + (GETPALOOKUP (rw_light, wallshade) << COLORMAPSHIFT); } - rt_initcols(nullptr); + rt_initcols(); for (int zz = 4; zz; --zz) { - R_WallSpriteColumn (true); + R_WallSpriteColumn (R_DrawMaskedColumnHoriz); dc_x++; } rt_draw4cols (dc_x - 4); @@ -2383,9 +3286,9 @@ static void R_RenderDecal (side_t *wall, DBaseDecal *decal, drawseg_t *clipper, { if (calclighting) { // calculate lighting - R_SetColorMapLight(usecolormap, rw_light, wallshade); + dc_colormap = usecolormap->Maps + (GETPALOOKUP (rw_light, wallshade) << COLORMAPSHIFT); } - R_WallSpriteColumn (false); + R_WallSpriteColumn (R_DrawMaskedColumn); dc_x++; } } @@ -2406,5 +3309,3 @@ static void R_RenderDecal (side_t *wall, DBaseDecal *decal, drawseg_t *clipper, done: WallC = savecoord; } - -} diff --git a/src/r_segs.h b/src/r_segs.h new file mode 100644 index 000000000..1fc428c96 --- /dev/null +++ b/src/r_segs.h @@ -0,0 +1,73 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id:$ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This source is available for distribution and/or modification +// only under the terms of the DOOM Source Code License as +// published by id Software. All rights reserved. +// +// The source is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License +// for more details. +// +// DESCRIPTION: +// Refresh module, drawing LineSegs from BSP. +// +//----------------------------------------------------------------------------- + + +#ifndef __R_SEGS_H__ +#define __R_SEGS_H__ + +struct drawseg_t; + +void R_RenderMaskedSegRange (drawseg_t *ds, int x1, int x2); + +extern short *openings; +extern ptrdiff_t lastopening; +extern size_t maxopenings; + +int OWallMost (short *mostbuf, double z, const FWallCoords *wallc); +int WallMost (short *mostbuf, const secplane_t &plane, const FWallCoords *wallc); +void PrepWall (float *swall, fixed_t *lwall, double walxrepeat, int x1, int x2); +void PrepLWall (fixed_t *lwall, double walxrepeat, int x1, int x2); + +ptrdiff_t R_NewOpening (ptrdiff_t len); + +void R_CheckDrawSegs (); + +void R_RenderSegLoop (); + +extern float swall[MAXWIDTH]; +extern fixed_t lwall[MAXWIDTH]; +extern float rw_light; // [RH] Scale lights with viewsize adjustments +extern float rw_lightstep; +extern float rw_lightleft; +extern fixed_t rw_offset; + +/* portal structure, this is used in r_ code in order to store drawsegs with portals (and mirrors) */ +struct PortalDrawseg +{ + line_t* src; // source line (the one drawn) this doesn't change over render loops + line_t* dst; // destination line (the one that the portal is linked with, equals 'src' for mirrors) + + int x1; // drawseg x1 + int x2; // drawseg x2 + + int len; + TArray ceilingclip; + TArray floorclip; + + bool mirror; // true if this is a mirror (src should equal dst) +}; + +extern PortalDrawseg* CurrentPortal; +extern int CurrentPortalUniq; +extern bool CurrentPortalInSkybox; +extern TArray WallPortals; + +#endif diff --git a/src/r_things.cpp b/src/r_things.cpp new file mode 100644 index 000000000..b930a1e92 --- /dev/null +++ b/src/r_things.cpp @@ -0,0 +1,3202 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id:$ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This source is available for distribution and/or modification +// only under the terms of the DOOM Source Code License as +// published by id Software. All rights reserved. +// +// The source is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License +// for more details. +// +// $Log:$ +// +// DESCRIPTION: +// Refresh of things, i.e. objects represented by sprites. +// +// This file contains some code from the Build Engine. +// +// "Build Engine & Tools" Copyright (c) 1993-1997 Ken Silverman +// Ken Silverman's official web site: "http://www.advsys.net/ken" +// See the included license file "BUILDLIC.TXT" for license info. +// +//----------------------------------------------------------------------------- + +#include +#include +#include + +#include "p_lnspec.h" +#include "templates.h" +#include "doomdef.h" +#include "m_swap.h" +#include "i_system.h" +#include "w_wad.h" +#include "r_local.h" +#include "c_console.h" +#include "c_cvars.h" +#include "c_dispatch.h" +#include "doomstat.h" +#include "v_video.h" +#include "sc_man.h" +#include "s_sound.h" +#include "sbar.h" +#include "gi.h" +#include "r_sky.h" +#include "cmdlib.h" +#include "g_level.h" +#include "d_net.h" +#include "colormatcher.h" +#include "d_netinf.h" +#include "p_effect.h" +#include "r_bsp.h" +#include "r_plane.h" +#include "r_segs.h" +#include "r_3dfloors.h" +#include "v_palette.h" +#include "r_data/r_translate.h" +#include "r_data/colormaps.h" +#include "r_data/voxels.h" +#include "p_local.h" +#include "p_maputl.h" + +// [RH] A c-buffer. Used for keeping track of offscreen voxel spans. + +struct FCoverageBuffer +{ + struct Span + { + Span *NextSpan; + short Start, Stop; + }; + + FCoverageBuffer(int size); + ~FCoverageBuffer(); + + void Clear(); + void InsertSpan(int listnum, int start, int stop); + Span *AllocSpan(); + + FMemArena SpanArena; + Span **Spans; // [0..NumLists-1] span lists + Span *FreeSpans; + unsigned int NumLists; +}; + +extern double globaluclip, globaldclip; +extern float MaskedScaleY; + +#define MINZ double((2048*4) / double(1 << 20)) +#define BASEYCENTER (100) + +EXTERN_CVAR (Bool, st_scale) +EXTERN_CVAR(Bool, r_shadercolormaps) +EXTERN_CVAR(Int, r_drawfuzz) +EXTERN_CVAR(Bool, r_deathcamera); + +// +// Sprite rotation 0 is facing the viewer, +// rotation 1 is one angle turn CLOCKWISE around the axis. +// This is not the same as the angle, +// which increases counter clockwise (protractor). +// +double pspritexscale; +double pspritexiscale; +double pspriteyscale; +fixed_t sky1scale; // [RH] Sky 1 scale factor +fixed_t sky2scale; // [RH] Sky 2 scale factor + +vissprite_t *VisPSprites[NUMPSPRITES]; +int VisPSpritesX1[NUMPSPRITES]; +FDynamicColormap *VisPSpritesBaseColormap[NUMPSPRITES]; + +static int spriteshade; + +FTexture *WallSpriteTile; + +// constant arrays +// used for psprite clipping and initializing clipping +short zeroarray[MAXWIDTH]; +short screenheightarray[MAXWIDTH]; + +EXTERN_CVAR (Bool, r_drawplayersprites) +EXTERN_CVAR (Bool, r_drawvoxels) + +// +// INITIALIZATION FUNCTIONS +// + +int OffscreenBufferWidth, OffscreenBufferHeight; +BYTE *OffscreenColorBuffer; +FCoverageBuffer *OffscreenCoverageBuffer; + +// + +// GAME FUNCTIONS +// +int MaxVisSprites; +vissprite_t **vissprites; +vissprite_t **firstvissprite; +vissprite_t **vissprite_p; +vissprite_t **lastvissprite; +int newvissprite; +bool DrewAVoxel; + +static vissprite_t **spritesorter; +static int spritesortersize = 0; +static int vsprcount; + +static void R_ProjectWallSprite(AActor *thing, const DVector3 &pos, FTextureID picnum, const DVector2 &scale, INTBOOL flip); + + + +void R_DeinitSprites() +{ + // Free vissprites + for (int i = 0; i < MaxVisSprites; ++i) + { + delete vissprites[i]; + } + free (vissprites); + vissprites = NULL; + vissprite_p = lastvissprite = NULL; + MaxVisSprites = 0; + + // Free vissprites sorter + if (spritesorter != NULL) + { + delete[] spritesorter; + spritesortersize = 0; + spritesorter = NULL; + } + + // Free offscreen buffer + if (OffscreenColorBuffer != NULL) + { + delete[] OffscreenColorBuffer; + OffscreenColorBuffer = NULL; + } + if (OffscreenCoverageBuffer != NULL) + { + delete OffscreenCoverageBuffer; + OffscreenCoverageBuffer = NULL; + } + OffscreenBufferHeight = OffscreenBufferWidth = 0; +} + +// +// R_ClearSprites +// Called at frame start. +// +void R_ClearSprites (void) +{ + vissprite_p = firstvissprite; + DrewAVoxel = false; +} + + +// +// R_NewVisSprite +// +vissprite_t *R_NewVisSprite (void) +{ + if (vissprite_p == lastvissprite) + { + ptrdiff_t firstvisspritenum = firstvissprite - vissprites; + ptrdiff_t prevvisspritenum = vissprite_p - vissprites; + + MaxVisSprites = MaxVisSprites ? MaxVisSprites * 2 : 128; + vissprites = (vissprite_t **)M_Realloc (vissprites, MaxVisSprites * sizeof(vissprite_t)); + lastvissprite = &vissprites[MaxVisSprites]; + firstvissprite = &vissprites[firstvisspritenum]; + vissprite_p = &vissprites[prevvisspritenum]; + DPrintf ("MaxVisSprites increased to %d\n", MaxVisSprites); + + // Allocate sprites from the new pile + for (vissprite_t **p = vissprite_p; p < lastvissprite; ++p) + { + *p = new vissprite_t; + } + } + + vissprite_p++; + return *(vissprite_p-1); +} + +// +// R_DrawMaskedColumn +// Used for sprites and masked mid textures. +// Masked means: partly transparent, i.e. stored +// in posts/runs of opaque pixels. +// +short* mfloorclip; +short* mceilingclip; + +double spryscale; +double sprtopscreen; + +bool sprflipvert; + +void R_DrawMaskedColumn (const BYTE *column, const FTexture::Span *span) +{ + const fixed_t centeryfrac = FLOAT2FIXED(CenterY); + const fixed_t texturemid = FLOAT2FIXED(dc_texturemid); + while (span->Length != 0) + { + const int length = span->Length; + const int top = span->TopOffset; + + // calculate unclipped screen coordinates for post + dc_yl = xs_RoundToInt(sprtopscreen + spryscale * top); + dc_yh = xs_RoundToInt(sprtopscreen + spryscale * (top + length)) - 1; + + if (sprflipvert) + { + swapvalues (dc_yl, dc_yh); + } + + if (dc_yh >= mfloorclip[dc_x]) + { + dc_yh = mfloorclip[dc_x] - 1; + } + if (dc_yl < mceilingclip[dc_x]) + { + dc_yl = mceilingclip[dc_x]; + } + + if (dc_yl <= dc_yh) + { + if (sprflipvert) + { + dc_texturefrac = (dc_yl*dc_iscale) - (top << FRACBITS) + - FixedMul (centeryfrac, dc_iscale) - texturemid; + const fixed_t maxfrac = length << FRACBITS; + while (dc_texturefrac >= maxfrac) + { + if (++dc_yl > dc_yh) + goto nextpost; + dc_texturefrac += dc_iscale; + } + fixed_t endfrac = dc_texturefrac + (dc_yh-dc_yl)*dc_iscale; + while (endfrac < 0) + { + if (--dc_yh < dc_yl) + goto nextpost; + endfrac -= dc_iscale; + } + } + else + { + dc_texturefrac = texturemid - (top << FRACBITS) + + (dc_yl*dc_iscale) - FixedMul (centeryfrac-FRACUNIT, dc_iscale); + while (dc_texturefrac < 0) + { + if (++dc_yl > dc_yh) + goto nextpost; + dc_texturefrac += dc_iscale; + } + fixed_t endfrac = dc_texturefrac + (dc_yh-dc_yl)*dc_iscale; + const fixed_t maxfrac = length << FRACBITS; + if (dc_yh < mfloorclip[dc_x]-1 && endfrac < maxfrac - dc_iscale) + { + dc_yh++; + } + else while (endfrac >= maxfrac) + { + if (--dc_yh < dc_yl) + goto nextpost; + endfrac -= dc_iscale; + } + } + dc_source = column + top; + dc_dest = ylookup[dc_yl] + dc_x + dc_destorg; + dc_count = dc_yh - dc_yl + 1; + colfunc (); + } +nextpost: + span++; + } +} + +// [ZZ] +// R_ClipSpriteColumnWithPortals +// + +static TArray portaldrawsegs; + +static inline void R_CollectPortals() +{ + // This function collects all drawsegs that may be of interest to R_ClipSpriteColumnWithPortals + // Having that function over the entire list of drawsegs can break down performance quite drastically. + // This is doing the costly stuff only once so that R_ClipSpriteColumnWithPortals can + // a) exit early if no relevant info is found and + // b) skip most of the collected drawsegs which have no portal attached. + portaldrawsegs.Clear(); + for (drawseg_t* seg = ds_p; seg-- > firstdrawseg; ) // copied code from killough below + { + // I don't know what makes this happen (some old top-down portal code or possibly skybox code? something adds null lines...) + // crashes at the first frame of the first map of Action2.wad + if (!seg->curline) continue; + + line_t* line = seg->curline->linedef; + // ignore minisegs from GL nodes. + if (!line) continue; + + // check if this line will clip sprites to itself + if (!line->isVisualPortal() && line->special != Line_Mirror) + continue; + + // don't clip sprites with portal's back side (it's transparent) + if (seg->curline->sidedef != line->sidedef[0]) + continue; + + portaldrawsegs.Push(seg); + } +} + +static inline bool R_ClipSpriteColumnWithPortals(vissprite_t* spr) +{ + // [ZZ] 10.01.2016: don't clip sprites from the root of a skybox. + if (CurrentPortalInSkybox) + return false; + + for (drawseg_t *seg : portaldrawsegs) + { + // ignore segs from other portals + if (seg->CurrentPortalUniq != CurrentPortalUniq) + continue; + + // (all checks that are already done in R_CollectPortals have been removed for performance reasons.) + + // don't clip if the sprite is in front of the portal + if (!P_PointOnLineSidePrecise(spr->gpos.X, spr->gpos.Y, seg->curline->linedef)) + continue; + + // now if current column is covered by this drawseg, we clip it away + if ((dc_x >= seg->x1) && (dc_x < seg->x2)) + return true; + } + + return false; +} + + +// +// R_DrawVisSprite +// mfloorclip and mceilingclip should also be set. +// +void R_DrawVisSprite (vissprite_t *vis) +{ + const BYTE *pixels; + const FTexture::Span *spans; + fixed_t frac; + FTexture *tex; + int x2, stop4; + fixed_t xiscale; + ESPSResult mode; + bool ispsprite = (!vis->sector && vis->gpos != FVector3(0, 0, 0)); + + if (vis->xscale == 0 || vis->yscale == 0) + { // scaled to 0; can't see + return; + } + + fixed_t centeryfrac = FLOAT2FIXED(CenterY); + dc_colormap = vis->Style.colormap; + + mode = R_SetPatchStyle (vis->Style.RenderStyle, vis->Style.Alpha, vis->Translation, vis->FillColor); + + if (vis->Style.RenderStyle == LegacyRenderStyles[STYLE_Shaded]) + { // For shaded sprites, R_SetPatchStyle sets a dc_colormap to an alpha table, but + // it is the brightest one. We need to get back to the proper light level for + // this sprite. + dc_colormap += vis->ColormapNum << COLORMAPSHIFT; + } + + if (mode != DontDraw) + { + if (mode == DoDraw0) + { + // One column at a time + stop4 = vis->x1; + } + else // DoDraw1 + { + // Up to four columns at a time + stop4 = vis->x2 & ~3; + } + + tex = vis->pic; + spryscale = vis->yscale; + sprflipvert = false; + dc_iscale = FLOAT2FIXED(1 / vis->yscale); + frac = vis->startfrac; + xiscale = vis->xiscale; + dc_texturemid = vis->texturemid; + + if (vis->renderflags & RF_YFLIP) + { + sprflipvert = true; + spryscale = -spryscale; + dc_iscale = -dc_iscale; + dc_texturemid -= vis->pic->GetHeight(); + sprtopscreen = CenterY + dc_texturemid * spryscale; + } + else + { + sprflipvert = false; + sprtopscreen = CenterY - dc_texturemid * spryscale; + } + + dc_x = vis->x1; + x2 = vis->x2; + + if (dc_x < x2) + { + while ((dc_x < stop4) && (dc_x & 3)) + { + pixels = tex->GetColumn (frac >> FRACBITS, &spans); + if (ispsprite || !R_ClipSpriteColumnWithPortals(vis)) + R_DrawMaskedColumn (pixels, spans); + dc_x++; + frac += xiscale; + } + + while (dc_x < stop4) + { + rt_initcols(); + for (int zz = 4; zz; --zz) + { + pixels = tex->GetColumn (frac >> FRACBITS, &spans); + if (ispsprite || !R_ClipSpriteColumnWithPortals(vis)) + R_DrawMaskedColumnHoriz (pixels, spans); + dc_x++; + frac += xiscale; + } + rt_draw4cols (dc_x - 4); + } + + while (dc_x < x2) + { + pixels = tex->GetColumn (frac >> FRACBITS, &spans); + if (ispsprite || !R_ClipSpriteColumnWithPortals(vis)) + R_DrawMaskedColumn (pixels, spans); + dc_x++; + frac += xiscale; + } + } + } + + R_FinishSetPatchStyle (); + + NetUpdate (); +} + +void R_DrawWallSprite(vissprite_t *spr) +{ + int x1, x2; + double iyscale; + + x1 = MAX(spr->x1, spr->wallc.sx1); + x2 = MIN(spr->x2, spr->wallc.sx2); + if (x1 >= x2) + return; + WallT.InitFromWallCoords(&spr->wallc); + PrepWall(swall, lwall, spr->pic->GetWidth() << FRACBITS, x1, x2); + iyscale = 1 / spr->yscale; + dc_texturemid = (spr->gzt - ViewPos.Z) * iyscale; + if (spr->renderflags & RF_XFLIP) + { + int right = (spr->pic->GetWidth() << FRACBITS) - 1; + + for (int i = x1; i < x2; i++) + { + lwall[i] = right - lwall[i]; + } + } + // Prepare lighting + bool calclighting = false; + FDynamicColormap *usecolormap = basecolormap; + bool rereadcolormap = true; + + // Decals that are added to the scene must fade to black. + if (spr->Style.RenderStyle == LegacyRenderStyles[STYLE_Add] && usecolormap->Fade != 0) + { + usecolormap = GetSpecialLights(usecolormap->Color, 0, usecolormap->Desaturate); + rereadcolormap = false; + } + + int shade = LIGHT2SHADE(spr->sector->lightlevel + r_actualextralight); + GlobVis = r_WallVisibility; + rw_lightleft = float (GlobVis / spr->wallc.sz1); + rw_lightstep = float((GlobVis / spr->wallc.sz2 - rw_lightleft) / (spr->wallc.sx2 - spr->wallc.sx1)); + rw_light = rw_lightleft + (x1 - spr->wallc.sx1) * rw_lightstep; + if (fixedlightlev >= 0) + dc_colormap = usecolormap->Maps + fixedlightlev; + else if (fixedcolormap != NULL) + dc_colormap = fixedcolormap; + else if (!foggy && (spr->renderflags & RF_FULLBRIGHT)) + dc_colormap = usecolormap->Maps; + else + calclighting = true; + + // Draw it + WallSpriteTile = spr->pic; + if (spr->renderflags & RF_YFLIP) + { + sprflipvert = true; + iyscale = -iyscale; + dc_texturemid -= spr->pic->GetHeight(); + } + else + { + sprflipvert = false; + } + + MaskedScaleY = (float)iyscale; + + dc_x = x1; + ESPSResult mode; + + mode = R_SetPatchStyle (spr->Style.RenderStyle, spr->Style.Alpha, spr->Translation, spr->FillColor); + + // R_SetPatchStyle can modify basecolormap. + if (rereadcolormap) + { + usecolormap = basecolormap; + } + + if (mode == DontDraw) + { + return; + } + else + { + int stop4; + + if (mode == DoDraw0) + { // 1 column at a time + stop4 = dc_x; + } + else // DoDraw1 + { // up to 4 columns at a time + stop4 = x2 & ~3; + } + + while ((dc_x < stop4) && (dc_x & 3)) + { + if (calclighting) + { // calculate lighting + dc_colormap = usecolormap->Maps + (GETPALOOKUP (rw_light, shade) << COLORMAPSHIFT); + } + if (!R_ClipSpriteColumnWithPortals(spr)) + R_WallSpriteColumn(R_DrawMaskedColumn); + dc_x++; + } + + while (dc_x < stop4) + { + if (calclighting) + { // calculate lighting + dc_colormap = usecolormap->Maps + (GETPALOOKUP (rw_light, shade) << COLORMAPSHIFT); + } + rt_initcols(); + for (int zz = 4; zz; --zz) + { + if (!R_ClipSpriteColumnWithPortals(spr)) + R_WallSpriteColumn(R_DrawMaskedColumnHoriz); + dc_x++; + } + rt_draw4cols(dc_x - 4); + } + + while (dc_x < x2) + { + if (calclighting) + { // calculate lighting + dc_colormap = usecolormap->Maps + (GETPALOOKUP (rw_light, shade) << COLORMAPSHIFT); + } + if (!R_ClipSpriteColumnWithPortals(spr)) + R_WallSpriteColumn(R_DrawMaskedColumn); + dc_x++; + } + } + R_FinishSetPatchStyle(); +} + +void R_WallSpriteColumn (void (*drawfunc)(const BYTE *column, const FTexture::Span *spans)) +{ + float iscale = swall[dc_x] * MaskedScaleY; + dc_iscale = FLOAT2FIXED(iscale); + spryscale = 1 / iscale; + if (sprflipvert) + sprtopscreen = CenterY + dc_texturemid * spryscale; + else + sprtopscreen = CenterY - dc_texturemid * spryscale; + + const BYTE *column; + const FTexture::Span *spans; + column = WallSpriteTile->GetColumn (lwall[dc_x] >> FRACBITS, &spans); + dc_texturefrac = 0; + drawfunc (column, spans); + rw_light += rw_lightstep; +} + +void R_DrawVisVoxel(vissprite_t *spr, int minslabz, int maxslabz, short *cliptop, short *clipbot) +{ + ESPSResult mode; + int flags = 0; + + // Do setup for blending. + dc_colormap = spr->Style.colormap; + mode = R_SetPatchStyle(spr->Style.RenderStyle, spr->Style.Alpha, spr->Translation, spr->FillColor); + + if (mode == DontDraw) + { + return; + } + if (colfunc == fuzzcolfunc || colfunc == R_FillColumnP) + { + flags = DVF_OFFSCREEN | DVF_SPANSONLY; + } + else if (colfunc != basecolfunc) + { + flags = DVF_OFFSCREEN; + } + if (flags != 0) + { + R_CheckOffscreenBuffer(RenderTarget->GetWidth(), RenderTarget->GetHeight(), !!(flags & DVF_SPANSONLY)); + } + if (spr->bInMirror) + { + flags |= DVF_MIRRORED; + } + + // Render the voxel, either directly to the screen or offscreen. + R_DrawVoxel(spr->pa.vpos, spr->pa.vang, spr->gpos, spr->angle, + spr->xscale, FLOAT2FIXED(spr->yscale), spr->voxel, spr->Style.colormap, cliptop, clipbot, + minslabz, maxslabz, flags); + + // Blend the voxel, if that's what we need to do. + if ((flags & ~DVF_MIRRORED) != 0) + { + for (int x = 0; x < viewwidth; ++x) + { + if (!(flags & DVF_SPANSONLY) && (x & 3) == 0) + { + rt_initcols(OffscreenColorBuffer + x * OffscreenBufferHeight); + } + for (FCoverageBuffer::Span *span = OffscreenCoverageBuffer->Spans[x]; span != NULL; span = span->NextSpan) + { + if (flags & DVF_SPANSONLY) + { + dc_x = x; + dc_yl = span->Start; + dc_yh = span->Stop - 1; + dc_count = span->Stop - span->Start; + dc_dest = ylookup[span->Start] + x + dc_destorg; + colfunc(); + } + else + { + unsigned int **tspan = &dc_ctspan[x & 3]; + (*tspan)[0] = span->Start; + (*tspan)[1] = span->Stop - 1; + *tspan += 2; + } + } + if (!(flags & DVF_SPANSONLY) && (x & 3) == 3) + { + rt_draw4cols(x - 3); + } + } + } + + R_FinishSetPatchStyle(); + NetUpdate(); +} + +// +// R_ProjectSprite +// Generates a vissprite for a thing if it might be visible. +// +void R_ProjectSprite (AActor *thing, int fakeside, F3DFloor *fakefloor, F3DFloor *fakeceiling) +{ + double tr_x; + double tr_y; + + double gzt; // killough 3/27/98 + double gzb; // [RH] use bottom of sprite, not actor + double tx;// , tx2; + double tz; + + double xscale = 1, yscale = 1; + + int x1; + int x2; + + FTextureID picnum; + FTexture *tex; + FVoxelDef *voxel; + + vissprite_t* vis; + + fixed_t iscale; + + sector_t* heightsec; // killough 3/27/98 + + // Don't waste time projecting sprites that are definitely not visible. + if (thing == NULL || + (thing->renderflags & RF_INVISIBLE) || + !thing->RenderStyle.IsVisible(thing->Alpha) || + !thing->IsVisibleToPlayer()) + { + return; + } + + // [ZZ] Or less definitely not visible (hue) + // [ZZ] 10.01.2016: don't try to clip stuff inside a skybox against the current portal. + if (!CurrentPortalInSkybox && CurrentPortal && !!P_PointOnLineSidePrecise(thing->Pos(), CurrentPortal->dst)) + return; + + // [RH] Interpolate the sprite's position to make it look smooth + DVector3 pos = thing->InterpolatedPosition(r_TicFracF); + pos.Z += thing->GetBobOffset(r_TicFracF); + + tex = NULL; + voxel = NULL; + + int spritenum = thing->sprite; + DVector2 spriteScale = thing->Scale; + int renderflags = thing->renderflags; + if (spriteScale.Y < 0) + { + spriteScale.Y = -spriteScale.Y; + renderflags ^= RF_YFLIP; + } + if (thing->player != NULL) + { + P_CheckPlayerSprite(thing, spritenum, spriteScale); + } + + if (thing->picnum.isValid()) + { + picnum = thing->picnum; + + tex = TexMan(picnum); + if (tex->UseType == FTexture::TEX_Null) + { + return; + } + + if (tex->Rotations != 0xFFFF) + { + // choose a different rotation based on player view + spriteframe_t *sprframe = &SpriteFrames[tex->Rotations]; + DAngle ang = (pos - ViewPos).Angle(); + angle_t rot; + if (sprframe->Texture[0] == sprframe->Texture[1]) + { + rot = (ang - thing->Angles.Yaw + 45.0/2*9).BAMs() >> 28; + } + else + { + rot = (ang - thing->Angles.Yaw + (45.0/2*9-180.0/16)).BAMs() >> 28; + } + picnum = sprframe->Texture[rot]; + if (sprframe->Flip & (1 << rot)) + { + renderflags ^= RF_XFLIP; + } + tex = TexMan[picnum]; // Do not animate the rotation + } + } + else + { + // decide which texture to use for the sprite +#ifdef RANGECHECK + if (spritenum >= (signed)sprites.Size () || spritenum < 0) + { + DPrintf ("R_ProjectSprite: invalid sprite number %u\n", spritenum); + return; + } +#endif + spritedef_t *sprdef = &sprites[spritenum]; + if (thing->frame >= sprdef->numframes) + { + // If there are no frames at all for this sprite, don't draw it. + return; + } + else + { + //picnum = SpriteFrames[sprdef->spriteframes + thing->frame].Texture[0]; + // choose a different rotation based on player view + spriteframe_t *sprframe = &SpriteFrames[sprdef->spriteframes + thing->frame]; + DAngle ang = (pos - ViewPos).Angle(); + angle_t rot; + if (sprframe->Texture[0] == sprframe->Texture[1]) + { + rot = (ang - thing->Angles.Yaw + 45.0 / 2 * 9).BAMs() >> 28; + } + else + { + rot = (ang - thing->Angles.Yaw + (45.0 / 2 * 9 - 180.0 / 16)).BAMs() >> 28; + } + picnum = sprframe->Texture[rot]; + if (sprframe->Flip & (1 << rot)) + { + renderflags ^= RF_XFLIP; + } + tex = TexMan[picnum]; // Do not animate the rotation + if (r_drawvoxels) + { + voxel = sprframe->Voxel; + } + } + } + if (spriteScale.X < 0) + { + spriteScale.X = -spriteScale.X; + renderflags ^= RF_XFLIP; + } + if (voxel == NULL && (tex == NULL || tex->UseType == FTexture::TEX_Null)) + { + return; + } + + if ((renderflags & RF_SPRITETYPEMASK) == RF_WALLSPRITE) + { + R_ProjectWallSprite(thing, pos, picnum, spriteScale, renderflags); + return; + } + + // transform the origin point + tr_x = pos.X - ViewPos.X; + tr_y = pos.Y - ViewPos.Y; + + tz = tr_x * ViewTanCos + tr_y * ViewTanSin; + + // thing is behind view plane? + if (voxel == NULL && tz < MINZ) + return; + + tx = tr_x * ViewSin - tr_y * ViewCos; + + // [RH] Flip for mirrors + if (MirrorFlags & RF_XFLIP) + { + tx = -tx; + } + //tx2 = tx >> 4; + + // too far off the side? + // if it's a voxel, it can be further off the side + if ((voxel == NULL && (fabs(tx / 64) > fabs(tz))) || + (voxel != NULL && (fabs(tx / 128) > abs(tz)))) + { + return; + } + + if (voxel == NULL) + { + // [RH] Added scaling + int scaled_to = tex->GetScaledTopOffset(); + int scaled_bo = scaled_to - tex->GetScaledHeight(); + gzt = pos.Z + spriteScale.Y * scaled_to; + gzb = pos.Z + spriteScale.Y * scaled_bo; + } + else + { + xscale = spriteScale.X * voxel->Scale; + yscale = spriteScale.Y * voxel->Scale; + double piv = voxel->Voxel->Mips[0].Pivot.Z; + gzt = pos.Z + yscale * piv - thing->Floorclip; + gzb = pos.Z + yscale * (piv - voxel->Voxel->Mips[0].SizeZ); + if (gzt <= gzb) + return; + } + + // killough 3/27/98: exclude things totally separated + // from the viewer, by either water or fake ceilings + // killough 4/11/98: improve sprite clipping for underwater/fake ceilings + + heightsec = thing->Sector->GetHeightSec(); + + if (heightsec != NULL) // only clip things which are in special sectors + { + if (fakeside == FAKED_AboveCeiling) + { + if (gzt < heightsec->ceilingplane.ZatPoint(pos)) + return; + } + else if (fakeside == FAKED_BelowFloor) + { + if (gzb >= heightsec->floorplane.ZatPoint(pos)) + return; + } + else + { + if (gzt < heightsec->floorplane.ZatPoint(pos)) + return; + if (!(heightsec->MoreFlags & SECF_FAKEFLOORONLY) && gzb >= heightsec->ceilingplane.ZatPoint(pos)) + return; + } + } + + if (voxel == NULL) + { + xscale = CenterX / tz; + + // [RH] Reject sprites that are off the top or bottom of the screen + if (globaluclip * tz > ViewPos.Z - gzb || globaldclip * tz < ViewPos.Z - gzt) + { + return; + } + + // [RH] Flip for mirrors + renderflags ^= MirrorFlags & RF_XFLIP; + + // calculate edges of the shape + const double thingxscalemul = spriteScale.X / tex->Scale.X; + + tx -= ((renderflags & RF_XFLIP) ? (tex->GetWidth() - tex->LeftOffset - 1) : tex->LeftOffset) * thingxscalemul; + x1 = centerx + xs_RoundToInt(tx * xscale); + + // off the right side? + if (x1 >= WindowRight) + return; + + tx += tex->GetWidth() * thingxscalemul; + x2 = centerx + xs_RoundToInt(tx * xscale); + + // off the left side or too small? + if ((x2 < WindowLeft || x2 <= x1)) + return; + + xscale = spriteScale.X * xscale / tex->Scale.X; + iscale = (tex->GetWidth() << FRACBITS) / (x2 - x1); + + double yscale = spriteScale.Y / tex->Scale.Y; + + // store information in a vissprite + vis = R_NewVisSprite(); + + vis->CurrentPortalUniq = CurrentPortalUniq; + vis->xscale = FLOAT2FIXED(xscale); + vis->yscale = float(InvZtoScale * yscale / tz); + vis->idepth = float(1 / tz); + vis->floorclip = thing->Floorclip / yscale; + vis->texturemid = tex->TopOffset - (ViewPos.Z - pos.Z + thing->Floorclip) / yscale; + vis->x1 = x1 < WindowLeft ? WindowLeft : x1; + vis->x2 = x2 > WindowRight ? WindowRight : x2; + vis->angle = thing->Angles.Yaw.BAMs(); + + if (renderflags & RF_XFLIP) + { + vis->startfrac = (tex->GetWidth() << FRACBITS) - 1; + vis->xiscale = -iscale; + } + else + { + vis->startfrac = 0; + vis->xiscale = iscale; + } + + if (vis->x1 > x1) + vis->startfrac += vis->xiscale * (vis->x1 - x1); + } + else + { + vis = R_NewVisSprite(); + + vis->CurrentPortalUniq = CurrentPortalUniq; + vis->xscale = FLOAT2FIXED(xscale); + vis->yscale = (float)yscale; + vis->x1 = WindowLeft; + vis->x2 = WindowRight; + vis->idepth = 1 / MINZ; + vis->floorclip = thing->Floorclip; + + pos.Z -= thing->Floorclip; + + vis->angle = thing->Angles.Yaw.BAMs() + voxel->AngleOffset.BAMs(); + + int voxelspin = (thing->flags & MF_DROPPED) ? voxel->DroppedSpin : voxel->PlacedSpin; + if (voxelspin != 0) + { + DAngle ang = double(I_FPSTime()) * voxelspin / 1000; + vis->angle -= ang.BAMs(); + } + + vis->pa.vpos = { (float)ViewPos.X, (float)ViewPos.Y, (float)ViewPos.Z }; + vis->pa.vang = FAngle((float)ViewAngle.Degrees); + } + + // killough 3/27/98: save sector for special clipping later + vis->heightsec = heightsec; + vis->sector = thing->Sector; + + vis->depth = (float)tz; + vis->gpos = { (float)pos.X, (float)pos.Y, (float)pos.Z }; + vis->gzb = (float)gzb; // [RH] use gzb, not thing->z + vis->gzt = (float)gzt; // killough 3/27/98 + vis->deltax = float(pos.X - ViewPos.X); + vis->deltay = float(pos.Y - ViewPos.Y); + vis->renderflags = renderflags; + if(thing->flags5 & MF5_BRIGHT) vis->renderflags |= RF_FULLBRIGHT; // kg3D + vis->Style.RenderStyle = thing->RenderStyle; + vis->FillColor = thing->fillcolor; + vis->Translation = thing->Translation; // [RH] thing translation table + vis->FakeFlatStat = fakeside; + vis->Style.Alpha = float(thing->Alpha); + vis->fakefloor = fakefloor; + vis->fakeceiling = fakeceiling; + vis->ColormapNum = 0; + vis->bInMirror = MirrorFlags & RF_XFLIP; + vis->bSplitSprite = false; + + if (voxel != NULL) + { + vis->voxel = voxel->Voxel; + vis->bIsVoxel = true; + vis->bWallSprite = false; + DrewAVoxel = true; + } + else + { + vis->pic = tex; + vis->bIsVoxel = false; + vis->bWallSprite = false; + } + + // The software renderer cannot invert the source without inverting the overlay + // too. That means if the source is inverted, we need to do the reverse of what + // the invert overlay flag says to do. + INTBOOL invertcolormap = (vis->Style.RenderStyle.Flags & STYLEF_InvertOverlay); + + if (vis->Style.RenderStyle.Flags & STYLEF_InvertSource) + { + invertcolormap = !invertcolormap; + } + + FDynamicColormap *mybasecolormap = basecolormap; + + // Sprites that are added to the scene must fade to black. + if (vis->Style.RenderStyle == LegacyRenderStyles[STYLE_Add] && mybasecolormap->Fade != 0) + { + mybasecolormap = GetSpecialLights(mybasecolormap->Color, 0, mybasecolormap->Desaturate); + } + + if (vis->Style.RenderStyle.Flags & STYLEF_FadeToBlack) + { + if (invertcolormap) + { // Fade to white + mybasecolormap = GetSpecialLights(mybasecolormap->Color, MAKERGB(255,255,255), mybasecolormap->Desaturate); + invertcolormap = false; + } + else + { // Fade to black + mybasecolormap = GetSpecialLights(mybasecolormap->Color, MAKERGB(0,0,0), mybasecolormap->Desaturate); + } + } + + // get light level + if (fixedcolormap != NULL) + { // fixed map + vis->Style.colormap = fixedcolormap; + } + else + { + if (invertcolormap) + { + mybasecolormap = GetSpecialLights(mybasecolormap->Color, mybasecolormap->Fade.InverseColor(), mybasecolormap->Desaturate); + } + if (fixedlightlev >= 0) + { + vis->Style.colormap = mybasecolormap->Maps + fixedlightlev; + } + else if (!foggy && ((renderflags & RF_FULLBRIGHT) || (thing->flags5 & MF5_BRIGHT))) + { // full bright + vis->Style.colormap = mybasecolormap->Maps; + } + else + { // diminished light + vis->ColormapNum = GETPALOOKUP( + r_SpriteVisibility / MAX(tz, MINZ), spriteshade); + vis->Style.colormap = mybasecolormap->Maps + (vis->ColormapNum << COLORMAPSHIFT); + } + } +} + +static void R_ProjectWallSprite(AActor *thing, const DVector3 &pos, FTextureID picnum, const DVector2 &scale, int renderflags) +{ + FWallCoords wallc; + double x1, x2; + DVector2 left, right; + double gzb, gzt, tz; + FTexture *pic = TexMan(picnum, true); + DAngle ang = thing->Angles.Yaw + 90; + double angcos = ang.Cos(); + double angsin = ang.Sin(); + vissprite_t *vis; + + // Determine left and right edges of sprite. The sprite's angle is its normal, + // so the edges are 90 degrees each side of it. + x2 = pic->GetScaledWidth(); + x1 = pic->GetScaledLeftOffset(); + + x1 *= scale.X; + x2 *= scale.X; + + left.X = pos.X - x1 * angcos - ViewPos.X; + left.Y = pos.Y - x1 * angsin - ViewPos.Y; + right.X = left.X + x2 * angcos; + right.Y = right.Y + x2 * angsin; + + // Is it off-screen? + if (wallc.Init(left, right, TOO_CLOSE_Z)) + return; + + if (wallc.sx1 >= WindowRight || wallc.sx2 <= WindowLeft) + return; + + // Sprite sorting should probably treat these as walls, not sprites, + // but right now, I just want to get them drawing. + tz = (pos.X - ViewPos.X) * ViewTanCos + (pos.Y - ViewPos.Y) * ViewTanSin; + + int scaled_to = pic->GetScaledTopOffset(); + int scaled_bo = scaled_to - pic->GetScaledHeight(); + gzt = pos.Z + scale.Y * scaled_to; + gzb = pos.Z + scale.Y * scaled_bo; + + vis = R_NewVisSprite(); + vis->CurrentPortalUniq = CurrentPortalUniq; + vis->x1 = wallc.sx1 < WindowLeft ? WindowLeft : wallc.sx1; + vis->x2 = wallc.sx2 >= WindowRight ? WindowRight : wallc.sx2; + vis->yscale = (float)scale.Y; + vis->idepth = float(1 / tz); + vis->depth = (float)tz; + vis->sector = thing->Sector; + vis->heightsec = NULL; + vis->gpos = { (float)pos.X, (float)pos.Y, (float)pos.Z }; + vis->gzb = (float)gzb; + vis->gzt = (float)gzt; + vis->deltax = float(pos.X - ViewPos.X); + vis->deltay = float(pos.Y - ViewPos.Y); + vis->renderflags = renderflags; + if(thing->flags5 & MF5_BRIGHT) vis->renderflags |= RF_FULLBRIGHT; // kg3D + vis->Style.RenderStyle = thing->RenderStyle; + vis->FillColor = thing->fillcolor; + vis->Translation = thing->Translation; + vis->FakeFlatStat = 0; + vis->Style.Alpha = float(thing->Alpha); + vis->fakefloor = NULL; + vis->fakeceiling = NULL; + vis->ColormapNum = 0; + vis->bInMirror = MirrorFlags & RF_XFLIP; + vis->pic = pic; + vis->bIsVoxel = false; + vis->bWallSprite = true; + vis->ColormapNum = GETPALOOKUP( + r_SpriteVisibility / MAX(tz, MINZ), spriteshade); + vis->Style.colormap = basecolormap->Maps + (vis->ColormapNum << COLORMAPSHIFT); + vis->wallc = wallc; +} + +// +// R_AddSprites +// During BSP traversal, this adds sprites by sector. +// +// killough 9/18/98: add lightlevel as parameter, fixing underwater lighting +// [RH] Save which side of heightsec sprite is on here. +void R_AddSprites (sector_t *sec, int lightlevel, int fakeside) +{ + AActor *thing; + F3DFloor *fakeceiling = NULL; + F3DFloor *fakefloor = NULL; + + // BSP is traversed by subsector. + // A sector might have been split into several + // subsectors during BSP building. + // Thus we check whether it was already added. + if (sec->thinglist == NULL || sec->validcount == validcount) + return; + + // Well, now it will be done. + sec->validcount = validcount; + + spriteshade = LIGHT2SHADE(lightlevel + r_actualextralight); + + // Handle all things in sector. + for (thing = sec->thinglist; thing; thing = thing->snext) + { + FIntCVar *cvar = thing->GetClass()->distancecheck; + if (cvar != NULL && *cvar >= 0) + { + double dist = (thing->Pos() - ViewPos).LengthSquared(); + double check = (double)**cvar; + if (dist >= check * check) + { + continue; + } + } + + // find fake level + for(auto rover : frontsector->e->XFloor.ffloors) + { + if(!(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERPLANES)) continue; + if(!(rover->flags & FF_SOLID) || rover->alpha != 255) continue; + if(!fakefloor) + { + if(!rover->top.plane->isSlope()) + { + if(rover->top.plane->ZatPoint(0., 0.) <= thing->Z()) fakefloor = rover; + } + } + if(!rover->bottom.plane->isSlope()) + { + if(rover->bottom.plane->ZatPoint(0., 0.) >= thing->Top()) fakeceiling = rover; + } + } + R_ProjectSprite (thing, fakeside, fakefloor, fakeceiling); + fakeceiling = NULL; + fakefloor = NULL; + } +} + + +// +// R_DrawPSprite +// +void R_DrawPSprite (pspdef_t* psp, int pspnum, AActor *owner, double sx, double sy) +{ + double tx; + int x1; + int x2; + spritedef_t* sprdef; + spriteframe_t* sprframe; + FTextureID picnum; + WORD flip; + FTexture* tex; + vissprite_t* vis; + static vissprite_t avis[NUMPSPRITES + 1]; + static vissprite_t *avisp[countof(avis)]; + bool noaccel; + + assert(pspnum >= 0 && pspnum < NUMPSPRITES); + + if (avisp[0] == NULL) + { + for (unsigned i = 0; i < countof(avis); ++i) + { + avisp[i] = &avis[i]; + } + } + + // decide which patch to use + if ( (unsigned)psp->sprite >= (unsigned)sprites.Size ()) + { + DPrintf ("R_DrawPSprite: invalid sprite number %i\n", psp->sprite); + return; + } + sprdef = &sprites[psp->sprite]; + if (psp->frame >= sprdef->numframes) + { + DPrintf ("R_DrawPSprite: invalid sprite frame %i : %i\n", psp->sprite, psp->frame); + return; + } + sprframe = &SpriteFrames[sprdef->spriteframes + psp->frame]; + + picnum = sprframe->Texture[0]; + flip = sprframe->Flip & 1; + tex = TexMan(picnum); + + if (tex->UseType == FTexture::TEX_Null) + return; + + // calculate edges of the shape + tx = sx - (320 / 2); + + tx -= tex->GetScaledLeftOffset(); + x1 = xs_RoundToInt(CenterX + tx * pspritexscale); + + // off the right side + if (x1 > viewwidth) + return; + + tx += tex->GetScaledWidth(); + x2 = xs_RoundToInt(CenterX + tx * pspritexscale); + + // off the left side + if (x2 <= 0) + return; + + // store information in a vissprite + vis = avisp[NUMPSPRITES]; + vis->renderflags = owner->renderflags; + vis->floorclip = 0; + + vis->texturemid = (BASEYCENTER - sy) * tex->Scale.Y + tex->TopOffset; + + if (camera->player && (RenderTarget != screen || + viewheight == RenderTarget->GetHeight() || + (RenderTarget->GetWidth() > 320 && !st_scale))) + { // Adjust PSprite for fullscreen views + AWeapon *weapon = NULL; + if (camera->player != NULL) + { + weapon = camera->player->ReadyWeapon; + } + if (pspnum <= ps_flash && weapon != NULL && weapon->YAdjust != 0) + { + if (RenderTarget != screen || viewheight == RenderTarget->GetHeight()) + { + vis->texturemid -= weapon->YAdjust; + } + else + { + vis->texturemid -= StatusBar->GetDisplacement () * weapon->YAdjust; + } + } + } + if (pspnum <= ps_flash) + { // Move the weapon down for 1280x1024. + vis->texturemid -= BaseRatioSizes[WidescreenRatio][2]; + } + vis->x1 = x1 < 0 ? 0 : x1; + vis->x2 = x2 >= viewwidth ? viewwidth : x2; + vis->xscale = FLOAT2FIXED(pspritexscale / tex->Scale.X); + vis->yscale = float(pspriteyscale / tex->Scale.Y); + vis->Translation = 0; // [RH] Use default colors + vis->pic = tex; + vis->ColormapNum = 0; + + if (flip) + { + vis->xiscale = -FLOAT2FIXED(pspritexiscale * tex->Scale.X); + vis->startfrac = (tex->GetWidth() << FRACBITS) - 1; + } + else + { + vis->xiscale = FLOAT2FIXED(pspritexiscale * tex->Scale.X); + vis->startfrac = 0; + } + + if (vis->x1 > x1) + vis->startfrac += vis->xiscale*(vis->x1-x1); + + noaccel = false; + FDynamicColormap *colormap_to_use = NULL; + if (pspnum <= ps_flash) + { + vis->Style.Alpha = float(owner->Alpha); + vis->Style.RenderStyle = owner->RenderStyle; + + // The software renderer cannot invert the source without inverting the overlay + // too. That means if the source is inverted, we need to do the reverse of what + // the invert overlay flag says to do. + INTBOOL invertcolormap = (vis->Style.RenderStyle.Flags & STYLEF_InvertOverlay); + + if (vis->Style.RenderStyle.Flags & STYLEF_InvertSource) + { + invertcolormap = !invertcolormap; + } + + FDynamicColormap *mybasecolormap = basecolormap; + + if (vis->Style.RenderStyle.Flags & STYLEF_FadeToBlack) + { + if (invertcolormap) + { // Fade to white + mybasecolormap = GetSpecialLights(mybasecolormap->Color, MAKERGB(255,255,255), mybasecolormap->Desaturate); + invertcolormap = false; + } + else + { // Fade to black + mybasecolormap = GetSpecialLights(mybasecolormap->Color, MAKERGB(0,0,0), mybasecolormap->Desaturate); + } + } + + if (realfixedcolormap != NULL) + { // fixed color + vis->Style.colormap = realfixedcolormap->Colormap; + } + else + { + if (invertcolormap) + { + mybasecolormap = GetSpecialLights(mybasecolormap->Color, mybasecolormap->Fade.InverseColor(), mybasecolormap->Desaturate); + } + if (fixedlightlev >= 0) + { + vis->Style.colormap = mybasecolormap->Maps + fixedlightlev; + } + else if (!foggy && psp->state->GetFullbright()) + { // full bright + vis->Style.colormap = mybasecolormap->Maps; // [RH] use basecolormap + } + else + { // local light + vis->Style.colormap = mybasecolormap->Maps + (GETPALOOKUP (0, spriteshade) << COLORMAPSHIFT); + } + } + if (camera->Inventory != NULL) + { + lighttable_t *oldcolormap = vis->Style.colormap; + camera->Inventory->AlterWeaponSprite (&vis->Style); + if (vis->Style.colormap != oldcolormap) + { + // The colormap has changed. Is it one we can easily identify? + // If not, then don't bother trying to identify it for + // hardware accelerated drawing. + if (vis->Style.colormap < SpecialColormaps[0].Colormap || + vis->Style.colormap > SpecialColormaps.Last().Colormap) + { + noaccel = true; + } + // Has the basecolormap changed? If so, we can't hardware accelerate it, + // since we don't know what it is anymore. + else if (vis->Style.colormap < mybasecolormap->Maps || + vis->Style.colormap >= mybasecolormap->Maps + NUMCOLORMAPS*256) + { + noaccel = true; + } + } + } + // If we're drawing with a special colormap, but shaders for them are disabled, do + // not accelerate. + if (!r_shadercolormaps && (vis->Style.colormap >= SpecialColormaps[0].Colormap && + vis->Style.colormap <= SpecialColormaps.Last().Colormap)) + { + noaccel = true; + } + // If drawing with a BOOM colormap, disable acceleration. + if (mybasecolormap == &NormalLight && NormalLight.Maps != realcolormaps) + { + noaccel = true; + } + // If the main colormap has fixed lights, and this sprite is being drawn with that + // colormap, disable acceleration so that the lights can remain fixed. + if (!noaccel && realfixedcolormap == NULL && + NormalLightHasFixedLights && mybasecolormap == &NormalLight && + vis->pic->UseBasePalette()) + { + noaccel = true; + } + colormap_to_use = mybasecolormap; + } + else + { + colormap_to_use = basecolormap; + vis->Style.colormap = basecolormap->Maps; + vis->Style.RenderStyle = STYLE_Normal; + } + + // Check for hardware-assisted 2D. If it's available, and this sprite is not + // fuzzy, don't draw it until after the switch to 2D mode. + if (!noaccel && RenderTarget == screen && (DFrameBuffer *)screen->Accel2D) + { + FRenderStyle style = vis->Style.RenderStyle; + style.CheckFuzz(); + if (style.BlendOp != STYLEOP_Fuzz) + { + VisPSpritesX1[pspnum] = x1; + VisPSpritesBaseColormap[pspnum] = colormap_to_use; + VisPSprites[pspnum] = vis; + swapvalues(avisp[pspnum], avisp[NUMPSPRITES]); + return; + } + } + R_DrawVisSprite (vis); +} + + + +//========================================================================== +// +// R_DrawPlayerSprites +// +//========================================================================== + +void R_DrawPlayerSprites () +{ + int i; + int lightnum; + pspdef_t* psp; + sector_t* sec = NULL; + static sector_t tempsec; + int floorlight, ceilinglight; + F3DFloor *rover; + + if (!r_drawplayersprites || + !camera || + !camera->player || + (players[consoleplayer].cheats & CF_CHASECAM) || + (r_deathcamera && camera->health <= 0)) + return; + + if(fixedlightlev < 0 && viewsector->e && viewsector->e->XFloor.lightlist.Size()) { + for(i = viewsector->e->XFloor.lightlist.Size() - 1; i >= 0; i--) + if(ViewPos.Z <= viewsector->e->XFloor.lightlist[i].plane.Zat0()) { + rover = viewsector->e->XFloor.lightlist[i].caster; + if(rover) { + if(rover->flags & FF_DOUBLESHADOW && ViewPos.Z <= rover->bottom.plane->Zat0()) + break; + sec = rover->model; + if(rover->flags & FF_FADEWALLS) + basecolormap = sec->ColorMap; + else + basecolormap = viewsector->e->XFloor.lightlist[i].extra_colormap; + } + break; + } + if(!sec) { + sec = viewsector; + basecolormap = sec->ColorMap; + } + floorlight = ceilinglight = sec->lightlevel; + } else { + // This used to use camera->Sector but due to interpolation that can be incorrect + // when the interpolated viewpoint is in a different sector than the camera. + sec = R_FakeFlat (viewsector, &tempsec, &floorlight, + &ceilinglight, false); + + // [RH] set basecolormap + basecolormap = sec->ColorMap; + } + + // [RH] set foggy flag + foggy = (level.fadeto || basecolormap->Fade || (level.flags & LEVEL_HASFADETABLE)); + r_actualextralight = foggy ? 0 : extralight << 4; + + // get light level + lightnum = ((floorlight + ceilinglight) >> 1) + r_actualextralight; + spriteshade = LIGHT2SHADE(lightnum) - 24*FRACUNIT; + + // clip to screen bounds + mfloorclip = screenheightarray; + mceilingclip = zeroarray; + + if (camera->player != NULL) + { + double centerhack = CenterY; + float ofsx, ofsy; + + CenterY = viewheight / 2; + + P_BobWeapon (camera->player, &camera->player->psprites[ps_weapon], &ofsx, &ofsy, r_TicFracF); + + // add all active psprites + for (i = 0, psp = camera->player->psprites; + i < NUMPSPRITES; + i++, psp++) + { + // [RH] Don't draw the targeter's crosshair if the player already has a crosshair set. + if (psp->state && (i != ps_targetcenter || CrosshairImage == NULL)) + { + R_DrawPSprite (psp, i, camera, psp->sx + ofsx, psp->sy + ofsy); + } + // [RH] Don't bob the targeter. + if (i == ps_flash) + { + ofsx = ofsy = 0; + } + } + + CenterY = centerhack; + } +} + +//========================================================================== +// +// R_DrawRemainingPlayerSprites +// +// Called from D_Display to draw sprites that were not drawn by +// R_DrawPlayerSprites(). +// +//========================================================================== + +void R_DrawRemainingPlayerSprites() +{ + for (int i = 0; i < NUMPSPRITES; ++i) + { + vissprite_t *vis; + + vis = VisPSprites[i]; + VisPSprites[i] = NULL; + + if (vis != NULL) + { + FDynamicColormap *colormap = VisPSpritesBaseColormap[i]; + bool flip = vis->xiscale < 0; + FSpecialColormap *special = NULL; + PalEntry overlay = 0; + FColormapStyle colormapstyle; + bool usecolormapstyle = false; + + if (vis->Style.colormap >= SpecialColormaps[0].Colormap && + vis->Style.colormap < SpecialColormaps[SpecialColormaps.Size()].Colormap) + { + // Yuck! There needs to be a better way to store colormaps in the vissprite... :( + ptrdiff_t specialmap = (vis->Style.colormap - SpecialColormaps[0].Colormap) / sizeof(FSpecialColormap); + special = &SpecialColormaps[specialmap]; + } + else if (colormap->Color == PalEntry(255,255,255) && + colormap->Desaturate == 0) + { + overlay = colormap->Fade; + overlay.a = BYTE(((vis->Style.colormap - colormap->Maps) >> 8) * 255 / NUMCOLORMAPS); + } + else + { + usecolormapstyle = true; + colormapstyle.Color = colormap->Color; + colormapstyle.Fade = colormap->Fade; + colormapstyle.Desaturate = colormap->Desaturate; + colormapstyle.FadeLevel = ((vis->Style.colormap - colormap->Maps) >> 8) / float(NUMCOLORMAPS); + } + screen->DrawTexture(vis->pic, + viewwindowx + VisPSpritesX1[i], + viewwindowy + viewheight/2 - vis->texturemid * vis->yscale - 0.5, + DTA_DestWidthF, FIXED2DBL(vis->pic->GetWidth() * vis->xscale), + DTA_DestHeightF, vis->pic->GetHeight() * vis->yscale, + DTA_Translation, TranslationToTable(vis->Translation), + DTA_FlipX, flip, + DTA_TopOffset, 0, + DTA_LeftOffset, 0, + DTA_ClipLeft, viewwindowx, + DTA_ClipTop, viewwindowy, + DTA_ClipRight, viewwindowx + viewwidth, + DTA_ClipBottom, viewwindowy + viewheight, + DTA_AlphaF, vis->Style.Alpha, + DTA_RenderStyle, vis->Style.RenderStyle, + DTA_FillColor, vis->FillColor, + DTA_SpecialColormap, special, + DTA_ColorOverlay, overlay.d, + DTA_ColormapStyle, usecolormapstyle ? &colormapstyle : NULL, + TAG_DONE); + } + } +} + +// +// R_SortVisSprites +// +// [RH] The old code for this function used a bubble sort, which was far less +// than optimal with large numbers of sprites. I changed it to use the +// stdlib qsort() function instead, and now it is a *lot* faster; the +// more vissprites that need to be sorted, the better the performance +// gain compared to the old function. +// +// Sort vissprites by depth, far to near + +// This is the standard version, which does a simple test based on depth. +static bool sv_compare(vissprite_t *a, vissprite_t *b) +{ + return a->idepth > b->idepth; +} + +// This is an alternate version, for when one or more voxel is in view. +// It does a 2D distance test based on whichever one is furthest from +// the viewpoint. +static bool sv_compare2d(vissprite_t *a, vissprite_t *b) +{ + return DVector2(a->deltax, a->deltay).LengthSquared() < + DVector2(b->deltax, b->deltay).LengthSquared(); +} + +#if 0 +static drawseg_t **drawsegsorter; +static int drawsegsortersize = 0; + +// Sort vissprites by leftmost column, left to right +static int sv_comparex (const void *arg1, const void *arg2) +{ + return (*(vissprite_t **)arg2)->x1 - (*(vissprite_t **)arg1)->x1; +} + +// Sort drawsegs by rightmost column, left to right +static int sd_comparex (const void *arg1, const void *arg2) +{ + return (*(drawseg_t **)arg2)->x2 - (*(drawseg_t **)arg1)->x2; +} + +CVAR (Bool, r_splitsprites, true, CVAR_ARCHIVE) + +// Split up vissprites that intersect drawsegs +void R_SplitVisSprites () +{ + size_t start, stop; + size_t numdrawsegs = ds_p - firstdrawseg; + size_t numsprites; + size_t spr, dseg, dseg2; + + if (!r_splitsprites) + return; + + if (numdrawsegs == 0 || vissprite_p - firstvissprite == 0) + return; + + // Sort drawsegs from left to right + if (numdrawsegs > drawsegsortersize) + { + if (drawsegsorter != NULL) + delete[] drawsegsorter; + drawsegsortersize = numdrawsegs * 2; + drawsegsorter = new drawseg_t *[drawsegsortersize]; + } + for (dseg = dseg2 = 0; dseg < numdrawsegs; ++dseg) + { + // Drawsegs that don't clip any sprites don't need to be considered. + if (firstdrawseg[dseg].silhouette) + { + drawsegsorter[dseg2++] = &firstdrawseg[dseg]; + } + } + numdrawsegs = dseg2; + if (numdrawsegs == 0) + { + return; + } + qsort (drawsegsorter, numdrawsegs, sizeof(drawseg_t *), sd_comparex); + + // Now sort vissprites from left to right, and walk them simultaneously + // with the drawsegs, splitting any that intersect. + start = firstvissprite - vissprites; + + int p = 0; + do + { + p++; + R_SortVisSprites (sv_comparex, start); + stop = vissprite_p - vissprites; + numsprites = stop - start; + + spr = dseg = 0; + do + { + vissprite_t *vis = spritesorter[spr], *vis2; + + // Skip drawsegs until we get to one that doesn't end before the sprite + // begins. + while (dseg < numdrawsegs && drawsegsorter[dseg]->x2 <= vis->x1) + { + dseg++; + } + // Now split the sprite against any drawsegs it intersects + for (dseg2 = dseg; dseg2 < numdrawsegs; dseg2++) + { + drawseg_t *ds = drawsegsorter[dseg2]; + + if (ds->x1 > vis->x2 || ds->x2 < vis->x1) + continue; + + if ((vis->idepth < ds->siz1) != (vis->idepth < ds->siz2)) + { // The drawseg is crossed; find the x where the intersection occurs + int cross = Scale (vis->idepth - ds->siz1, ds->sx2 - ds->sx1, ds->siz2 - ds->siz1) + ds->sx1 + 1; + +/* if (cross < ds->x1 || cross > ds->x2) + { // The original seg is crossed, but the drawseg is not + continue; + } +*/ if (cross <= vis->x1 || cross >= vis->x2) + { // Don't create 0-sized sprites + continue; + } + + vis->bSplitSprite = true; + + // Create a new vissprite for the right part of the sprite + vis2 = R_NewVisSprite (); + *vis2 = *vis; + vis2->startfrac += vis2->xiscale * (cross - vis2->x1); + vis->x2 = cross-1; + vis2->x1 = cross; + //vis2->alpha /= 2; + //vis2->RenderStyle = STYLE_Add; + + if (vis->idepth < ds->siz1) + { // Left is in back, right is in front + vis->sector = ds->curline->backsector; + vis2->sector = ds->curline->frontsector; + } + else + { // Right is in front, left is in back + vis->sector = ds->curline->frontsector; + vis2->sector = ds->curline->backsector; + } + } + } + } + while (dseg < numdrawsegs && ++spr < numsprites); + + // Repeat for any new sprites that were added. + } + while (start = stop, stop != vissprite_p - vissprites); +} +#endif + +#ifdef __GNUC__ +static void swap(vissprite_t *&a, vissprite_t *&b) +{ + vissprite_t *t = a; + a = b; + b = t; +} +#endif + +void R_SortVisSprites (bool (*compare)(vissprite_t *, vissprite_t *), size_t first) +{ + int i; + vissprite_t **spr; + + vsprcount = int(vissprite_p - &vissprites[first]); + + if (vsprcount == 0) + return; + + if (spritesortersize < MaxVisSprites) + { + if (spritesorter != NULL) + delete[] spritesorter; + spritesorter = new vissprite_t *[MaxVisSprites]; + spritesortersize = MaxVisSprites; + } + + if (!(i_compatflags & COMPATF_SPRITESORT)) + { + for (i = 0, spr = firstvissprite; i < vsprcount; i++, spr++) + { + spritesorter[i] = *spr; + } + } + else + { + // If the compatibility option is on sprites of equal distance need to + // be sorted in inverse order. This is most easily achieved by + // filling the sort array backwards before the sort. + for (i = 0, spr = firstvissprite + vsprcount-1; i < vsprcount; i++, spr--) + { + spritesorter[i] = *spr; + } + } + + std::stable_sort(&spritesorter[0], &spritesorter[vsprcount], compare); +} + +// +// R_DrawSprite +// +void R_DrawSprite (vissprite_t *spr) +{ + static short clipbot[MAXWIDTH]; + static short cliptop[MAXWIDTH]; + drawseg_t *ds; + int i; + int x1, x2; + int r1, r2; + short topclip, botclip; + short *clip1, *clip2; + lighttable_t *colormap = spr->Style.colormap; + F3DFloor *rover; + FDynamicColormap *mybasecolormap; + + // [RH] Check for particles + if (!spr->bIsVoxel && spr->pic == NULL) + { + // kg3D - reject invisible parts + if ((fake3D & FAKE3D_CLIPBOTTOM) && spr->gpos.Z <= sclipBottom) return; + if ((fake3D & FAKE3D_CLIPTOP) && spr->gpos.Z >= sclipTop) return; + R_DrawParticle (spr); + return; + } + + x1 = spr->x1; + x2 = spr->x2; + + // [RH] Quickly reject sprites with bad x ranges. + if (x1 >= x2) + return; + + // [RH] Sprites split behind a one-sided line can also be discarded. + if (spr->sector == NULL) + return; + + // kg3D - reject invisible parts + if ((fake3D & FAKE3D_CLIPBOTTOM) && spr->gzt <= sclipBottom) return; + if ((fake3D & FAKE3D_CLIPTOP) && spr->gzb >= sclipTop) return; + + // kg3D - correct colors now + if (!fixedcolormap && fixedlightlev < 0 && spr->sector->e && spr->sector->e->XFloor.lightlist.Size()) + { + if (!(fake3D & FAKE3D_CLIPTOP)) + { + sclipTop = spr->sector->ceilingplane.ZatPoint(ViewPos); + } + sector_t *sec = NULL; + for (i = spr->sector->e->XFloor.lightlist.Size() - 1; i >= 0; i--) + { + if (sclipTop <= spr->sector->e->XFloor.lightlist[i].plane.Zat0()) + { + rover = spr->sector->e->XFloor.lightlist[i].caster; + if (rover) + { + if (rover->flags & FF_DOUBLESHADOW && sclipTop <= rover->bottom.plane->Zat0()) + { + break; + } + sec = rover->model; + if (rover->flags & FF_FADEWALLS) + { + mybasecolormap = sec->ColorMap; + } + else + { + mybasecolormap = spr->sector->e->XFloor.lightlist[i].extra_colormap; + } + } + break; + } + } + // found new values, recalculate + if (sec) + { + INTBOOL invertcolormap = (spr->Style.RenderStyle.Flags & STYLEF_InvertOverlay); + + if (spr->Style.RenderStyle.Flags & STYLEF_InvertSource) + { + invertcolormap = !invertcolormap; + } + + // Sprites that are added to the scene must fade to black. + if (spr->Style.RenderStyle == LegacyRenderStyles[STYLE_Add] && mybasecolormap->Fade != 0) + { + mybasecolormap = GetSpecialLights(mybasecolormap->Color, 0, mybasecolormap->Desaturate); + } + + if (spr->Style.RenderStyle.Flags & STYLEF_FadeToBlack) + { + if (invertcolormap) + { // Fade to white + mybasecolormap = GetSpecialLights(mybasecolormap->Color, MAKERGB(255,255,255), mybasecolormap->Desaturate); + invertcolormap = false; + } + else + { // Fade to black + mybasecolormap = GetSpecialLights(mybasecolormap->Color, MAKERGB(0,0,0), mybasecolormap->Desaturate); + } + } + + // get light level + if (invertcolormap) + { + mybasecolormap = GetSpecialLights(mybasecolormap->Color, mybasecolormap->Fade.InverseColor(), mybasecolormap->Desaturate); + } + if (fixedlightlev >= 0) + { + spr->Style.colormap = mybasecolormap->Maps + fixedlightlev; + } + else if (!foggy && (spr->renderflags & RF_FULLBRIGHT)) + { // full bright + spr->Style.colormap = mybasecolormap->Maps; + } + else + { // diminished light + spriteshade = LIGHT2SHADE(sec->lightlevel + r_actualextralight); + spr->Style.colormap = mybasecolormap->Maps + (GETPALOOKUP ( + r_SpriteVisibility / MAX(MINZ, (double)spr->depth), spriteshade) << COLORMAPSHIFT); + } + } + } + + // [RH] Initialize the clipping arrays to their largest possible range + // instead of using a special "not clipped" value. This eliminates + // visual anomalies when looking down and should be faster, too. + topclip = 0; + botclip = viewheight; + + // killough 3/27/98: + // Clip the sprite against deep water and/or fake ceilings. + // [RH] rewrote this to be based on which part of the sector is really visible + + double scale = InvZtoScale * spr->idepth; + double hzb = DBL_MIN, hzt = DBL_MAX; + + if (spr->bIsVoxel && spr->floorclip != 0) + { + hzb = spr->gzb; + } + + if (spr->heightsec && !(spr->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC)) + { // only things in specially marked sectors + if (spr->FakeFlatStat != FAKED_AboveCeiling) + { + double hz = spr->heightsec->floorplane.ZatPoint(spr->gpos); + int h = xs_RoundToInt(CenterY - (hz - ViewPos.Z) * scale); + + if (spr->FakeFlatStat == FAKED_BelowFloor) + { // seen below floor: clip top + if (!spr->bIsVoxel && h > topclip) + { + topclip = MIN (h, viewheight); + } + hzt = MIN(hzt, hz); + } + else + { // seen in the middle: clip bottom + if (!spr->bIsVoxel && h < botclip) + { + botclip = MAX (0, h); + } + hzb = MAX(hzb, hz); + } + } + if (spr->FakeFlatStat != FAKED_BelowFloor && !(spr->heightsec->MoreFlags & SECF_FAKEFLOORONLY)) + { + double hz = spr->heightsec->ceilingplane.ZatPoint(spr->gpos); + int h = xs_RoundToInt(CenterY - (hz - ViewPos.Z) * scale); + + if (spr->FakeFlatStat == FAKED_AboveCeiling) + { // seen above ceiling: clip bottom + if (!spr->bIsVoxel && h < botclip) + { + botclip = MAX (0, h); + } + hzb = MAX(hzb, hz); + } + else + { // seen in the middle: clip top + if (!spr->bIsVoxel && h > topclip) + { + topclip = MIN (h, viewheight); + } + hzt = MIN(hzt, hz); + } + } + } + // killough 3/27/98: end special clipping for deep water / fake ceilings + else if (!spr->bIsVoxel && spr->floorclip) + { // [RH] Move floorclip stuff from R_DrawVisSprite to here + //int clip = ((FLOAT2FIXED(CenterY) - FixedMul (spr->texturemid - (spr->pic->GetHeight() << FRACBITS) + spr->floorclip, spr->yscale)) >> FRACBITS); + int clip = xs_RoundToInt(CenterY - (spr->texturemid - spr->pic->GetHeight() + spr->floorclip) * spr->yscale); + if (clip < botclip) + { + botclip = MAX(0, clip); + } + } + + if (fake3D & FAKE3D_CLIPBOTTOM) + { + if (!spr->bIsVoxel) + { + double hz = sclipBottom; + if (spr->fakefloor) + { + double floorz = spr->fakefloor->top.plane->Zat0(); + if (ViewPos.Z > floorz && floorz == sclipBottom ) + { + hz = spr->fakefloor->bottom.plane->Zat0(); + } + } + int h = xs_RoundToInt(CenterY - (hz - ViewPos.Z) * scale); + if (h < botclip) + { + botclip = MAX(0, h); + } + } + hzb = MAX(hzb, sclipBottom); + } + if (fake3D & FAKE3D_CLIPTOP) + { + if (!spr->bIsVoxel) + { + double hz = sclipTop; + if (spr->fakeceiling != NULL) + { + double ceilingZ = spr->fakeceiling->bottom.plane->Zat0(); + if (ViewPos.Z < ceilingZ && ceilingZ == sclipTop) + { + hz = spr->fakeceiling->top.plane->Zat0(); + } + } + int h = xs_RoundToInt(CenterY - (hz - ViewPos.Z) * scale); + if (h > topclip) + { + topclip = MIN(h, viewheight); + } + } + hzt = MIN(hzt, sclipTop); + } + +#if 0 + // [RH] Sprites that were split by a drawseg should also be clipped + // by the sector's floor and ceiling. (Not sure how/if to handle this + // with fake floors, since those already do clipping.) + if (spr->bSplitSprite && + (spr->heightsec == NULL || (spr->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC))) + { + fixed_t h = spr->sector->floorplane.ZatPoint (spr->gx, spr->gy); + h = (centeryfrac - FixedMul (h-viewz, scale)) >> FRACBITS; + if (h < botclip) + { + botclip = MAX (0, h); + } + h = spr->sector->ceilingplane.ZatPoint (spr->gx, spr->gy); + h = (centeryfrac - FixedMul (h-viewz, scale)) >> FRACBITS; + if (h > topclip) + { + topclip = MIN (h, viewheight); + } + } +#endif + + if (topclip >= botclip) + { + spr->Style.colormap = colormap; + return; + } + + i = x2 - x1; + clip1 = clipbot + x1; + clip2 = cliptop + x1; + do + { + *clip1++ = botclip; + *clip2++ = topclip; + } while (--i); + + // Scan drawsegs from end to start for obscuring segs. + // The first drawseg that is closer than the sprite is the clip seg. + + // Modified by Lee Killough: + // (pointer check was originally nonportable + // and buggy, by going past LEFT end of array): + + // for (ds=ds_p-1 ; ds >= drawsegs ; ds--) old buggy code + + for (ds = ds_p; ds-- > firstdrawseg; ) // new -- killough + { + // [ZZ] portal handling here + //if (ds->CurrentPortalUniq != spr->CurrentPortalUniq) + // continue; + // [ZZ] WARNING: uncommenting the two above lines, totally breaks sprite clipping + + // kg3D - no clipping on fake segs + if (ds->fake) continue; + // determine if the drawseg obscures the sprite + if (ds->x1 >= x2 || ds->x2 <= x1 || + (!(ds->silhouette & SIL_BOTH) && ds->maskedtexturecol == -1 && + !ds->bFogBoundary) ) + { + // does not cover sprite + continue; + } + + r1 = MAX (ds->x1, x1); + r2 = MIN (ds->x2, x2); + + float neardepth, fardepth; + if (!spr->bWallSprite) + { + if (ds->sz1 < ds->sz2) + { + neardepth = ds->sz1, fardepth = ds->sz2; + } + else + { + neardepth = ds->sz2, fardepth = ds->sz1; + } + } + // Check if sprite is in front of draw seg: + if ((!spr->bWallSprite && neardepth > spr->depth) || ((spr->bWallSprite || fardepth > spr->depth) && + (spr->gpos.Y - ds->curline->v1->fY()) * (ds->curline->v2->fX() - ds->curline->v1->fX()) - + (spr->gpos.X - ds->curline->v1->fX()) * (ds->curline->v2->fY() - ds->curline->v1->fY()) <= 0)) + { + // seg is behind sprite, so draw the mid texture if it has one + if (ds->CurrentPortalUniq == CurrentPortalUniq && // [ZZ] instead, portal uniq check is made here + (ds->maskedtexturecol != -1 || ds->bFogBoundary)) + R_RenderMaskedSegRange (ds, r1, r2); + continue; + } + + // clip this piece of the sprite + // killough 3/27/98: optimized and made much shorter + // [RH] Optimized further (at least for VC++; + // other compilers should be at least as good as before) + + if (ds->silhouette & SIL_BOTTOM) //bottom sil + { + clip1 = clipbot + r1; + clip2 = openings + ds->sprbottomclip + r1 - ds->x1; + i = r2 - r1; + do + { + if (*clip1 > *clip2) + *clip1 = *clip2; + clip1++; + clip2++; + } while (--i); + } + + if (ds->silhouette & SIL_TOP) // top sil + { + clip1 = cliptop + r1; + clip2 = openings + ds->sprtopclip + r1 - ds->x1; + i = r2 - r1; + do + { + if (*clip1 < *clip2) + *clip1 = *clip2; + clip1++; + clip2++; + } while (--i); + } + } + + // all clipping has been performed, so draw the sprite + + if (!spr->bIsVoxel) + { + mfloorclip = clipbot; + mceilingclip = cliptop; + if (!spr->bWallSprite) + { + R_DrawVisSprite(spr); + } + else + { + R_DrawWallSprite(spr); + } + } + else + { + // If it is completely clipped away, don't bother drawing it. + if (cliptop[x2] >= clipbot[x2]) + { + for (i = x1; i < x2; ++i) + { + if (cliptop[i] < clipbot[i]) + { + break; + } + } + if (i == x2) + { + spr->Style.colormap = colormap; + return; + } + } + // Add everything outside the left and right edges to the clipping array + // for R_DrawVisVoxel(). + if (x1 > 0) + { + clearbufshort(cliptop, x1, viewheight); + } + if (x2 < viewwidth - 1) + { + clearbufshort(cliptop + x2, viewwidth - x2, viewheight); + } + int minvoxely = spr->gzt <= hzt ? 0 : xs_RoundToInt((spr->gzt - hzt) / spr->yscale); + int maxvoxely = spr->gzb > hzb ? INT_MAX : xs_RoundToInt((spr->gzt - hzb) / spr->yscale); + R_DrawVisVoxel(spr, minvoxely, maxvoxely, cliptop, clipbot); + } + spr->Style.colormap = colormap; +} + +// kg3D: +// R_DrawMasked contains sorting +// original renamed to R_DrawMaskedSingle + +void R_DrawMaskedSingle (bool renew) +{ + drawseg_t *ds; + int i; + +#if 0 + R_SplitVisSprites (); +#endif + + for (i = vsprcount; i > 0; i--) + { + if (spritesorter[i-1]->CurrentPortalUniq != CurrentPortalUniq) + continue; // probably another time + R_DrawSprite (spritesorter[i-1]); + } + + // render any remaining masked mid textures + + // Modified by Lee Killough: + // (pointer check was originally nonportable + // and buggy, by going past LEFT end of array): + + // for (ds=ds_p-1 ; ds >= drawsegs ; ds--) old buggy code + + if (renew) + { + fake3D |= FAKE3D_REFRESHCLIP; + } + for (ds = ds_p; ds-- > firstdrawseg; ) // new -- killough + { + // [ZZ] the same as above + if (ds->CurrentPortalUniq != CurrentPortalUniq) + continue; + // kg3D - no fake segs + if (ds->fake) continue; + if (ds->maskedtexturecol != -1 || ds->bFogBoundary) + { + R_RenderMaskedSegRange (ds, ds->x1, ds->x2); + } + } +} + +void R_DrawHeightPlanes(double height); // kg3D - fake planes + +void R_DrawMasked (void) +{ + R_CollectPortals(); + R_SortVisSprites (DrewAVoxel ? sv_compare2d : sv_compare, firstvissprite - vissprites); + + if (height_top == NULL) + { // kg3D - no visible 3D floors, normal rendering + R_DrawMaskedSingle(false); + } + else + { // kg3D - correct sorting + HeightLevel *hl; + + // ceilings + for (hl = height_cur; hl != NULL && hl->height >= ViewPos.Z; hl = hl->prev) + { + if (hl->next) + { + fake3D = FAKE3D_CLIPBOTTOM | FAKE3D_CLIPTOP; + sclipTop = hl->next->height; + } + else + { + fake3D = FAKE3D_CLIPBOTTOM; + } + sclipBottom = hl->height; + R_DrawMaskedSingle(true); + R_DrawHeightPlanes(hl->height); + } + + // floors + fake3D = FAKE3D_DOWN2UP | FAKE3D_CLIPTOP; + sclipTop = height_top->height; + R_DrawMaskedSingle(true); + hl = height_top; + for (hl = height_top; hl != NULL && hl->height < ViewPos.Z; hl = hl->next) + { + R_DrawHeightPlanes(hl->height); + if (hl->next) + { + fake3D = FAKE3D_DOWN2UP | FAKE3D_CLIPTOP | FAKE3D_CLIPBOTTOM; + sclipTop = hl->next->height; + } + else + { + fake3D = FAKE3D_DOWN2UP | FAKE3D_CLIPBOTTOM; + } + sclipBottom = hl->height; + R_DrawMaskedSingle(true); + } + R_3D_DeleteHeights(); + fake3D = 0; + } + R_DrawPlayerSprites (); +} + + +void R_ProjectParticle (particle_t *particle, const sector_t *sector, int shade, int fakeside) +{ + double tr_x, tr_y; + double tx, ty; + double tz, tiz; + double xscale, yscale; + int x1, x2, y1, y2; + vissprite_t* vis; + sector_t* heightsec = NULL; + BYTE* map; + + // [ZZ] Particle not visible through the portal plane + if (CurrentPortal && !!P_PointOnLineSide(particle->Pos, CurrentPortal->dst)) + return; + + // transform the origin point + tr_x = particle->Pos.X - ViewPos.X; + tr_y = particle->Pos.Y - ViewPos.Y; + + tz = tr_x * ViewTanCos + tr_y * ViewTanSin; + + // particle is behind view plane? + if (tz < MINZ) + return; + + tx = tr_x * ViewSin - tr_y * ViewCos; + + // Flip for mirrors + if (MirrorFlags & RF_XFLIP) + { + tx = viewwidth - tx - 1; + } + + // too far off the side? + if (tz <= fabs(tx)) + return; + + tiz = 1 / tz; + xscale = centerx * tiz; + + // calculate edges of the shape + double psize = particle->size / 8.0; + + x1 = MAX(WindowLeft, centerx + xs_RoundToInt((tx - psize) * xscale)); + x2 = MIN(WindowRight, centerx + xs_RoundToInt((tx + psize) * xscale)); + + if (x1 >= x2) + return; + + yscale = xs_RoundToInt(YaspectMul * xscale); + ty = FLOAT2FIXED(particle->Pos.Z - ViewPos.Z); + y1 = xs_RoundToInt(CenterY - (ty + psize) * yscale); + y2 = xs_RoundToInt(CenterY - (ty - psize) * yscale); + + // Clip the particle now. Because it's a point and projected as its subsector is + // entered, we don't need to clip it to drawsegs like a normal sprite. + + // Clip particles behind walls. + if (y1 < ceilingclip[x1]) y1 = ceilingclip[x1]; + if (y1 < ceilingclip[x2-1]) y1 = ceilingclip[x2-1]; + if (y2 >= floorclip[x1]) y2 = floorclip[x1] - 1; + if (y2 >= floorclip[x2-1]) y2 = floorclip[x2-1] - 1; + + if (y1 > y2) + return; + + // Clip particles above the ceiling or below the floor. + heightsec = sector->GetHeightSec(); + + const secplane_t *topplane; + const secplane_t *botplane; + FTextureID toppic; + FTextureID botpic; + + if (heightsec) // only clip things which are in special sectors + { + if (fakeside == FAKED_AboveCeiling) + { + topplane = §or->ceilingplane; + botplane = &heightsec->ceilingplane; + toppic = sector->GetTexture(sector_t::ceiling); + botpic = heightsec->GetTexture(sector_t::ceiling); + map = heightsec->ColorMap->Maps; + } + else if (fakeside == FAKED_BelowFloor) + { + topplane = &heightsec->floorplane; + botplane = §or->floorplane; + toppic = heightsec->GetTexture(sector_t::floor); + botpic = sector->GetTexture(sector_t::floor); + map = heightsec->ColorMap->Maps; + } + else + { + topplane = &heightsec->ceilingplane; + botplane = &heightsec->floorplane; + toppic = heightsec->GetTexture(sector_t::ceiling); + botpic = heightsec->GetTexture(sector_t::floor); + map = sector->ColorMap->Maps; + } + } + else + { + topplane = §or->ceilingplane; + botplane = §or->floorplane; + toppic = sector->GetTexture(sector_t::ceiling); + botpic = sector->GetTexture(sector_t::floor); + map = sector->ColorMap->Maps; + } + + if (botpic != skyflatnum && particle->Pos.Z < botplane->ZatPoint (particle->Pos)) + return; + if (toppic != skyflatnum && particle->Pos.Z >= topplane->ZatPoint (particle->Pos)) + return; + + // store information in a vissprite + vis = R_NewVisSprite (); + vis->CurrentPortalUniq = CurrentPortalUniq; + vis->heightsec = heightsec; + vis->xscale = FLOAT2FIXED(xscale); + vis->yscale = (float)xscale; +// vis->yscale *= InvZtoScale; + vis->depth = (float)tz; + vis->idepth = float(1 / tz); + vis->gpos = { (float)particle->Pos.X, (float)particle->Pos.Y, (float)particle->Pos.Z }; + vis->y1 = y1; + vis->y2 = y2; + vis->x1 = x1; + vis->x2 = x2; + vis->Translation = 0; + vis->startfrac = 255 & (particle->color >>24); + vis->pic = NULL; + vis->bIsVoxel = false; + vis->renderflags = particle->trans; + vis->FakeFlatStat = fakeside; + vis->floorclip = 0; + vis->ColormapNum = 0; + + if (fixedlightlev >= 0) + { + vis->Style.colormap = map + fixedlightlev; + } + else if (fixedcolormap) + { + vis->Style.colormap = fixedcolormap; + } + else if (particle->bright) + { + vis->Style.colormap = map; + } + else + { + // Particles are slightly more visible than regular sprites. + vis->ColormapNum = GETPALOOKUP(tiz * r_SpriteVisibility * 0.5, shade); + vis->Style.colormap = map + (vis->ColormapNum << COLORMAPSHIFT); + } +} + +static void R_DrawMaskedSegsBehindParticle (const vissprite_t *vis) +{ + const int x1 = vis->x1; + const int x2 = vis->x2; + + // Draw any masked textures behind this particle so that when the + // particle is drawn, it will be in front of them. + for (unsigned int p = InterestingDrawsegs.Size(); p-- > FirstInterestingDrawseg; ) + { + drawseg_t *ds = &drawsegs[InterestingDrawsegs[p]]; + // kg3D - no fake segs + if(ds->fake) continue; + if (ds->x1 >= x2 || ds->x2 <= x1) + { + continue; + } + if ((ds->siz2 - ds->siz1) * ((x2 + x1)/2 - ds->sx1) / (ds->sx2 - ds->sx1) + ds->siz1 < vis->idepth) + { + // [ZZ] only draw stuff that's inside the same portal as the particle, other portals will care for themselves + if (ds->CurrentPortalUniq == vis->CurrentPortalUniq) + R_RenderMaskedSegRange (ds, MAX(ds->x1, x1), MIN(ds->x2, x2)); + } + } +} + +void R_DrawParticle (vissprite_t *vis) +{ + DWORD *bg2rgb; + int spacing; + BYTE *dest; + DWORD fg; + BYTE color = vis->Style.colormap[vis->startfrac]; + int yl = vis->y1; + int ycount = vis->y2 - yl + 1; + int x1 = vis->x1; + int countbase = vis->x2 - x1; + + R_DrawMaskedSegsBehindParticle (vis); + + // vis->renderflags holds translucency level (0-255) + { + fixed_t fglevel, bglevel; + DWORD *fg2rgb; + + fglevel = ((vis->renderflags + 1) << 8) & ~0x3ff; + bglevel = FRACUNIT-fglevel; + fg2rgb = Col2RGB8[fglevel>>10]; + bg2rgb = Col2RGB8[bglevel>>10]; + fg = fg2rgb[color]; + } + + /* + + spacing = RenderTarget->GetPitch() - countbase; + dest = ylookup[yl] + x1 + dc_destorg; + + do + { + int count = countbase; + do + { + DWORD bg = bg2rgb[*dest]; + bg = (fg+bg) | 0x1f07c1f; + *dest++ = RGB32k.All[bg & (bg>>15)]; + } while (--count); + dest += spacing; + } while (--ycount);*/ + + // original was row-wise + // width = countbase + // height = ycount + + spacing = RenderTarget->GetPitch(); + + for (int x = x1; x < (x1+countbase); x++) + { + dc_x = x; + if (R_ClipSpriteColumnWithPortals(vis)) + continue; + dest = ylookup[yl] + x + dc_destorg; + for (int y = 0; y < ycount; y++) + { + DWORD bg = bg2rgb[*dest]; + bg = (fg+bg) | 0x1f07c1f; + *dest = RGB32k.All[bg & (bg>>15)]; + dest += spacing; + } + } +} + +extern double BaseYaspectMul;; + +void R_DrawVoxel(const FVector3 &globalpos, FAngle viewangle, + const FVector3 &dasprpos, angle_t dasprang, + fixed_t daxscale, fixed_t dayscale, FVoxel *voxobj, + lighttable_t *colormap, short *daumost, short *dadmost, int minslabz, int maxslabz, int flags) +{ + int i, j, k, x, y, syoff, ggxstart, ggystart, nxoff; + fixed_t cosang, sinang, sprcosang, sprsinang; + int backx, backy, gxinc, gyinc; + int daxscalerecip, dayscalerecip, cnt, gxstart, gystart, dazscale; + int lx, rx, nx, ny, x1=0, y1=0, x2=0, y2=0, yinc=0; + int yoff, xs=0, ys=0, xe, ye, xi=0, yi=0, cbackx, cbacky, dagxinc, dagyinc; + kvxslab_t *voxptr, *voxend; + FVoxelMipLevel *mip; + int z1a[64], z2a[64], yplc[64]; + + const int nytooclose = centerxwide * 2100, nytoofar = 32768*32768 - 1048576; + const int xdimenscale = FLOAT2FIXED(centerxwide * YaspectMul / 160); + const double centerxwide_f = centerxwide; + const double centerxwidebig_f = centerxwide_f * 65536*65536*8; + + // Convert to Build's coordinate system. + fixed_t globalposx = xs_Fix<4>::ToFix(globalpos.X); + fixed_t globalposy = xs_Fix<4>::ToFix(-globalpos.Y); + fixed_t globalposz = xs_Fix<8>::ToFix(-globalpos.Z); + + fixed_t dasprx = xs_Fix<4>::ToFix(dasprpos.X); + fixed_t daspry = xs_Fix<4>::ToFix(-dasprpos.Y); + fixed_t dasprz = xs_Fix<8>::ToFix(-dasprpos.Z); + + // Shift the scales from 16 bits of fractional precision to 6. + // Also do some magic voodoo scaling to make them the right size. + daxscale = daxscale / (0xC000 >> 6); + dayscale = dayscale / (0xC000 >> 6); + + angle_t viewang = viewangle.BAMs(); + cosang = finecosine[viewang >> ANGLETOFINESHIFT] >> 2; + sinang = -finesine[viewang >> ANGLETOFINESHIFT] >> 2; + sprcosang = finecosine[dasprang >> ANGLETOFINESHIFT] >> 2; + sprsinang = -finesine[dasprang >> ANGLETOFINESHIFT] >> 2; + + R_SetupDrawSlab(colormap); + + // Select mip level + i = abs(DMulScale6(dasprx - globalposx, cosang, daspry - globalposy, sinang)); + i = DivScale6(i, MIN(daxscale, dayscale)); + j = xs_Fix<13>::ToFix(FocalLengthX); + for (k = 0; i >= j && k < voxobj->NumMips; ++k) + { + i >>= 1; + } + if (k >= voxobj->NumMips) k = voxobj->NumMips - 1; + + mip = &voxobj->Mips[k]; if (mip->SlabData == NULL) return; + + minslabz >>= k; + maxslabz >>= k; + + daxscale <<= (k+8); dayscale <<= (k+8); + dazscale = FixedDiv(dayscale, FLOAT2FIXED(BaseYaspectMul)); + daxscale = fixed_t(daxscale / YaspectMul); + daxscale = Scale(daxscale, xdimenscale, centerxwide << 9); + dayscale = Scale(dayscale, FixedMul(xdimenscale, viewingrangerecip), centerxwide << 9); + + daxscalerecip = (1<<30) / daxscale; + dayscalerecip = (1<<30) / dayscale; + + fixed_t piv_x = fixed_t(mip->Pivot.X*256.); + fixed_t piv_y = fixed_t(mip->Pivot.Y*256.); + fixed_t piv_z = fixed_t(mip->Pivot.Z*256.); + + x = FixedMul(globalposx - dasprx, daxscalerecip); + y = FixedMul(globalposy - daspry, daxscalerecip); + backx = (DMulScale10(x, sprcosang, y, sprsinang) + piv_x) >> 8; + backy = (DMulScale10(y, sprcosang, x, -sprsinang) + piv_y) >> 8; + cbackx = clamp(backx, 0, mip->SizeX - 1); + cbacky = clamp(backy, 0, mip->SizeY - 1); + + sprcosang = MulScale14(daxscale, sprcosang); + sprsinang = MulScale14(daxscale, sprsinang); + + x = (dasprx - globalposx) - DMulScale18(piv_x, sprcosang, piv_y, -sprsinang); + y = (daspry - globalposy) - DMulScale18(piv_y, sprcosang, piv_x, sprsinang); + + cosang = FixedMul(cosang, dayscalerecip); + sinang = FixedMul(sinang, dayscalerecip); + + gxstart = y*cosang - x*sinang; + gystart = x*cosang + y*sinang; + gxinc = DMulScale10(sprsinang, cosang, sprcosang, -sinang); + gyinc = DMulScale10(sprcosang, cosang, sprsinang, sinang); + if ((abs(globalposz - dasprz) >> 10) >= abs(dazscale)) return; + + x = 0; y = 0; j = MAX(mip->SizeX, mip->SizeY); + fixed_t *ggxinc = (fixed_t *)alloca((j + 1) * sizeof(fixed_t) * 2); + fixed_t *ggyinc = ggxinc + (j + 1); + for (i = 0; i <= j; i++) + { + ggxinc[i] = x; x += gxinc; + ggyinc[i] = y; y += gyinc; + } + + syoff = DivScale21(globalposz - dasprz, FixedMul(dazscale, 0xE800)) + (piv_z << 7); + yoff = (abs(gxinc) + abs(gyinc)) >> 1; + + for (cnt = 0; cnt < 8; cnt++) + { + switch (cnt) + { + case 0: xs = 0; ys = 0; xi = 1; yi = 1; break; + case 1: xs = mip->SizeX-1; ys = 0; xi = -1; yi = 1; break; + case 2: xs = 0; ys = mip->SizeY-1; xi = 1; yi = -1; break; + case 3: xs = mip->SizeX-1; ys = mip->SizeY-1; xi = -1; yi = -1; break; + case 4: xs = 0; ys = cbacky; xi = 1; yi = 2; break; + case 5: xs = mip->SizeX-1; ys = cbacky; xi = -1; yi = 2; break; + case 6: xs = cbackx; ys = 0; xi = 2; yi = 1; break; + case 7: xs = cbackx; ys = mip->SizeY-1; xi = 2; yi = -1; break; + } + xe = cbackx; ye = cbacky; + if (cnt < 4) + { + if ((xi < 0) && (xe >= xs)) continue; + if ((xi > 0) && (xe <= xs)) continue; + if ((yi < 0) && (ye >= ys)) continue; + if ((yi > 0) && (ye <= ys)) continue; + } + else + { + if ((xi < 0) && (xe > xs)) continue; + if ((xi > 0) && (xe < xs)) continue; + if ((yi < 0) && (ye > ys)) continue; + if ((yi > 0) && (ye < ys)) continue; + xe += xi; ye += yi; + } + + i = ksgn(ys-backy)+ksgn(xs-backx)*3+4; + switch(i) + { + case 6: case 7: x1 = 0; y1 = 0; break; + case 8: case 5: x1 = gxinc; y1 = gyinc; break; + case 0: case 3: x1 = gyinc; y1 = -gxinc; break; + case 2: case 1: x1 = gxinc+gyinc; y1 = gyinc-gxinc; break; + } + switch(i) + { + case 2: case 5: x2 = 0; y2 = 0; break; + case 0: case 1: x2 = gxinc; y2 = gyinc; break; + case 8: case 7: x2 = gyinc; y2 = -gxinc; break; + case 6: case 3: x2 = gxinc+gyinc; y2 = gyinc-gxinc; break; + } + BYTE oand = (1 << int(xs 0) { dagxinc = gxinc; dagyinc = FixedMul(gyinc, viewingrangerecip); } + else { dagxinc = -gxinc; dagyinc = -FixedMul(gyinc, viewingrangerecip); } + + /* Fix for non 90 degree viewing ranges */ + nxoff = FixedMul(x2 - x1, viewingrangerecip); + x1 = FixedMul(x1, viewingrangerecip); + + ggxstart = gxstart + ggyinc[ys]; + ggystart = gystart - ggxinc[ys]; + + for (x = xs; x != xe; x += xi) + { + BYTE *slabxoffs = &mip->SlabData[mip->OffsetX[x]]; + short *xyoffs = &mip->OffsetXY[x * (mip->SizeY + 1)]; + + nx = FixedMul(ggxstart + ggxinc[x], viewingrangerecip) + x1; + ny = ggystart + ggyinc[x]; + for (y = ys; y != ye; y += yi, nx += dagyinc, ny -= dagxinc) + { + if ((ny <= nytooclose) || (ny >= nytoofar)) continue; + voxptr = (kvxslab_t *)(slabxoffs + xyoffs[y]); + voxend = (kvxslab_t *)(slabxoffs + xyoffs[y+1]); + if (voxptr >= voxend) continue; + + lx = xs_RoundToInt(nx * centerxwide_f / (ny + y1)) + centerx; + if (lx < 0) lx = 0; + rx = xs_RoundToInt((nx + nxoff) * centerxwide_f / (ny + y2)) + centerx; + if (rx > viewwidth) rx = viewwidth; + if (rx <= lx) continue; + + if (flags & DVF_MIRRORED) + { + int t = viewwidth - lx; + lx = viewwidth - rx; + rx = t; + } + + fixed_t l1 = xs_RoundToInt(centerxwidebig_f / (ny - yoff)); + fixed_t l2 = xs_RoundToInt(centerxwidebig_f / (ny + yoff)); + for (; voxptr < voxend; voxptr = (kvxslab_t *)((BYTE *)voxptr + voxptr->zleng + 3)) + { + const BYTE *col = voxptr->col; + int zleng = voxptr->zleng; + int ztop = voxptr->ztop; + fixed_t z1, z2; + + if (ztop < minslabz) + { + int diff = minslabz - ztop; + ztop = minslabz; + col += diff; + zleng -= diff; + } + if (ztop + zleng > maxslabz) + { + int diff = ztop + zleng - maxslabz; + zleng -= diff; + } + if (zleng <= 0) continue; + + j = (ztop << 15) - syoff; + if (j < 0) + { + k = j + (zleng << 15); + if (k < 0) + { + if ((voxptr->backfacecull & oand32) == 0) continue; + z2 = MulScale32(l2, k) + centery; /* Below slab */ + } + else + { + if ((voxptr->backfacecull & oand) == 0) continue; /* Middle of slab */ + z2 = MulScale32(l1, k) + centery; + } + z1 = MulScale32(l1, j) + centery; + } + else + { + if ((voxptr->backfacecull & oand16) == 0) continue; + z1 = MulScale32(l2, j) + centery; /* Above slab */ + z2 = MulScale32(l1, j + (zleng << 15)) + centery; + } + + if (z2 <= z1) continue; + + if (zleng == 1) + { + yinc = 0; + } + else + { + if (z2-z1 >= 1024) yinc = FixedDiv(zleng, z2 - z1); + else yinc = (((1 << 24) - 1) / (z2 - z1)) * zleng >> 8; + } + // [RH] Clip each column separately, not just by the first one. + for (int stripwidth = MIN(countof(z1a), rx - lx), lxt = lx; + lxt < rx; + (lxt += countof(z1a)), stripwidth = MIN(countof(z1a), rx - lxt)) + { + // Calculate top and bottom pixels locations + for (int xxx = 0; xxx < stripwidth; ++xxx) + { + if (zleng == 1) + { + yplc[xxx] = 0; + z1a[xxx] = MAX(z1, daumost[lxt + xxx]); + } + else + { + if (z1 < daumost[lxt + xxx]) + { + yplc[xxx] = yinc * (daumost[lxt + xxx] - z1); + z1a[xxx] = daumost[lxt + xxx]; + } + else + { + yplc[xxx] = 0; + z1a[xxx] = z1; + } + } + z2a[xxx] = MIN(z2, dadmost[lxt + xxx]); + } + // Find top and bottom pixels that match and draw them as one strip + for (int xxl = 0, xxr; xxl < stripwidth; ) + { + if (z1a[xxl] >= z2a[xxl]) + { // No column here + xxl++; + continue; + } + int z1 = z1a[xxl]; + int z2 = z2a[xxl]; + // How many columns share the same extents? + for (xxr = xxl + 1; xxr < stripwidth; ++xxr) + { + if (z1a[xxr] != z1 || z2a[xxr] != z2) + break; + } + + if (!(flags & DVF_OFFSCREEN)) + { + // Draw directly to the screen. + R_DrawSlab(xxr - xxl, yplc[xxl], z2 - z1, yinc, col, ylookup[z1] + lxt + xxl + dc_destorg); + } + else + { + // Record the area covered and possibly draw to an offscreen buffer. + dc_yl = z1; + dc_yh = z2 - 1; + dc_count = z2 - z1; + dc_iscale = yinc; + for (int x = xxl; x < xxr; ++x) + { + OffscreenCoverageBuffer->InsertSpan(lxt + x, z1, z2); + if (!(flags & DVF_SPANSONLY)) + { + dc_x = lxt + x; + rt_initcols(OffscreenColorBuffer + (dc_x & ~3) * OffscreenBufferHeight); + dc_source = col; + dc_texturefrac = yplc[xxl]; + hcolfunc_pre(); + } + } + } + xxl = xxr; + } + } + } + } + } + } +} + +//========================================================================== +// +// FCoverageBuffer Constructor +// +//========================================================================== + +FCoverageBuffer::FCoverageBuffer(int lists) + : Spans(NULL), FreeSpans(NULL) +{ + NumLists = lists; + Spans = new Span *[lists]; + memset(Spans, 0, sizeof(Span*)*lists); +} + +//========================================================================== +// +// FCoverageBuffer Destructor +// +//========================================================================== + +FCoverageBuffer::~FCoverageBuffer() +{ + if (Spans != NULL) + { + delete[] Spans; + } +} + +//========================================================================== +// +// FCoverageBuffer :: Clear +// +//========================================================================== + +void FCoverageBuffer::Clear() +{ + SpanArena.FreeAll(); + memset(Spans, 0, sizeof(Span*)*NumLists); + FreeSpans = NULL; +} + +//========================================================================== +// +// FCoverageBuffer :: InsertSpan +// +// start is inclusive. +// stop is exclusive. +// +//========================================================================== + +void FCoverageBuffer::InsertSpan(int listnum, int start, int stop) +{ + assert(unsigned(listnum) < NumLists); + assert(start < stop); + + Span **span_p = &Spans[listnum]; + Span *span; + + if (*span_p == NULL || (*span_p)->Start > stop) + { // This list is empty or the first entry is after this one, so we can just insert the span. + goto addspan; + } + + // Insert the new span in order, merging with existing ones. + while (*span_p != NULL) + { + if ((*span_p)->Stop < start) // ===== (existing span) + { // Span ends before this one starts. // ++++ (new span) + span_p = &(*span_p)->NextSpan; + continue; + } + + // Does the new span overlap or abut the existing one? + if ((*span_p)->Start <= start) + { + if ((*span_p)->Stop >= stop) // ============= + { // The existing span completely covers this one. // +++++ + return; + } +extend: // Extend the existing span with the new one. // ====== + span = *span_p; // +++++++ + span->Stop = stop; // (or) +++++ + + // Free up any spans we just covered up. + span_p = &(*span_p)->NextSpan; + while (*span_p != NULL && (*span_p)->Start <= stop && (*span_p)->Stop <= stop) + { + Span *span = *span_p; // ====== ====== + *span_p = span->NextSpan; // +++++++++++++ + span->NextSpan = FreeSpans; + FreeSpans = span; + } + if (*span_p != NULL && (*span_p)->Start <= stop) // ======= ======== + { // Our new span connects two existing spans. // ++++++++++++++ + // They should all be collapsed into a single span. + span->Stop = (*span_p)->Stop; + span = *span_p; + *span_p = span->NextSpan; + span->NextSpan = FreeSpans; + FreeSpans = span; + } + goto check; + } + else if ((*span_p)->Start <= stop) // ===== + { // The new span extends the existing span from // ++++ + // the beginning. // (or) ++++ + (*span_p)->Start = start; + if ((*span_p)->Stop < stop) + { // The new span also extends the existing span // ====== + // at the bottom // ++++++++++++++ + goto extend; + } + goto check; + } + else // ====== + { // No overlap, so insert a new span. // +++++ + goto addspan; + } + } + // Append a new span to the end of the list. +addspan: + span = AllocSpan(); + span->NextSpan = *span_p; + span->Start = start; + span->Stop = stop; + *span_p = span; +check: +#ifdef _DEBUG + // Validate the span list: Spans must be in order, and there must be + // at least one pixel between spans. + for (span = Spans[listnum]; span != NULL; span = span->NextSpan) + { + assert(span->Start < span->Stop); + if (span->NextSpan != NULL) + { + assert(span->Stop < span->NextSpan->Start); + } + } +#endif + ; +} + +//========================================================================== +// +// FCoverageBuffer :: AllocSpan +// +//========================================================================== + +FCoverageBuffer::Span *FCoverageBuffer::AllocSpan() +{ + Span *span; + + if (FreeSpans != NULL) + { + span = FreeSpans; + FreeSpans = span->NextSpan; + } + else + { + span = (Span *)SpanArena.Alloc(sizeof(Span)); + } + return span; +} + +//========================================================================== +// +// R_CheckOffscreenBuffer +// +// Allocates the offscreen coverage buffer and optionally the offscreen +// color buffer. If they already exist but are the wrong size, they will +// be reallocated. +// +//========================================================================== + +void R_CheckOffscreenBuffer(int width, int height, bool spansonly) +{ + if (OffscreenCoverageBuffer == NULL) + { + assert(OffscreenColorBuffer == NULL && "The color buffer cannot exist without the coverage buffer"); + OffscreenCoverageBuffer = new FCoverageBuffer(width); + } + else if (OffscreenCoverageBuffer->NumLists != (unsigned)width) + { + delete OffscreenCoverageBuffer; + OffscreenCoverageBuffer = new FCoverageBuffer(width); + if (OffscreenColorBuffer != NULL) + { + delete[] OffscreenColorBuffer; + OffscreenColorBuffer = NULL; + } + } + else + { + OffscreenCoverageBuffer->Clear(); + } + + if (!spansonly) + { + if (OffscreenColorBuffer == NULL) + { + OffscreenColorBuffer = new BYTE[width * height]; + } + else if (OffscreenBufferWidth != width || OffscreenBufferHeight != height) + { + delete[] OffscreenColorBuffer; + OffscreenColorBuffer = new BYTE[width * height]; + } + } + OffscreenBufferWidth = width; + OffscreenBufferHeight = height; +} diff --git a/src/r_things.h b/src/r_things.h new file mode 100644 index 000000000..97010713a --- /dev/null +++ b/src/r_things.h @@ -0,0 +1,145 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id:$ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This source is available for distribution and/or modification +// only under the terms of the DOOM Source Code License as +// published by id Software. All rights reserved. +// +// The source is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License +// for more details. +// +// DESCRIPTION: +// Rendering of moving objects, sprites. +// +//----------------------------------------------------------------------------- + + +#ifndef __R_THINGS__ +#define __R_THINGS__ + +#include "r_bsp.h" + +// A vissprite_t is a thing +// that will be drawn during a refresh. +// I.e. a sprite object that is partly visible. + +struct vissprite_t +{ + struct posang + { + FVector3 vpos; // view origin + FAngle vang; // view angle + }; + + short x1, x2; + FVector3 gpos; // origin in world coordinates + union + { + float gzb, gzt; // global bottom / top for silhouette clipping + int y1, y2; // top / bottom of particle on screen + }; + angle_t angle; + fixed_t xscale; + float yscale; + float depth; + float idepth; // 1/z + float deltax, deltay; + DWORD FillColor; + double floorclip; + union + { + FTexture *pic; + struct FVoxel *voxel; + }; + union + { + // Used by face sprites + struct + { + double texturemid; + fixed_t startfrac; // horizontal position of x1 + fixed_t xiscale; // negative if flipped + }; + // Used by wall sprites + FWallCoords wallc; + // Used by voxels + posang pa; + }; + sector_t *heightsec; // killough 3/27/98: height sector for underwater/fake ceiling + sector_t *sector; // [RH] sector this sprite is in + F3DFloor *fakefloor; + F3DFloor *fakeceiling; + BYTE bIsVoxel:1; // [RH] Use voxel instead of pic + BYTE bWallSprite:1; // [RH] This is a wall sprite + BYTE bSplitSprite:1; // [RH] Sprite was split by a drawseg + BYTE bInMirror:1; // [RH] Sprite is "inside" a mirror + BYTE FakeFlatStat; // [RH] which side of fake/floor ceiling sprite is on + BYTE ColormapNum; // Which colormap is rendered (needed for shaded drawer) + short renderflags; + DWORD Translation; // [RH] for color translation + visstyle_t Style; + int CurrentPortalUniq; // [ZZ] to identify the portal that this thing is in. used for clipping. + + vissprite_t() {} +}; + +struct particle_t; + +void R_DrawParticle (vissprite_t *); +void R_ProjectParticle (particle_t *, const sector_t *sector, int shade, int fakeside); + +extern int MaxVisSprites; + +extern vissprite_t **vissprites, **firstvissprite; +extern vissprite_t **vissprite_p; + +// Constant arrays used for psprite clipping +// and initializing clipping. +extern short zeroarray[MAXWIDTH]; +extern short screenheightarray[MAXWIDTH]; + +// vars for R_DrawMaskedColumn +extern short* mfloorclip; +extern short* mceilingclip; +extern double spryscale; +extern double sprtopscreen; +extern bool sprflipvert; + +extern double pspritexscale; +extern double pspritexiscale; +extern double pspriteyscale; + +extern FTexture *WallSpriteTile; + + +void R_DrawMaskedColumn (const BYTE *column, const FTexture::Span *spans); +void R_WallSpriteColumn (void (*drawfunc)(const BYTE *column, const FTexture::Span *spans)); + +void R_CacheSprite (spritedef_t *sprite); +void R_SortVisSprites (int (*compare)(const void *, const void *), size_t first); +void R_AddSprites (sector_t *sec, int lightlevel, int fakeside); +void R_AddPSprites (); +void R_DrawSprites (); +void R_ClearSprites (); +void R_DrawMasked (); +void R_DrawRemainingPlayerSprites (); + +void R_CheckOffscreenBuffer(int width, int height, bool spansonly); + +enum { DVF_OFFSCREEN = 1, DVF_SPANSONLY = 2, DVF_MIRRORED = 4 }; + +void R_DrawVoxel(const FVector3 &viewpos, FAngle viewangle, + const FVector3 &sprpos, angle_t dasprang, + fixed_t daxscale, fixed_t dayscale, struct FVoxel *voxobj, + lighttable_t *colormap, short *daumost, short *dadmost, int minslabz, int maxslabz, int flags); + +void R_ClipVisSprite (vissprite_t *vis, int xl, int xh); + + +#endif diff --git a/src/v_draw.cpp b/src/v_draw.cpp index a73cc6fd7..0a9b75b19 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -32,7 +32,7 @@ ** */ -#define NO_SWRENDER // set this if you want to exclude the software renderer. Without software renderer the base implementations of DrawTextureV and FillSimplePoly need to be disabled because they depend on it. +// #define NO_SWRENDER // set this if you want to exclude the software renderer. Without software renderer the base implementations of DrawTextureV and FillSimplePoly need to be disabled because they depend on it. #include #include @@ -42,6 +42,11 @@ #include "m_swap.h" #include "r_defs.h" #include "r_utility.h" +#ifndef NO_SWRENDER +#include "r_draw.h" +#include "r_main.h" +#include "r_things.h" +#endif #include "r_data/r_translate.h" #include "doomstat.h" #include "v_palette.h" diff --git a/src/win32/fb_d3d9.cpp b/src/win32/fb_d3d9.cpp new file mode 100644 index 000000000..efdced151 --- /dev/null +++ b/src/win32/fb_d3d9.cpp @@ -0,0 +1,3870 @@ +/* +** fb_d3d9.cpp +** Code to let ZDoom use Direct3D 9 as a simple framebuffer +** +**--------------------------------------------------------------------------- +** Copyright 1998-2011 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** This file does _not_ implement hardware-acclerated 3D rendering. It is +** just a means of getting the pixel data to the screen in a more reliable +** method on modern hardware by copying the entire frame to a texture, +** drawing that to the screen, and presenting. +** +** That said, it does implement hardware-accelerated 2D rendering. +*/ + +// HEADER FILES ------------------------------------------------------------ + +#ifdef _DEBUG +#define D3D_DEBUG_INFO +#endif +#define DIRECT3D_VERSION 0x0900 +#define WIN32_LEAN_AND_MEAN + +#include +#include + +#include + +#define USE_WINDOWS_DWORD +#include "doomtype.h" + +#include "c_dispatch.h" +#include "templates.h" +#include "i_system.h" +#include "i_video.h" +#include "i_input.h" +#include "v_video.h" +#include "v_pfx.h" +#include "stats.h" +#include "doomerrors.h" +#include "r_main.h" +#include "r_data/r_translate.h" +#include "f_wipe.h" +#include "sbar.h" +#include "win32iface.h" +#include "doomstat.h" +#include "v_palette.h" +#include "w_wad.h" +#include "r_data/colormaps.h" +#include "SkylineBinPack.h" + +// MACROS ------------------------------------------------------------------ + +// The number of points for the vertex buffer. +#define NUM_VERTS 10240 + +// The number of indices for the index buffer. +#define NUM_INDEXES ((NUM_VERTS * 6) / 4) + +// The number of quads we can batch together. +#define MAX_QUAD_BATCH (NUM_INDEXES / 6) + +// The default size for a texture atlas. +#define DEF_ATLAS_WIDTH 512 +#define DEF_ATLAS_HEIGHT 512 + +// TYPES ------------------------------------------------------------------- + +IMPLEMENT_CLASS(D3DFB) + +struct D3DFB::PackedTexture +{ + D3DFB::Atlas *Owner; + + PackedTexture **Prev, *Next; + + // Pixels this image covers + RECT Area; + + // Texture coordinates for this image + float Left, Top, Right, Bottom; + + // Texture has extra space on the border? + bool Padded; +}; + +struct D3DFB::Atlas +{ + Atlas(D3DFB *fb, int width, int height, D3DFORMAT format); + ~Atlas(); + + PackedTexture *AllocateImage(const Rect &rect, bool padded); + void FreeBox(PackedTexture *box); + + SkylineBinPack Packer; + Atlas *Next; + IDirect3DTexture9 *Tex; + D3DFORMAT Format; + PackedTexture *UsedList; // Boxes that contain images + int Width, Height; + bool OneUse; +}; + +class D3DTex : public FNativeTexture +{ +public: + D3DTex(FTexture *tex, D3DFB *fb, bool wrapping); + ~D3DTex(); + + FTexture *GameTex; + D3DFB::PackedTexture *Box; + + D3DTex **Prev; + D3DTex *Next; + + bool IsGray; + + bool Create(D3DFB *fb, bool wrapping); + bool Update(); + bool CheckWrapping(bool wrapping); + D3DFORMAT GetTexFormat(); + FTextureFormat ToTexFmt(D3DFORMAT fmt); +}; + +class D3DPal : public FNativePalette +{ +public: + D3DPal(FRemapTable *remap, D3DFB *fb); + ~D3DPal(); + + D3DPal **Prev; + D3DPal *Next; + + IDirect3DTexture9 *Tex; + D3DCOLOR BorderColor; + bool DoColorSkip; + + bool Update(); + + FRemapTable *Remap; + int RoundedPaletteSize; +}; + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +void DoBlending (const PalEntry *from, PalEntry *to, int count, int r, int g, int b, int a); + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +extern HWND Window; +extern IVideo *Video; +extern BOOL AppActive; +extern int SessionState; +extern bool VidResizing; + +EXTERN_CVAR (Bool, fullscreen) +EXTERN_CVAR (Float, Gamma) +EXTERN_CVAR (Bool, vid_vsync) +EXTERN_CVAR (Float, transsouls) +EXTERN_CVAR (Int, vid_refreshrate) + +extern IDirect3D9 *D3D; + +extern cycle_t BlitCycles; + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +const char *const D3DFB::ShaderNames[D3DFB::NUM_SHADERS] = +{ + "NormalColor.pso", + "NormalColorPal.pso", + "NormalColorInv.pso", + "NormalColorPalInv.pso", + + "RedToAlpha.pso", + "RedToAlphaInv.pso", + + "VertexColor.pso", + + "SpecialColormap.pso", + "SpecialColorMapPal.pso", + + "InGameColormap.pso", + "InGameColormapDesat.pso", + "InGameColormapInv.pso", + "InGameColormapInvDesat.pso", + "InGameColormapPal.pso", + "InGameColormapPalDesat.pso", + "InGameColormapPalInv.pso", + "InGameColormapPalInvDesat.pso", + + "BurnWipe.pso", + "GammaCorrection.pso", +}; + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +CUSTOM_CVAR(Bool, vid_hw2d, true, CVAR_NOINITCALL) +{ + V_SetBorderNeedRefresh(); + ST_SetNeedRefresh(); +} + +CVAR(Bool, d3d_antilag, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR(Int, d3d_showpacks, 0, 0) +CVAR(Bool, vid_hwaalines, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// D3DFB - Constructor +// +//========================================================================== + +D3DFB::D3DFB (UINT adapter, int width, int height, bool fullscreen) + : BaseWinFB (width, height) +{ + D3DPRESENT_PARAMETERS d3dpp; + + LastHR = 0; + + Adapter = adapter; + D3DDevice = NULL; + VertexBuffer = NULL; + IndexBuffer = NULL; + FBTexture = NULL; + TempRenderTexture = NULL; + RenderTexture[0] = NULL; + RenderTexture[1] = NULL; + InitialWipeScreen = NULL; + ScreenshotTexture = NULL; + ScreenshotSurface = NULL; + FinalWipeScreen = NULL; + PaletteTexture = NULL; + GammaTexture = NULL; + FrontCopySurface = NULL; + for (int i = 0; i < NUM_SHADERS; ++i) + { + Shaders[i] = NULL; + } + GammaShader = NULL; + BlockSurface[0] = NULL; + BlockSurface[1] = NULL; + VSync = vid_vsync; + BlendingRect.left = 0; + BlendingRect.top = 0; + BlendingRect.right = FBWidth; + BlendingRect.bottom = FBHeight; + In2D = 0; + Palettes = NULL; + Textures = NULL; + Accel2D = true; + GatheringWipeScreen = false; + ScreenWipe = NULL; + InScene = false; + QuadExtra = new BufferedTris[MAX_QUAD_BATCH]; + Atlases = NULL; + PixelDoubling = 0; + SkipAt = -1; + CurrRenderTexture = 0; + RenderTextureToggle = 0; + + Gamma = 1.0; + FlashColor0 = 0; + FlashColor1 = 0xFFFFFFFF; + FlashColor = 0; + FlashAmount = 0; + + NeedGammaUpdate = false; + NeedPalUpdate = false; + + if (MemBuffer == NULL) + { + return; + } + + memcpy(SourcePalette, GPalette.BaseColors, sizeof(PalEntry)*256); + + Windowed = !(static_cast(Video)->GoFullscreen(fullscreen)); + + TrueHeight = height; + if (fullscreen) + { + for (Win32Video::ModeInfo *mode = static_cast(Video)->m_Modes; mode != NULL; mode = mode->next) + { + if (mode->width == Width && mode->height == Height) + { + TrueHeight = mode->realheight; + PixelDoubling = mode->doubling; + break; + } + } + } + // Offset from top of screen to top of letterboxed screen + LBOffsetI = (TrueHeight - Height) / 2; + LBOffset = float(LBOffsetI); + + FillPresentParameters(&d3dpp, fullscreen, VSync); + + HRESULT hr; + + LOG("CreateDevice attempt 1 hwvp\n"); + if (FAILED(hr = D3D->CreateDevice(Adapter, D3DDEVTYPE_HAL, Window, + D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &d3dpp, &D3DDevice)) && + (hr != D3DERR_DEVICELOST || D3DDevice == NULL)) + { + LOG2("CreateDevice returned hr %08x dev %p; attempt 2 swvp\n", hr, D3DDevice); + if (FAILED(D3D->CreateDevice(Adapter, D3DDEVTYPE_HAL, Window, + D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &d3dpp, &D3DDevice)) && + (hr != D3DERR_DEVICELOST || D3DDevice == NULL)) + { + if (d3dpp.FullScreen_RefreshRateInHz != 0) + { + d3dpp.FullScreen_RefreshRateInHz = 0; + LOG2("CreateDevice returned hr %08x dev %p; attempt 3 (hwvp, default Hz)\n", hr, D3DDevice); + if (FAILED(hr = D3D->CreateDevice(Adapter, D3DDEVTYPE_HAL, Window, + D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &d3dpp, &D3DDevice)) && + (hr != D3DERR_DEVICELOST || D3DDevice == NULL)) + { + LOG2("CreateDevice returned hr %08x dev %p; attempt 4 (swvp, default Hz)\n", hr, D3DDevice); + if (FAILED(D3D->CreateDevice(Adapter, D3DDEVTYPE_HAL, Window, + D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &d3dpp, &D3DDevice)) && + hr != D3DERR_DEVICELOST) + { + D3DDevice = NULL; + } + } + } + } + } + LOG2("Final CreateDevice returned HR %08x and device %p\n", hr, D3DDevice); + LastHR = hr; + if (D3DDevice != NULL) + { + D3DADAPTER_IDENTIFIER9 adapter_id; + D3DDEVICE_CREATION_PARAMETERS create_params; + + if (FAILED(hr = D3DDevice->GetDeviceCaps(&DeviceCaps))) + { + memset(&DeviceCaps, 0, sizeof(DeviceCaps)); + } + if (SUCCEEDED(hr = D3DDevice->GetCreationParameters(&create_params)) && + SUCCEEDED(hr = D3D->GetAdapterIdentifier(create_params.AdapterOrdinal, 0, &adapter_id))) + { + // NVidia's drivers lie, claiming they don't support + // antialiased lines when, really, they do. + if (adapter_id.VendorId == 0x10de) + { + DeviceCaps.LineCaps |= D3DLINECAPS_ANTIALIAS; + } + // ATI's drivers apparently also lie, so screw this caps bit. + } + CreateResources(); + SetInitialState(); + } +} + +//========================================================================== +// +// D3DFB - Destructor +// +//========================================================================== + +D3DFB::~D3DFB () +{ + ReleaseResources(); + SAFE_RELEASE( D3DDevice ); + delete[] QuadExtra; +} + +//========================================================================== +// +// D3DFB :: SetInitialState +// +// Called after initial device creation and reset, when everything is set +// to D3D's defaults. +// +//========================================================================== + +void D3DFB::SetInitialState() +{ + AlphaBlendEnabled = FALSE; + AlphaBlendOp = D3DBLENDOP_ADD; + AlphaSrcBlend = D3DBLEND(0); + AlphaDestBlend = D3DBLEND(0); + + CurPixelShader = NULL; + memset(Constant, 0, sizeof(Constant)); + + for (unsigned i = 0; i < countof(Texture); ++i) + { + Texture[i] = NULL; + D3DDevice->SetSamplerState(i, D3DSAMP_ADDRESSU, (i == 1 && SM14) ? D3DTADDRESS_BORDER : D3DTADDRESS_CLAMP); + D3DDevice->SetSamplerState(i, D3DSAMP_ADDRESSV, (i == 1 && SM14) ? D3DTADDRESS_BORDER : D3DTADDRESS_CLAMP); + if (i > 1) + { + // Set linear filtering for the SM14 gamma texture. + D3DDevice->SetSamplerState(i, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); + } + } + + NeedGammaUpdate = true; + NeedPalUpdate = true; + OldRenderTarget = NULL; + + if (!Windowed && SM14) + { + // Fix for Radeon 9000, possibly other R200s: When the device is + // reset, it resets the gamma ramp, but the driver apparently keeps a + // cached copy of the ramp that it doesn't update, so when + // SetGammaRamp is called later to handle the NeedGammaUpdate flag, + // it doesn't do anything, because the gamma ramp is the same as the + // one passed in the last call, even though the visible gamma ramp + // actually has changed. + // + // So here we force the gamma ramp to something absolutely horrible and + // trust that we will be able to properly set the gamma later when + // NeedGammaUpdate is handled. + D3DGAMMARAMP ramp; + memset(&ramp, 0, sizeof(ramp)); + D3DDevice->SetGammaRamp(0, 0, &ramp); + } + + // This constant is used for grayscaling weights (.xyz) and color inversion (.w) + float weights[4] = { 77/256.f, 143/256.f, 37/256.f, 1 }; + D3DDevice->SetPixelShaderConstantF(PSCONST_Weights, weights, 1); + + // D3DRS_ALPHATESTENABLE defaults to FALSE + // D3DRS_ALPHAREF defaults to 0 + D3DDevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_NOTEQUAL); + AlphaTestEnabled = FALSE; + + CurBorderColor = 0; + + // Clear to black, just in case it wasn't done already. + D3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 0, 0); +} + +//========================================================================== +// +// D3DFB :: FillPresentParameters +// +//========================================================================== + +void D3DFB::FillPresentParameters (D3DPRESENT_PARAMETERS *pp, bool fullscreen, bool vsync) +{ + memset (pp, 0, sizeof(*pp)); + pp->Windowed = !fullscreen; + pp->SwapEffect = D3DSWAPEFFECT_DISCARD; + pp->BackBufferWidth = Width << PixelDoubling; + pp->BackBufferHeight = TrueHeight << PixelDoubling; + pp->BackBufferFormat = fullscreen ? D3DFMT_A8R8G8B8 : D3DFMT_UNKNOWN; + pp->BackBufferCount = 1; + pp->hDeviceWindow = Window; + pp->PresentationInterval = vsync ? D3DPRESENT_INTERVAL_ONE : D3DPRESENT_INTERVAL_IMMEDIATE; + if (fullscreen) + { + pp->FullScreen_RefreshRateInHz = vid_refreshrate; + } +} + +//========================================================================== +// +// D3DFB :: CreateResources +// +//========================================================================== + +bool D3DFB::CreateResources() +{ + Atlases = NULL; + if (!Windowed) + { + // Remove the window border in fullscreen mode + SetWindowLong (Window, GWL_STYLE, WS_POPUP|WS_VISIBLE|WS_SYSMENU); + } + else + { + // Resize the window to match desired dimensions + RECT rect = { 0, 0, Width, Height }; + AdjustWindowRectEx(&rect, WS_VISIBLE|WS_OVERLAPPEDWINDOW, FALSE, WS_EX_APPWINDOW); + int sizew = rect.right - rect.left; + int sizeh = rect.bottom - rect.top; + LOG2 ("Resize window to %dx%d\n", sizew, sizeh); + VidResizing = true; + // Make sure the window has a border in windowed mode + SetWindowLong(Window, GWL_STYLE, WS_VISIBLE|WS_OVERLAPPEDWINDOW); + if (GetWindowLong(Window, GWL_EXSTYLE) & WS_EX_TOPMOST) + { + // Direct3D 9 will apparently add WS_EX_TOPMOST to fullscreen windows, + // and removing it is a little tricky. Using SetWindowLongPtr to clear it + // will not do the trick, but sending the window behind everything will. + SetWindowPos(Window, HWND_BOTTOM, 0, 0, sizew, sizeh, + SWP_DRAWFRAME | SWP_NOCOPYBITS | SWP_NOMOVE); + SetWindowPos(Window, HWND_TOP, 0, 0, 0, 0, SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOSIZE); + } + else + { + SetWindowPos(Window, NULL, 0, 0, sizew, sizeh, + SWP_DRAWFRAME | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOZORDER); + } + I_RestoreWindowedPos(); + VidResizing = false; + } + if (!LoadShaders()) + { + return false; + } + if (!CreateFBTexture() || + !CreatePaletteTexture()) + { + return false; + } + if (!CreateVertexes()) + { + return false; + } + CreateGammaTexture(); + CreateBlockSurfaces(); + return true; +} + +//========================================================================== +// +// D3DFB :: LoadShaders +// +// Returns true if all required shaders were loaded. (Gamma and burn wipe +// are the only ones not considered "required".) +// +//========================================================================== + +bool D3DFB::LoadShaders() +{ + static const char models[][4] = { "30/", "20/", "14/" }; + FString shaderdir, shaderpath; + unsigned model, i; + int lump; + + // We determine the best available model simply by trying them all in + // order of decreasing preference. + for (model = 0; model < countof(models); ++model) + { + shaderdir = "shaders/d3d/sm"; + shaderdir += models[model]; + for (i = 0; i < NUM_SHADERS; ++i) + { + shaderpath = shaderdir; + shaderpath += ShaderNames[i]; + lump = Wads.CheckNumForFullName(shaderpath); + if (lump >= 0) + { + FMemLump data = Wads.ReadLump(lump); + if (FAILED(D3DDevice->CreatePixelShader((DWORD *)data.GetMem(), &Shaders[i])) && + i < SHADER_BurnWipe) + { + break; + } + } + } + if (i == NUM_SHADERS) + { // Success! + SM14 = (model == countof(models) - 1); + return true; + } + // Failure. Release whatever managed to load (which is probably nothing.) + for (i = 0; i < NUM_SHADERS; ++i) + { + SAFE_RELEASE( Shaders[i] ); + } + } + return false; +} + +//========================================================================== +// +// D3DFB :: ReleaseResources +// +//========================================================================== + +void D3DFB::ReleaseResources () +{ + I_SaveWindowedPos (); + KillNativeTexs(); + KillNativePals(); + ReleaseDefaultPoolItems(); + SAFE_RELEASE( ScreenshotSurface ); + SAFE_RELEASE( ScreenshotTexture ); + SAFE_RELEASE( PaletteTexture ); + for (int i = 0; i < NUM_SHADERS; ++i) + { + SAFE_RELEASE( Shaders[i] ); + } + GammaShader = NULL; + if (ScreenWipe != NULL) + { + delete ScreenWipe; + ScreenWipe = NULL; + } + Atlas *pack, *next; + for (pack = Atlases; pack != NULL; pack = next) + { + next = pack->Next; + delete pack; + } + GatheringWipeScreen = false; +} + +//========================================================================== +// +// D3DFB :: ReleaseDefaultPoolItems +// +// Free resources created with D3DPOOL_DEFAULT. +// +//========================================================================== + +void D3DFB::ReleaseDefaultPoolItems() +{ + SAFE_RELEASE( FBTexture ); + SAFE_RELEASE( FinalWipeScreen ); + SAFE_RELEASE( RenderTexture[0] ); + SAFE_RELEASE( RenderTexture[1] ); + SAFE_RELEASE( InitialWipeScreen ); + SAFE_RELEASE( VertexBuffer ); + SAFE_RELEASE( IndexBuffer ); + SAFE_RELEASE( BlockSurface[0] ); + SAFE_RELEASE( BlockSurface[1] ); + SAFE_RELEASE( FrontCopySurface ); +} + +//========================================================================== +// +// D3DFB :: Reset +// +//========================================================================== + +bool D3DFB::Reset () +{ + D3DPRESENT_PARAMETERS d3dpp; + + ReleaseDefaultPoolItems(); + FillPresentParameters (&d3dpp, !Windowed, VSync); + if (!SUCCEEDED(D3DDevice->Reset (&d3dpp))) + { + if (d3dpp.FullScreen_RefreshRateInHz != 0) + { + d3dpp.FullScreen_RefreshRateInHz = 0; + if (!SUCCEEDED(D3DDevice->Reset (&d3dpp))) + { + return false; + } + } + else + { + return false; + } + } + LOG("Device was reset\n"); + if (!CreateFBTexture() || !CreateVertexes()) + { + return false; + } + CreateBlockSurfaces(); + SetInitialState(); + return true; +} + +//========================================================================== +// +// D3DFB :: CreateBlockSurfaces +// +// Create blocking surfaces for antilag. It's okay if these can't be +// created; antilag just won't work. +// +//========================================================================== + +void D3DFB::CreateBlockSurfaces() +{ + BlockNum = 0; + if (SUCCEEDED(D3DDevice->CreateOffscreenPlainSurface(16, 16, D3DFMT_A8R8G8B8, + D3DPOOL_DEFAULT, &BlockSurface[0], 0))) + { + if (FAILED(D3DDevice->CreateOffscreenPlainSurface(16, 16, D3DFMT_A8R8G8B8, + D3DPOOL_DEFAULT, &BlockSurface[1], 0))) + { + BlockSurface[0]->Release(); + BlockSurface[0] = NULL; + } + } +} + +//========================================================================== +// +// D3DFB :: KillNativePals +// +// Frees all native palettes. +// +//========================================================================== + +void D3DFB::KillNativePals() +{ + while (Palettes != NULL) + { + delete Palettes; + } +} + +//========================================================================== +// +// D3DFB :: KillNativeTexs +// +// Frees all native textures. +// +//========================================================================== + +void D3DFB::KillNativeTexs() +{ + while (Textures != NULL) + { + delete Textures; + } +} + +//========================================================================== +// +// D3DFB :: CreateFBTexture +// +// Creates the "Framebuffer" texture. With the advent of hardware-assisted +// 2D, this is something of a misnomer now. The FBTexture is only used for +// uploading the software 3D image to video memory so that it can be drawn +// to the real frame buffer. +// +// It also creates the TempRenderTexture, since this seemed like a +// convenient place to do so. +// +//========================================================================== + +bool D3DFB::CreateFBTexture () +{ + if (FAILED(D3DDevice->CreateTexture(Width, Height, 1, D3DUSAGE_DYNAMIC, D3DFMT_L8, D3DPOOL_DEFAULT, &FBTexture, NULL))) + { + int pow2width, pow2height, i; + + for (i = 1; i < Width; i <<= 1) {} pow2width = i; + for (i = 1; i < Height; i <<= 1) {} pow2height = i; + + if (FAILED(D3DDevice->CreateTexture(pow2width, pow2height, 1, D3DUSAGE_DYNAMIC, D3DFMT_L8, D3DPOOL_DEFAULT, &FBTexture, NULL))) + { + return false; + } + else + { + FBWidth = pow2width; + FBHeight = pow2height; + } + } + else + { + FBWidth = Width; + FBHeight = Height; + } + RenderTextureToggle = 0; + RenderTexture[0] = NULL; + RenderTexture[1] = NULL; + if (FAILED(D3DDevice->CreateTexture(FBWidth, FBHeight, 1, D3DUSAGE_RENDERTARGET, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &RenderTexture[0], NULL))) + { + return false; + } + if (Windowed || PixelDoubling) + { + // Windowed or pixel doubling: Create another render texture so we can flip between them. + RenderTextureToggle = 1; + if (FAILED(D3DDevice->CreateTexture(FBWidth, FBHeight, 1, D3DUSAGE_RENDERTARGET, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &RenderTexture[1], NULL))) + { + return false; + } + } + else + { + // Fullscreen and not pixel doubling: Create a render target to have the back buffer copied to. + if (FAILED(D3DDevice->CreateRenderTarget(Width, Height, D3DFMT_X8R8G8B8, D3DMULTISAMPLE_NONE, 0, FALSE, &FrontCopySurface, NULL))) + { + return false; + } + } + // Initialize the TempRenderTextures to black. + for (int i = 0; i <= RenderTextureToggle; ++i) + { + IDirect3DSurface9 *surf; + if (SUCCEEDED(RenderTexture[i]->GetSurfaceLevel(0, &surf))) + { + D3DDevice->ColorFill(surf, NULL, D3DCOLOR_XRGB(0,0,0)); + surf->Release(); + } + } + TempRenderTexture = RenderTexture[0]; + CurrRenderTexture = 0; + return true; +} + +//========================================================================== +// +// D3DFB :: CreatePaletteTexture +// +//========================================================================== + +bool D3DFB::CreatePaletteTexture () +{ + if (FAILED(D3DDevice->CreateTexture (256, 1, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &PaletteTexture, NULL))) + { + return false; + } + return true; +} + +//========================================================================== +// +// D3DFB :: CreateGammaTexture +// +//========================================================================== + +bool D3DFB::CreateGammaTexture () +{ + // If this fails, you just won't get gamma correction in a window + // on SM14 cards. + assert(GammaTexture == NULL); + if (SM14) + { + return SUCCEEDED(D3DDevice->CreateTexture(256, 1, 1, 0, D3DFMT_A8R8G8B8, + D3DPOOL_MANAGED, &GammaTexture, NULL)); + } + return false; +} + +//========================================================================== +// +// D3DFB :: CreateVertexes +// +//========================================================================== + +bool D3DFB::CreateVertexes () +{ + VertexPos = -1; + IndexPos = -1; + QuadBatchPos = -1; + BatchType = BATCH_None; + if (FAILED(D3DDevice->CreateVertexBuffer(sizeof(FBVERTEX)*NUM_VERTS, + D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_FBVERTEX, D3DPOOL_DEFAULT, &VertexBuffer, NULL))) + { + return false; + } + if (FAILED(D3DDevice->CreateIndexBuffer(sizeof(WORD)*NUM_INDEXES, + D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &IndexBuffer, NULL))) + { + return false; + } + return true; +} + +//========================================================================== +// +// D3DFB :: CalcFullscreenCoords +// +//========================================================================== + +void D3DFB::CalcFullscreenCoords (FBVERTEX verts[4], bool viewarea_only, bool can_double, D3DCOLOR color0, D3DCOLOR color1) const +{ + float offset = OldRenderTarget != NULL ? 0 : LBOffset; + float top = offset - 0.5f; + float texright = float(Width) / float(FBWidth); + float texbot = float(Height) / float(FBHeight); + float mxl, mxr, myt, myb, tmxl, tmxr, tmyt, tmyb; + + if (viewarea_only) + { // Just calculate vertices for the viewarea/BlendingRect + mxl = float(BlendingRect.left) - 0.5f; + mxr = float(BlendingRect.right) - 0.5f; + myt = float(BlendingRect.top) + top; + myb = float(BlendingRect.bottom) + top; + tmxl = float(BlendingRect.left) / float(Width) * texright; + tmxr = float(BlendingRect.right) / float(Width) * texright; + tmyt = float(BlendingRect.top) / float(Height) * texbot; + tmyb = float(BlendingRect.bottom) / float(Height) * texbot; + } + else + { // Calculate vertices for the whole screen + mxl = -0.5f; + mxr = float(Width << (can_double ? PixelDoubling : 0)) - 0.5f; + myt = top; + myb = float(Height << (can_double ? PixelDoubling : 0)) + top; + tmxl = 0; + tmxr = texright; + tmyt = 0; + tmyb = texbot; + } + + //{ mxl, myt, 0, 1, 0, 0xFFFFFFFF, tmxl, tmyt }, + //{ mxr, myt, 0, 1, 0, 0xFFFFFFFF, tmxr, tmyt }, + //{ mxr, myb, 0, 1, 0, 0xFFFFFFFF, tmxr, tmyb }, + //{ mxl, myb, 0, 1, 0, 0xFFFFFFFF, tmxl, tmyb }, + + verts[0].x = mxl; + verts[0].y = myt; + verts[0].z = 0; + verts[0].rhw = 1; + verts[0].color0 = color0; + verts[0].color1 = color1; + verts[0].tu = tmxl; + verts[0].tv = tmyt; + + verts[1].x = mxr; + verts[1].y = myt; + verts[1].z = 0; + verts[1].rhw = 1; + verts[1].color0 = color0; + verts[1].color1 = color1; + verts[1].tu = tmxr; + verts[1].tv = tmyt; + + verts[2].x = mxr; + verts[2].y = myb; + verts[2].z = 0; + verts[2].rhw = 1; + verts[2].color0 = color0; + verts[2].color1 = color1; + verts[2].tu = tmxr; + verts[2].tv = tmyb; + + verts[3].x = mxl; + verts[3].y = myb; + verts[3].z = 0; + verts[3].rhw = 1; + verts[3].color0 = color0; + verts[3].color1 = color1; + verts[3].tu = tmxl; + verts[3].tv = tmyb; +} + +//========================================================================== +// +// D3DFB :: GetPageCount +// +//========================================================================== + +int D3DFB::GetPageCount () +{ + return 1; +} + +//========================================================================== +// +// D3DFB :: PaletteChanged +// +//========================================================================== + +void D3DFB::PaletteChanged () +{ +} + +//========================================================================== +// +// D3DFB :: QueryNewPalette +// +//========================================================================== + +int D3DFB::QueryNewPalette () +{ + return 0; +} + +//========================================================================== +// +// D3DFB :: IsValid +// +//========================================================================== + +bool D3DFB::IsValid () +{ + return D3DDevice != NULL; +} + +//========================================================================== +// +// D3DFB :: GetHR +// +//========================================================================== + +HRESULT D3DFB::GetHR () +{ + return LastHR; +} + +//========================================================================== +// +// D3DFB :: IsFullscreen +// +//========================================================================== + +bool D3DFB::IsFullscreen () +{ + return !Windowed; +} + +//========================================================================== +// +// D3DFB :: Lock +// +//========================================================================== + +bool D3DFB::Lock (bool buffered) +{ + if (LockCount++ > 0) + { + return false; + } + assert (!In2D); + Accel2D = vid_hw2d; + Buffer = MemBuffer; + return false; +} + +//========================================================================== +// +// D3DFB :: Unlock +// +//========================================================================== + +void D3DFB::Unlock () +{ + LOG1 ("Unlock <%d>\n", LockCount); + + if (LockCount == 0) + { + return; + } + + if (UpdatePending && LockCount == 1) + { + Update (); + } + else if (--LockCount == 0) + { + Buffer = NULL; + } +} + +//========================================================================== +// +// D3DFB :: Update +// +// When In2D == 0: Copy buffer to screen and present +// When In2D == 1: Copy buffer to screen but do not present +// When In2D == 2: Set up for 2D drawing but do not draw anything +// When In2D == 3: Present and set In2D to 0 +// +//========================================================================== + +void D3DFB::Update () +{ + if (In2D == 3) + { + if (InScene) + { + DrawRateStuff(); + DrawPackedTextures(d3d_showpacks); + EndBatch(); // Make sure all batched primitives are drawn. + Flip(); + } + In2D = 0; + return; + } + + if (LockCount != 1) + { + I_FatalError ("Framebuffer must have exactly 1 lock to be updated"); + if (LockCount > 0) + { + UpdatePending = true; + --LockCount; + } + return; + } + + if (In2D == 0) + { + DrawRateStuff(); + } + + if (NeedGammaUpdate) + { + float psgamma[4]; + float igamma; + + NeedGammaUpdate = false; + igamma = 1 / Gamma; + if (!Windowed) + { + D3DGAMMARAMP ramp; + + for (int i = 0; i < 256; ++i) + { + ramp.blue[i] = ramp.green[i] = ramp.red[i] = WORD(65535.f * powf(i / 255.f, igamma)); + } + LOG("SetGammaRamp\n"); + D3DDevice->SetGammaRamp(0, D3DSGR_CALIBRATE, &ramp); + } + else + { + if (igamma != 1) + { + UpdateGammaTexture(igamma); + GammaShader = Shaders[SHADER_GammaCorrection]; + } + else + { + GammaShader = NULL; + } + } + psgamma[2] = psgamma[1] = psgamma[0] = igamma; + psgamma[3] = 0.5; // For SM14 version + D3DDevice->SetPixelShaderConstantF(PSCONST_Gamma, psgamma, 1); + } + + if (NeedPalUpdate) + { + UploadPalette(); + } + + BlitCycles.Reset(); + BlitCycles.Clock(); + + LockCount = 0; + HRESULT hr = D3DDevice->TestCooperativeLevel(); + if (FAILED(hr) && (hr != D3DERR_DEVICENOTRESET || !Reset())) + { + Sleep(1); + return; + } + Draw3DPart(In2D <= 1); + if (In2D == 0) + { + Flip(); + } + + BlitCycles.Unclock(); + //LOG1 ("cycles = %d\n", BlitCycles); + + Buffer = NULL; + UpdatePending = false; +} + +//========================================================================== +// +// D3DFB :: Flip +// +//========================================================================== + +void D3DFB::Flip() +{ + assert(InScene); + + DrawLetterbox(); + DoWindowedGamma(); + D3DDevice->EndScene(); + + CopyNextFrontBuffer(); + + // Attempt to counter input lag. + if (d3d_antilag && BlockSurface[0] != NULL) + { + D3DLOCKED_RECT lr; + volatile int dummy; + D3DDevice->ColorFill(BlockSurface[BlockNum], NULL, D3DCOLOR_ARGB(0xFF,0,0x20,0x50)); + BlockNum ^= 1; + if (!FAILED((BlockSurface[BlockNum]->LockRect(&lr, NULL, D3DLOCK_READONLY)))) + { + dummy = *(int *)lr.pBits; + BlockSurface[BlockNum]->UnlockRect(); + } + } + // Limiting the frame rate is as simple as waiting for the timer to signal this event. + if (FPSLimitEvent != NULL) + { + WaitForSingleObject(FPSLimitEvent, 1000); + } + D3DDevice->Present(NULL, NULL, NULL, NULL); + InScene = false; + + if (RenderTextureToggle) + { + // Flip the TempRenderTexture to the other one now. + CurrRenderTexture ^= RenderTextureToggle; + TempRenderTexture = RenderTexture[CurrRenderTexture]; + } +} + +//========================================================================== +// +// D3DFB :: CopyNextFrontBuffer +// +// Duplicates the contents of the back buffer that will become the front +// buffer upon Present into FrontCopySurface so that we can get the +// contents of the display without wasting time in GetFrontBufferData(). +// +//========================================================================== + +void D3DFB::CopyNextFrontBuffer() +{ + IDirect3DSurface9 *backbuff; + + if (Windowed || PixelDoubling) + { + // Windowed mode or pixel doubling: TempRenderTexture has what we want + SAFE_RELEASE( FrontCopySurface ); + if (SUCCEEDED(TempRenderTexture->GetSurfaceLevel(0, &backbuff))) + { + FrontCopySurface = backbuff; + } + } + else + { + // Fullscreen, not pixel doubled: The back buffer has what we want, + // but it might be letter boxed. + if (SUCCEEDED(D3DDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuff))) + { + RECT srcrect = { 0, LBOffsetI, Width, LBOffsetI + Height }; + D3DDevice->StretchRect(backbuff, &srcrect, FrontCopySurface, NULL, D3DTEXF_NONE); + backbuff->Release(); + } + } +} + +//========================================================================== +// +// D3DFB :: PaintToWindow +// +//========================================================================== + +bool D3DFB::PaintToWindow () +{ + HRESULT hr; + + if (LockCount != 0) + { + return false; + } + hr = D3DDevice->TestCooperativeLevel(); + if (FAILED(hr)) + { + if (hr != D3DERR_DEVICENOTRESET || !Reset()) + { + Sleep (1); + return false; + } + } + Draw3DPart(true); + return true; +} + +//========================================================================== +// +// D3DFB :: Draw3DPart +// +// The software 3D part, to be exact. +// +//========================================================================== + +void D3DFB::Draw3DPart(bool copy3d) +{ + if (copy3d) + { + RECT texrect = { 0, 0, Width, Height }; + D3DLOCKED_RECT lockrect; + + if ((FBWidth == Width && FBHeight == Height && + SUCCEEDED(FBTexture->LockRect (0, &lockrect, NULL, D3DLOCK_DISCARD))) || + SUCCEEDED(FBTexture->LockRect (0, &lockrect, &texrect, 0))) + { + if (lockrect.Pitch == Pitch && Pitch == Width) + { + memcpy (lockrect.pBits, MemBuffer, Width * Height); + } + else + { + BYTE *dest = (BYTE *)lockrect.pBits; + BYTE *src = MemBuffer; + for (int y = 0; y < Height; y++) + { + memcpy (dest, src, Width); + dest += lockrect.Pitch; + src += Pitch; + } + } + FBTexture->UnlockRect (0); + } + } + InScene = true; + D3DDevice->BeginScene(); + D3DDevice->SetRenderState(D3DRS_ANTIALIASEDLINEENABLE, vid_hwaalines); + assert(OldRenderTarget == NULL); + if (TempRenderTexture != NULL && + ((Windowed && TempRenderTexture != FinalWipeScreen) || GatheringWipeScreen || PixelDoubling)) + { + IDirect3DSurface9 *targetsurf; + if (SUCCEEDED(TempRenderTexture->GetSurfaceLevel(0, &targetsurf))) + { + if (SUCCEEDED(D3DDevice->GetRenderTarget(0, &OldRenderTarget))) + { + if (FAILED(D3DDevice->SetRenderTarget(0, targetsurf))) + { + // Setting the render target failed. + } + } + targetsurf->Release(); + } + } + + SetTexture(0, FBTexture); + SetPaletteTexture(PaletteTexture, 256, BorderColor); + D3DDevice->SetFVF (D3DFVF_FBVERTEX); + memset(Constant, 0, sizeof(Constant)); + SetAlphaBlend(D3DBLENDOP(0)); + EnableAlphaTest(FALSE); + SetPixelShader(Shaders[SHADER_NormalColorPal]); + if (copy3d) + { + FBVERTEX verts[4]; + D3DCOLOR color0, color1; + if (Accel2D) + { + if (realfixedcolormap == NULL) + { + color0 = 0; + color1 = 0xFFFFFFF; + } + else + { + color0 = D3DCOLOR_COLORVALUE(realfixedcolormap->ColorizeStart[0]/2, + realfixedcolormap->ColorizeStart[1]/2, realfixedcolormap->ColorizeStart[2]/2, 0); + color1 = D3DCOLOR_COLORVALUE(realfixedcolormap->ColorizeEnd[0]/2, + realfixedcolormap->ColorizeEnd[1]/2, realfixedcolormap->ColorizeEnd[2]/2, 1); + SetPixelShader(Shaders[SHADER_SpecialColormapPal]); + } + } + else + { + color0 = FlashColor0; + color1 = FlashColor1; + } + CalcFullscreenCoords(verts, Accel2D, false, color0, color1); + D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, verts, sizeof(FBVERTEX)); + } + SetPixelShader(Shaders[SHADER_NormalColorPal]); +} + +//========================================================================== +// +// D3DFB :: DrawLetterbox +// +// Draws the black bars at the top and bottom of the screen for letterboxed +// modes. +// +//========================================================================== + +void D3DFB::DrawLetterbox() +{ + if (LBOffsetI != 0) + { + D3DRECT rects[2] = { { 0, 0, Width, LBOffsetI }, { 0, Height + LBOffsetI, Width, TrueHeight } }; + D3DDevice->Clear (2, rects, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.f, 0); + } +} + +//========================================================================== +// +// D3DFB :: DoWindowedGamma +// +// Draws the render target texture to the real back buffer using a gamma- +// correcting pixel shader. +// +//========================================================================== + +void D3DFB::DoWindowedGamma() +{ + if (OldRenderTarget != NULL) + { + FBVERTEX verts[4]; + + CalcFullscreenCoords(verts, false, true, 0, 0xFFFFFFFF); + D3DDevice->SetRenderTarget(0, OldRenderTarget); + D3DDevice->SetFVF(D3DFVF_FBVERTEX); + SetTexture(0, TempRenderTexture); + SetPixelShader(Windowed && GammaShader ? GammaShader : Shaders[SHADER_NormalColor]); + if (SM14 && Windowed && GammaShader) + { + SetTexture(2, GammaTexture); + SetTexture(3, GammaTexture); + SetTexture(4, GammaTexture); + } + SetAlphaBlend(D3DBLENDOP(0)); + EnableAlphaTest(FALSE); + D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, verts, sizeof(FBVERTEX)); + OldRenderTarget->Release(); + OldRenderTarget = NULL; + if (SM14 && Windowed && GammaShader) + { +// SetTexture(0, GammaTexture); +// SetPixelShader(Shaders[SHADER_NormalColor]); +// D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, verts, sizeof(FBVERTEX)); + } + } +} + +//========================================================================== +// +// D3DFB :: UpdateGammaTexture +// +// Updates the gamma texture used by the PS14 shader. We only use the first +// half of the texture so that we needn't worry about imprecision causing +// it to grab from the border. +// +//========================================================================== + +void D3DFB::UpdateGammaTexture(float igamma) +{ + D3DLOCKED_RECT lockrect; + + if (GammaTexture != NULL && SUCCEEDED(GammaTexture->LockRect(0, &lockrect, NULL, 0))) + { + BYTE *pix = (BYTE *)lockrect.pBits; + for (int i = 0; i <= 128; ++i) + { + pix[i*4+2] = pix[i*4+1] = pix[i*4] = BYTE(255.f * powf(i / 128.f, igamma)); + pix[i*4+3] = 255; + } + GammaTexture->UnlockRect(0); + } +} + +//========================================================================== +// +// D3DFB :: DoOffByOneCheck +// +// Pixel Shader 1.x does not have enough precision to properly map a "color" +// from the source texture to an index in the palette texture. The best we +// can do is use 255 pixels of the palette and get the 256th from the +// texture border color. This routine determines which pixel of the texture +// is skipped so that we don't use it for palette data. +// +//========================================================================== + +void D3DFB::DoOffByOneCheck () +{ + IDirect3DSurface9 *savedrendertarget; + IDirect3DSurface9 *testsurf, *readsurf; + D3DLOCKED_RECT lockrect; + RECT testrect = { 0, 0, 256, 1 }; + float texright = 256.f / float(FBWidth); + float texbot = 1.f / float(FBHeight); + FBVERTEX verts[4] = + { + { -0.5f, -0.5f, 0.5f, 1.f, D3DCOLOR_RGBA(0,0,0,0), D3DCOLOR_RGBA(255,255,255,255), 0.f, 0.f }, + { 255.5f, -0.5f, 0.5f, 1.f, D3DCOLOR_RGBA(0,0,0,0), D3DCOLOR_RGBA(255,255,255,255), texright, 0.f }, + { 255.5f, 0.5f, 0.5f, 1.f, D3DCOLOR_RGBA(0,0,0,0), D3DCOLOR_RGBA(255,255,255,255), texright, texbot }, + { -0.5f, 0.5f, 0.5f, 1.f, D3DCOLOR_RGBA(0,0,0,0), D3DCOLOR_RGBA(255,255,255,255), 0.f, texbot } + }; + int i, c; + + if (SkipAt >= 0) + { + return; + } + + // Create an easily recognizable R3G3B2 palette. + if (SUCCEEDED(PaletteTexture->LockRect(0, &lockrect, NULL, 0))) + { + BYTE *pal = (BYTE *)(lockrect.pBits); + for (i = 0; i < 256; ++i) + { + pal[i*4+0] = (i & 0x03) << 6; // blue + pal[i*4+1] = (i & 0x1C) << 3; // green + pal[i*4+2] = (i & 0xE0); // red; + pal[i*4+3] = 255; + } + PaletteTexture->UnlockRect (0); + } + else + { + return; + } + // Prepare a texture with values 0-256. + if (SUCCEEDED(FBTexture->LockRect(0, &lockrect, &testrect, 0))) + { + for (i = 0; i < 256; ++i) + { + ((BYTE *)lockrect.pBits)[i] = i; + } + FBTexture->UnlockRect(0); + } + else + { + return; + } + // Create a render target that we can draw it to. + if (FAILED(D3DDevice->GetRenderTarget(0, &savedrendertarget))) + { + return; + } + if (FAILED(D3DDevice->CreateRenderTarget(256, 1, D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0, FALSE, &testsurf, NULL))) + { + return; + } + if (FAILED(D3DDevice->CreateOffscreenPlainSurface(256, 1, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &readsurf, NULL))) + { + testsurf->Release(); + return; + } + if (FAILED(D3DDevice->SetRenderTarget(0, testsurf))) + { + testsurf->Release(); + readsurf->Release(); + return; + } + // Write it to the render target using the pixel shader. + D3DDevice->BeginScene(); + D3DDevice->SetTexture(0, FBTexture); + D3DDevice->SetTexture(1, PaletteTexture); + D3DDevice->SetFVF(D3DFVF_FBVERTEX); + D3DDevice->SetPixelShader(Shaders[SHADER_NormalColorPal]); + SetConstant(PSCONST_PaletteMod, 1.f, 0.5f / 256.f, 0, 0); + D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, verts, sizeof(FBVERTEX)); + D3DDevice->EndScene(); + D3DDevice->SetRenderTarget(0, savedrendertarget); + savedrendertarget->Release(); + // Now read it back and see where it skips an entry + if (SUCCEEDED(D3DDevice->GetRenderTargetData(testsurf, readsurf)) && + SUCCEEDED(readsurf->LockRect(&lockrect, &testrect, D3DLOCK_READONLY))) + { + const BYTE *pix = (const BYTE *)lockrect.pBits; + for (i = 0; i < 256; ++i, pix += 4) + { + c = (pix[0] >> 6) | // blue + ((pix[1] >> 5) << 2) | // green + ((pix[2] >> 5) << 5); // red + if (c != i) + { + break; + } + } + } + readsurf->UnlockRect(); + readsurf->Release(); + testsurf->Release(); + SkipAt = i; +} + +void D3DFB::UploadPalette () +{ + D3DLOCKED_RECT lockrect; + + if (SkipAt < 0) + { + if (SM14) + { + DoOffByOneCheck(); + } + else + { + SkipAt = 256; + } + } + if (SUCCEEDED(PaletteTexture->LockRect(0, &lockrect, NULL, 0))) + { + BYTE *pix = (BYTE *)lockrect.pBits; + int i; + + for (i = 0; i < SkipAt; ++i, pix += 4) + { + pix[0] = SourcePalette[i].b; + pix[1] = SourcePalette[i].g; + pix[2] = SourcePalette[i].r; + pix[3] = (i == 0 ? 0 : 255); + // To let masked textures work, the first palette entry's alpha is 0. + } + pix += 4; + for (; i < 255; ++i, pix += 4) + { + pix[0] = SourcePalette[i].b; + pix[1] = SourcePalette[i].g; + pix[2] = SourcePalette[i].r; + pix[3] = 255; + } + PaletteTexture->UnlockRect(0); + BorderColor = D3DCOLOR_XRGB(SourcePalette[255].r, SourcePalette[255].g, SourcePalette[255].b); + } +} + +PalEntry *D3DFB::GetPalette () +{ + return SourcePalette; +} + +void D3DFB::UpdatePalette () +{ + NeedPalUpdate = true; +} + +bool D3DFB::SetGamma (float gamma) +{ + LOG1 ("SetGamma %g\n", gamma); + Gamma = gamma; + NeedGammaUpdate = true; + return true; +} + +bool D3DFB::SetFlash (PalEntry rgb, int amount) +{ + FlashColor = rgb; + FlashAmount = amount; + + // Fill in the constants for the pixel shader to do linear interpolation between the palette and the flash: + float r = rgb.r / 255.f, g = rgb.g / 255.f, b = rgb.b / 255.f, a = amount / 256.f; + FlashColor0 = D3DCOLOR_COLORVALUE(r * a, g * a, b * a, 0); + a = 1 - a; + FlashColor1 = D3DCOLOR_COLORVALUE(a, a, a, 1); + return true; +} + +void D3DFB::GetFlash (PalEntry &rgb, int &amount) +{ + rgb = FlashColor; + amount = FlashAmount; +} + +void D3DFB::GetFlashedPalette (PalEntry pal[256]) +{ + memcpy (pal, SourcePalette, 256*sizeof(PalEntry)); + if (FlashAmount) + { + DoBlending (pal, pal, 256, FlashColor.r, FlashColor.g, FlashColor.b, FlashAmount); + } +} + +void D3DFB::SetVSync (bool vsync) +{ + if (VSync != vsync) + { + VSync = vsync; + Reset(); + } +} + +void D3DFB::NewRefreshRate () +{ + if (!Windowed) + { + Reset(); + } +} + +void D3DFB::Blank () +{ + // Only used by movie player, which isn't working with D3D9 yet. +} + +void D3DFB::SetBlendingRect(int x1, int y1, int x2, int y2) +{ + BlendingRect.left = x1; + BlendingRect.top = y1; + BlendingRect.right = x2; + BlendingRect.bottom = y2; +} + +//========================================================================== +// +// D3DFB :: GetScreenshotBuffer +// +// Returns a pointer into a surface holding the current screen data. +// +//========================================================================== + +void D3DFB::GetScreenshotBuffer(const BYTE *&buffer, int &pitch, ESSType &color_type) +{ + D3DLOCKED_RECT lrect; + + if (!Accel2D) + { + Super::GetScreenshotBuffer(buffer, pitch, color_type); + return; + } + buffer = NULL; + if ((ScreenshotTexture = GetCurrentScreen()) != NULL) + { + if (FAILED(ScreenshotTexture->GetSurfaceLevel(0, &ScreenshotSurface))) + { + ScreenshotTexture->Release(); + ScreenshotTexture = NULL; + } + else if (FAILED(ScreenshotSurface->LockRect(&lrect, NULL, D3DLOCK_READONLY | D3DLOCK_NOSYSLOCK))) + { + ScreenshotSurface->Release(); + ScreenshotSurface = NULL; + ScreenshotTexture->Release(); + ScreenshotTexture = NULL; + } + else + { + buffer = (const BYTE *)lrect.pBits; + pitch = lrect.Pitch; + color_type = SS_BGRA; + } + } +} + +//========================================================================== +// +// D3DFB :: ReleaseScreenshotBuffer +// +//========================================================================== + +void D3DFB::ReleaseScreenshotBuffer() +{ + if (LockCount > 0) + { + Super::ReleaseScreenshotBuffer(); + } + if (ScreenshotSurface != NULL) + { + ScreenshotSurface->UnlockRect(); + ScreenshotSurface->Release(); + ScreenshotSurface = NULL; + } + SAFE_RELEASE( ScreenshotTexture ); +} + +//========================================================================== +// +// D3DFB :: GetCurrentScreen +// +// Returns a texture containing the pixels currently visible on-screen. +// +//========================================================================== + +IDirect3DTexture9 *D3DFB::GetCurrentScreen(D3DPOOL pool) +{ + IDirect3DTexture9 *tex; + IDirect3DSurface9 *surf; + D3DSURFACE_DESC desc; + HRESULT hr; + + assert(pool == D3DPOOL_SYSTEMMEM || pool == D3DPOOL_DEFAULT); + + if (FrontCopySurface == NULL || FAILED(FrontCopySurface->GetDesc(&desc))) + { + return NULL; + } + if (pool == D3DPOOL_SYSTEMMEM) + { + hr = D3DDevice->CreateTexture(desc.Width, desc.Height, 1, 0, desc.Format, D3DPOOL_SYSTEMMEM, &tex, NULL); + } + else + { + hr = D3DDevice->CreateTexture(FBWidth, FBHeight, 1, D3DUSAGE_RENDERTARGET, desc.Format, D3DPOOL_DEFAULT, &tex, NULL); + } + if (FAILED(hr)) + { + return NULL; + } + if (FAILED(tex->GetSurfaceLevel(0, &surf))) + { + tex->Release(); + return NULL; + } + if (pool == D3DPOOL_SYSTEMMEM) + { + // Video -> System memory : use GetRenderTargetData + hr = D3DDevice->GetRenderTargetData(FrontCopySurface, surf); + } + else + { + // Video -> Video memory : use StretchRect + RECT destrect = { 0, 0, Width, Height }; + hr = D3DDevice->StretchRect(FrontCopySurface, NULL, surf, &destrect, D3DTEXF_POINT); + } + surf->Release(); + if (FAILED(hr)) + { + tex->Release(); + return NULL; + } + return tex; +} + +/**************************************************************************/ +/* 2D Stuff */ +/**************************************************************************/ + +//========================================================================== +// +// D3DFB :: DrawPackedTextures +// +// DEBUG: Draws the texture atlases to the screen, starting with the +// 1-based packnum. Ignores atlases that are flagged for use by one +// texture only. +// +//========================================================================== + +void D3DFB::DrawPackedTextures(int packnum) +{ + D3DCOLOR empty_colors[8] = + { + 0x50FF0000, 0x5000FF00, 0x500000FF, 0x50FFFF00, + 0x50FF00FF, 0x5000FFFF, 0x50FF8000, 0x500080FF + }; + Atlas *pack; + int x = 8, y = 8; + + if (packnum <= 0) + { + return; + } + pack = Atlases; + // Find the first texture atlas that is an actual atlas. + while (pack != NULL && pack->OneUse) + { // Skip textures that aren't used as atlases + pack = pack->Next; + } + // Skip however many atlases we would have otherwise drawn + // until we've skipped of them. + while (pack != NULL && packnum != 1) + { + if (!pack->OneUse) + { // Skip textures that aren't used as atlases + packnum--; + } + pack = pack->Next; + } + // Draw atlases until we run out of room on the screen. + while (pack != NULL) + { + if (pack->OneUse) + { // Skip textures that aren't used as atlases + pack = pack->Next; + continue; + } + + AddColorOnlyRect(x-1, y-1-LBOffsetI, 258, 258, D3DCOLOR_XRGB(255,255,0)); + int back = 0; + for (PackedTexture *box = pack->UsedList; box != NULL; box = box->Next) + { + AddColorOnlyQuad( + x + box->Area.left * 256 / pack->Width, + y + box->Area.top * 256 / pack->Height, + (box->Area.right - box->Area.left) * 256 / pack->Width, + (box->Area.bottom - box->Area.top) * 256 / pack->Height, empty_colors[back]); + back = (back + 1) & 7; + } +// AddColorOnlyQuad(x, y-LBOffsetI, 256, 256, D3DCOLOR_ARGB(180,0,0,0)); + + CheckQuadBatch(); + + BufferedTris *quad = &QuadExtra[QuadBatchPos]; + FBVERTEX *vert = &VertexData[VertexPos]; + + quad->Group1 = 0; + if (pack->Format == D3DFMT_L8/* && !tex->IsGray*/) + { + quad->Flags = BQF_WrapUV | BQF_GamePalette/* | BQF_DisableAlphaTest*/; + quad->ShaderNum = BQS_PalTex; + } + else + { + quad->Flags = BQF_WrapUV/* | BQF_DisableAlphaTest*/; + quad->ShaderNum = BQS_Plain; + } + quad->Palette = NULL; + quad->Texture = pack->Tex; + quad->NumVerts = 4; + quad->NumTris = 2; + + float x0 = float(x) - 0.5f; + float y0 = float(y) - 0.5f; + float x1 = x0 + 256.f; + float y1 = y0 + 256.f; + + vert[0].x = x0; + vert[0].y = y0; + vert[0].z = 0; + vert[0].rhw = 1; + vert[0].color0 = 0; + vert[0].color1 = 0xFFFFFFFF; + vert[0].tu = 0; + vert[0].tv = 0; + + vert[1].x = x1; + vert[1].y = y0; + vert[1].z = 0; + vert[1].rhw = 1; + vert[1].color0 = 0; + vert[1].color1 = 0xFFFFFFFF; + vert[1].tu = 1; + vert[1].tv = 0; + + vert[2].x = x1; + vert[2].y = y1; + vert[2].z = 0; + vert[2].rhw = 1; + vert[2].color0 = 0; + vert[2].color1 = 0xFFFFFFFF; + vert[2].tu = 1; + vert[2].tv = 1; + + vert[3].x = x0; + vert[3].y = y1; + vert[3].z = 0; + vert[3].rhw = 1; + vert[3].color0 = 0; + vert[3].color1 = 0xFFFFFFFF; + vert[3].tu = 0; + vert[3].tv = 1; + + IndexData[IndexPos ] = VertexPos; + IndexData[IndexPos + 1] = VertexPos + 1; + IndexData[IndexPos + 2] = VertexPos + 2; + IndexData[IndexPos + 3] = VertexPos; + IndexData[IndexPos + 4] = VertexPos + 2; + IndexData[IndexPos + 5] = VertexPos + 3; + + QuadBatchPos++; + VertexPos += 4; + IndexPos += 6; + + x += 256 + 8; + if (x > Width - 256) + { + x = 8; + y += 256 + 8; + if (y > TrueHeight - 256) + { + return; + } + } + pack = pack->Next; + } +} + +//========================================================================== +// +// D3DFB :: AllocPackedTexture +// +// Finds space to pack an image inside a texture atlas and returns it. +// Large images and those that need to wrap always get their own textures. +// +//========================================================================== + +D3DFB::PackedTexture *D3DFB::AllocPackedTexture(int w, int h, bool wrapping, D3DFORMAT format) +{ + Atlas *pack; + Rect box; + bool padded; + + // The - 2 to account for padding + if (w > 256 - 2 || h > 256 - 2 || wrapping) + { // Create a new texture atlas. + pack = new Atlas(this, w, h, format); + pack->OneUse = true; + box = pack->Packer.Insert(w, h); + padded = false; + } + else + { // Try to find space in an existing texture atlas. + w += 2; // Add padding + h += 2; + for (pack = Atlases; pack != NULL; pack = pack->Next) + { + // Use the first atlas it fits in. + if (pack->Format == format) + { + box = pack->Packer.Insert(w, h); + if (box.width != 0) + { + break; + } + } + } + if (pack == NULL) + { // Create a new texture atlas. + pack = new Atlas(this, DEF_ATLAS_WIDTH, DEF_ATLAS_HEIGHT, format); + box = pack->Packer.Insert(w, h); + } + padded = true; + } + assert(box.width != 0 && box.height != 0); + return pack->AllocateImage(box, padded); +} + +//========================================================================== +// +// Atlas Constructor +// +//========================================================================== + +D3DFB::Atlas::Atlas(D3DFB *fb, int w, int h, D3DFORMAT format) + : Packer(w, h, true) +{ + Tex = NULL; + Format = format; + UsedList = NULL; + OneUse = false; + Width = 0; + Height = 0; + Next = NULL; + + // Attach to the end of the atlas list + Atlas **prev = &fb->Atlases; + while (*prev != NULL) + { + prev = &((*prev)->Next); + } + *prev = this; + +#if 1 + if (FAILED(fb->D3DDevice->CreateTexture(w, h, 1, 0, format, D3DPOOL_MANAGED, &Tex, NULL))) +#endif + { // Try again, using power-of-2 sizes + int i; + + for (i = 1; i < w; i <<= 1) {} w = i; + for (i = 1; i < h; i <<= 1) {} h = i; + if (FAILED(fb->D3DDevice->CreateTexture(w, h, 1, 0, format, D3DPOOL_MANAGED, &Tex, NULL))) + { + return; + } + } + Width = w; + Height = h; +} + +//========================================================================== +// +// Atlas Destructor +// +//========================================================================== + +D3DFB::Atlas::~Atlas() +{ + PackedTexture *box, *next; + + SAFE_RELEASE( Tex ); + for (box = UsedList; box != NULL; box = next) + { + next = box->Next; + delete box; + } +} + +//========================================================================== +// +// Atlas :: AllocateImage +// +// Moves the box from the empty list to the used list, sizing it to the +// requested dimensions and adding additional boxes to the empty list if +// needed. +// +// The passed box *MUST* be in this texture atlas's empty list. +// +//========================================================================== + +D3DFB::PackedTexture *D3DFB::Atlas::AllocateImage(const Rect &rect, bool padded) +{ + PackedTexture *box = new PackedTexture; + + box->Owner = this; + box->Area.left = rect.x; + box->Area.top = rect.y; + box->Area.right = rect.x + rect.width; + box->Area.bottom = rect.y + rect.height; + + box->Left = float(box->Area.left + padded) / Width; + box->Right = float(box->Area.right - padded) / Width; + box->Top = float(box->Area.top + padded) / Height; + box->Bottom = float(box->Area.bottom - padded) / Height; + + box->Padded = padded; + + // Add it to the used list. + box->Next = UsedList; + if (box->Next != NULL) + { + box->Next->Prev = &box->Next; + } + UsedList = box; + box->Prev = &UsedList; + + return box; +} + +//========================================================================== +// +// Atlas :: FreeBox +// +// Removes a box from the used list and deletes it. Space is returned to the +// waste list. Once all boxes for this atlas are freed, the entire bin +// packer is reinitialized for maximum efficiency. +// +//========================================================================== + +void D3DFB::Atlas::FreeBox(D3DFB::PackedTexture *box) +{ + *(box->Prev) = box->Next; + if (box->Next != NULL) + { + box->Next->Prev = box->Prev; + } + Rect waste; + waste.x = box->Area.left; + waste.y = box->Area.top; + waste.width = box->Area.right - box->Area.left; + waste.height = box->Area.bottom - box->Area.top; + box->Owner->Packer.AddWaste(waste); + delete box; + if (UsedList == NULL) + { + Packer.Init(Width, Height, true); + } +} + +//========================================================================== +// +// D3DTex Constructor +// +//========================================================================== + +D3DTex::D3DTex(FTexture *tex, D3DFB *fb, bool wrapping) +{ + // Attach to the texture list for the D3DFB + Next = fb->Textures; + if (Next != NULL) + { + Next->Prev = &Next; + } + Prev = &fb->Textures; + fb->Textures = this; + + GameTex = tex; + Box = NULL; + IsGray = false; + + Create(fb, wrapping); +} + +//========================================================================== +// +// D3DTex Destructor +// +//========================================================================== + +D3DTex::~D3DTex() +{ + if (Box != NULL) + { + Box->Owner->FreeBox(Box); + Box = NULL; + } + // Detach from the texture list + *Prev = Next; + if (Next != NULL) + { + Next->Prev = Prev; + } + // Remove link from the game texture + if (GameTex != NULL) + { + GameTex->Native = NULL; + } +} + +//========================================================================== +// +// D3DTex :: CheckWrapping +// +// Returns true if the texture is compatible with the specified wrapping +// mode. +// +//========================================================================== + +bool D3DTex::CheckWrapping(bool wrapping) +{ + // If it doesn't need to wrap, then it works. + if (!wrapping) + { + return true; + } + // If it needs to wrap, then it can't be packed inside another texture. + return Box->Owner->OneUse; +} + +//========================================================================== +// +// D3DTex :: Create +// +// Creates an IDirect3DTexture9 for the texture and copies the image data +// to it. Note that unlike FTexture, this image is row-major. +// +//========================================================================== + +bool D3DTex::Create(D3DFB *fb, bool wrapping) +{ + assert(Box == NULL); + if (Box != NULL) + { + Box->Owner->FreeBox(Box); + } + + Box = fb->AllocPackedTexture(GameTex->GetWidth(), GameTex->GetHeight(), wrapping, GetTexFormat()); + + if (Box == NULL) + { + return false; + } + if (!Update()) + { + Box->Owner->FreeBox(Box); + Box = NULL; + return false; + } + return true; +} + +//========================================================================== +// +// D3DTex :: Update +// +// Copies image data from the underlying FTexture to the D3D texture. +// +//========================================================================== + +bool D3DTex::Update() +{ + D3DSURFACE_DESC desc; + D3DLOCKED_RECT lrect; + RECT rect; + BYTE *dest; + + assert(Box != NULL); + assert(Box->Owner != NULL); + assert(Box->Owner->Tex != NULL); + assert(GameTex != NULL); + + if (FAILED(Box->Owner->Tex->GetLevelDesc(0, &desc))) + { + return false; + } + rect = Box->Area; + if (FAILED(Box->Owner->Tex->LockRect(0, &lrect, &rect, 0))) + { + return false; + } + dest = (BYTE *)lrect.pBits; + if (Box->Padded) + { + dest += lrect.Pitch + (desc.Format == D3DFMT_L8 ? 1 : 4); + } + GameTex->FillBuffer(dest, lrect.Pitch, GameTex->GetHeight(), ToTexFmt(desc.Format)); + if (Box->Padded) + { + // Clear top padding row. + dest = (BYTE *)lrect.pBits; + int numbytes = GameTex->GetWidth() + 2; + if (desc.Format != D3DFMT_L8) + { + numbytes <<= 2; + } + memset(dest, 0, numbytes); + dest += lrect.Pitch; + // Clear left and right padding columns. + if (desc.Format == D3DFMT_L8) + { + for (int y = Box->Area.bottom - Box->Area.top - 2; y > 0; --y) + { + dest[0] = 0; + dest[numbytes-1] = 0; + dest += lrect.Pitch; + } + } + else + { + for (int y = Box->Area.bottom - Box->Area.top - 2; y > 0; --y) + { + *(DWORD *)dest = 0; + *(DWORD *)(dest + numbytes - 4) = 0; + dest += lrect.Pitch; + } + } + // Clear bottom padding row. + memset(dest, 0, numbytes); + } + Box->Owner->Tex->UnlockRect(0); + return true; +} + +//========================================================================== +// +// D3DTex :: GetTexFormat +// +// Returns the texture format that would best fit this texture. +// +//========================================================================== + +D3DFORMAT D3DTex::GetTexFormat() +{ + FTextureFormat fmt = GameTex->GetFormat(); + + IsGray = false; + + switch (fmt) + { + case TEX_Pal: return D3DFMT_L8; + case TEX_Gray: IsGray = true; return D3DFMT_L8; + case TEX_RGB: return D3DFMT_A8R8G8B8; + case TEX_DXT1: return D3DFMT_DXT1; + case TEX_DXT2: return D3DFMT_DXT2; + case TEX_DXT3: return D3DFMT_DXT3; + case TEX_DXT4: return D3DFMT_DXT4; + case TEX_DXT5: return D3DFMT_DXT5; + default: I_FatalError ("GameTex->GetFormat() returned invalid format."); + } + return D3DFMT_L8; +} + +//========================================================================== +// +// D3DTex :: ToTexFmt +// +// Converts a D3DFORMAT constant to something the FTexture system +// understands. +// +//========================================================================== + +FTextureFormat D3DTex::ToTexFmt(D3DFORMAT fmt) +{ + switch (fmt) + { + case D3DFMT_L8: return IsGray ? TEX_Gray : TEX_Pal; + case D3DFMT_A8R8G8B8: return TEX_RGB; + case D3DFMT_DXT1: return TEX_DXT1; + case D3DFMT_DXT2: return TEX_DXT2; + case D3DFMT_DXT3: return TEX_DXT3; + case D3DFMT_DXT4: return TEX_DXT4; + case D3DFMT_DXT5: return TEX_DXT5; + default: + assert(0); // LOL WUT? + return TEX_Pal; + } +} + +//========================================================================== +// +// D3DPal Constructor +// +//========================================================================== + +D3DPal::D3DPal(FRemapTable *remap, D3DFB *fb) + : Tex(NULL), Remap(remap) +{ + int count; + + // Attach to the palette list for the D3DFB + Next = fb->Palettes; + if (Next != NULL) + { + Next->Prev = &Next; + } + Prev = &fb->Palettes; + fb->Palettes = this; + + // Palette textures must be 256 entries for Shader Model 1.4 + if (fb->SM14) + { + count = 256; + // If the palette isn't big enough, then we don't need to + // worry about setting the gamma ramp. + DoColorSkip = (remap->NumEntries >= 256 - 8); + } + else + { + int pow2count; + + // Round up to the nearest power of 2. + for (pow2count = 1; pow2count < remap->NumEntries; pow2count <<= 1) + { } + count = pow2count; + DoColorSkip = false; + } + BorderColor = 0; + RoundedPaletteSize = count; + if (SUCCEEDED(fb->D3DDevice->CreateTexture(count, 1, 1, 0, + D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &Tex, NULL))) + { + if (!Update()) + { + Tex->Release(); + Tex = NULL; + } + } +} + +//========================================================================== +// +// D3DPal Destructor +// +//========================================================================== + +D3DPal::~D3DPal() +{ + SAFE_RELEASE( Tex ); + // Detach from the palette list + *Prev = Next; + if (Next != NULL) + { + Next->Prev = Prev; + } + // Remove link from the remap table + if (Remap != NULL) + { + Remap->Native = NULL; + } +} + +//========================================================================== +// +// D3DPal :: Update +// +// Copies the palette to the texture. +// +//========================================================================== + +bool D3DPal::Update() +{ + D3DLOCKED_RECT lrect; + D3DCOLOR *buff; + const PalEntry *pal; + int skipat, i; + + assert(Tex != NULL); + + if (FAILED(Tex->LockRect(0, &lrect, NULL, 0))) + { + return false; + } + buff = (D3DCOLOR *)lrect.pBits; + pal = Remap->Palette; + + // See explanation in UploadPalette() for skipat rationale. + skipat = MIN(Remap->NumEntries, DoColorSkip ? 256 - 8 : 256); + + for (i = 0; i < skipat; ++i) + { + buff[i] = D3DCOLOR_ARGB(pal[i].a, pal[i].r, pal[i].g, pal[i].b); + } + for (++i; i < Remap->NumEntries; ++i) + { + buff[i] = D3DCOLOR_ARGB(pal[i].a, pal[i-1].r, pal[i-1].g, pal[i-1].b); + } + BorderColor = D3DCOLOR_ARGB(pal[i].a, pal[i-1].r, pal[i-1].g, pal[i-1].b); + + Tex->UnlockRect(0); + return true; +} + +//========================================================================== +// +// D3DFB :: Begin2D +// +// Begins 2D mode drawing operations. In particular, DrawTexture is +// rerouted to use Direct3D instead of the software renderer. +// +//========================================================================== + +bool D3DFB::Begin2D(bool copy3d) +{ + if (!Accel2D) + { + return false; + } + if (In2D) + { + return true; + } + In2D = 2 - copy3d; + Update(); + In2D = 3; + + return true; +} + +//========================================================================== +// +// D3DFB :: DrawBlendingRect +// +// Call after Begin2D to blend the 3D view. +// +//========================================================================== + +void D3DFB::DrawBlendingRect() +{ + if (!In2D || !Accel2D) + { + return; + } + Dim(FlashColor, FlashAmount / 256.f, viewwindowx, viewwindowy, viewwidth, viewheight); +} + +//========================================================================== +// +// D3DFB :: CreateTexture +// +// Returns a native texture that wraps a FTexture. +// +//========================================================================== + +FNativeTexture *D3DFB::CreateTexture(FTexture *gametex, bool wrapping) +{ + D3DTex *tex = new D3DTex(gametex, this, wrapping); + if (tex->Box == NULL) + { + delete tex; + return NULL; + } + return tex; +} + +//========================================================================== +// +// D3DFB :: CreatePalette +// +// Returns a native texture that contains a palette. +// +//========================================================================== + +FNativePalette *D3DFB::CreatePalette(FRemapTable *remap) +{ + D3DPal *tex = new D3DPal(remap, this); + if (tex->Tex == NULL) + { + delete tex; + return NULL; + } + return tex; +} + +//========================================================================== +// +// D3DFB :: Clear +// +// Fills the specified region with a color. +// +//========================================================================== + +void D3DFB::Clear (int left, int top, int right, int bottom, int palcolor, uint32 color) +{ + if (In2D < 2) + { + Super::Clear(left, top, right, bottom, palcolor, color); + return; + } + if (!InScene) + { + return; + } + if (palcolor >= 0 && color == 0) + { + color = GPalette.BaseColors[palcolor]; + } + else if (APART(color) < 255) + { + Dim(color, APART(color)/255.f, left, top, right - left, bottom - top); + return; + } + AddColorOnlyQuad(left, top, right - left, bottom - top, color | 0xFF000000); +} + +//========================================================================== +// +// D3DFB :: Dim +// +//========================================================================== + +void D3DFB::Dim (PalEntry color, float amount, int x1, int y1, int w, int h) +{ + if (amount <= 0) + { + return; + } + if (In2D < 2) + { + Super::Dim(color, amount, x1, y1, w, h); + return; + } + if (!InScene) + { + return; + } + if (amount > 1) + { + amount = 1; + } + AddColorOnlyQuad(x1, y1, w, h, color | (int(amount * 255) << 24)); +} + +//========================================================================== +// +// D3DFB :: BeginLineBatch +// +//========================================================================== + +void D3DFB::BeginLineBatch() +{ + if (In2D < 2 || !InScene || BatchType == BATCH_Lines) + { + return; + } + EndQuadBatch(); // Make sure all quads have been drawn first. + VertexBuffer->Lock(0, 0, (void **)&VertexData, D3DLOCK_DISCARD); + VertexPos = 0; + BatchType = BATCH_Lines; +} + +//========================================================================== +// +// D3DFB :: EndLineBatch +// +//========================================================================== + +void D3DFB::EndLineBatch() +{ + if (In2D < 2 || !InScene || BatchType != BATCH_Lines) + { + return; + } + VertexBuffer->Unlock(); + if (VertexPos > 0) + { + SetPixelShader(Shaders[SHADER_VertexColor]); + SetAlphaBlend(D3DBLENDOP_ADD, D3DBLEND_SRCALPHA, D3DBLEND_INVSRCALPHA); + D3DDevice->SetStreamSource(0, VertexBuffer, 0, sizeof(FBVERTEX)); + D3DDevice->DrawPrimitive(D3DPT_LINELIST, 0, VertexPos / 2); + } + VertexPos = -1; + BatchType = BATCH_None; +} + +//========================================================================== +// +// D3DFB :: DrawLine +// +//========================================================================== + +void D3DFB::DrawLine(int x0, int y0, int x1, int y1, int palcolor, uint32 color) +{ + if (In2D < 2) + { + Super::DrawLine(x0, y0, x1, y1, palcolor, color); + return; + } + if (!InScene) + { + return; + } + if (BatchType != BATCH_Lines) + { + BeginLineBatch(); + } + if (VertexPos == NUM_VERTS) + { // Flush the buffer and refill it. + EndLineBatch(); + BeginLineBatch(); + } + // Add the endpoints to the vertex buffer. + VertexData[VertexPos].x = float(x0); + VertexData[VertexPos].y = float(y0) + LBOffset; + VertexData[VertexPos].z = 0; + VertexData[VertexPos].rhw = 1; + VertexData[VertexPos].color0 = color; + VertexData[VertexPos].color1 = 0; + VertexData[VertexPos].tu = 0; + VertexData[VertexPos].tv = 0; + + VertexData[VertexPos+1].x = float(x1); + VertexData[VertexPos+1].y = float(y1) + LBOffset; + VertexData[VertexPos+1].z = 0; + VertexData[VertexPos+1].rhw = 1; + VertexData[VertexPos+1].color0 = color; + VertexData[VertexPos+1].color1 = 0; + VertexData[VertexPos+1].tu = 0; + VertexData[VertexPos+1].tv = 0; + + VertexPos += 2; +} + +//========================================================================== +// +// D3DFB :: DrawPixel +// +//========================================================================== + +void D3DFB::DrawPixel(int x, int y, int palcolor, uint32 color) +{ + if (In2D < 2) + { + Super::DrawPixel(x, y, palcolor, color); + return; + } + if (!InScene) + { + return; + } + FBVERTEX pt = + { + float(x), float(y), 0, 1, color + }; + EndBatch(); // Draw out any batched operations. + SetPixelShader(Shaders[SHADER_VertexColor]); + SetAlphaBlend(D3DBLENDOP_ADD, D3DBLEND_SRCALPHA, D3DBLEND_INVSRCALPHA); + D3DDevice->DrawPrimitiveUP(D3DPT_POINTLIST, 1, &pt, sizeof(FBVERTEX)); +} + +//========================================================================== +// +// D3DFB :: DrawTextureV +// +// If not in 2D mode, just call the normal software version. +// If in 2D mode, then use Direct3D calls to perform the drawing. +// +//========================================================================== + +void D3DFB::DrawTextureParms (FTexture *img, DrawParms &parms) +{ + if (In2D < 2) + { + Super::DrawTextureParms(img, parms); + return; + } + if (!InScene) + { + return; + } + + D3DTex *tex = static_cast(img->GetNative(false)); + + if (tex == NULL) + { + assert(tex != NULL); + return; + } + + CheckQuadBatch(); + + double xscale = parms.destwidth / parms.texwidth; + double yscale = parms.destheight / parms.texheight; + double x0 = parms.x - parms.left * xscale; + double y0 = parms.y - parms.top * yscale; + double x1 = x0 + parms.destwidth; + double y1 = y0 + parms.destheight; + float u0 = tex->Box->Left; + float v0 = tex->Box->Top; + float u1 = tex->Box->Right; + float v1 = tex->Box->Bottom; + double uscale = 1.f / tex->Box->Owner->Width; + bool scissoring = false; + FBVERTEX *vert; + float yoffs; + + if (parms.flipX) + { + swapvalues(u0, u1); + } + if (parms.windowleft > 0 || parms.windowright < parms.texwidth) + { + double wi = MIN(parms.windowright, parms.texwidth); + x0 += parms.windowleft * xscale; + u0 = float(u0 + parms.windowleft * uscale); + x1 -= (parms.texwidth - wi) * xscale; + u1 = float(u1 - (parms.texwidth - wi) * uscale); + } + +#if 0 + float vscale = 1.f / tex->Box->Owner->Height / yscale; + if (y0 < parms.uclip) + { + v0 += (float(parms.uclip) - y0) * vscale; + y0 = float(parms.uclip); + } + if (y1 > parms.dclip) + { + v1 -= (y1 - float(parms.dclip)) * vscale; + y1 = float(parms.dclip); + } + if (x0 < parms.lclip) + { + u0 += float(parms.lclip - x0) * uscale / xscale * 2; + x0 = float(parms.lclip); + } + if (x1 > parms.rclip) + { + u1 -= (x1 - parms.rclip) * uscale / xscale * 2; + x1 = float(parms.rclip); + } +#else + // Use a scissor test because the math above introduces some jitter + // that is noticeable at low resolutions. Unfortunately, this means this + // quad has to be in a batch by itself. + if (y0 < parms.uclip || y1 > parms.dclip || x0 < parms.lclip || x1 > parms.rclip) + { + scissoring = true; + if (QuadBatchPos > 0) + { + EndQuadBatch(); + BeginQuadBatch(); + } + RECT scissor = { + parms.lclip, parms.uclip + LBOffsetI, + parms.rclip, parms.dclip + LBOffsetI + }; + D3DDevice->SetScissorRect(&scissor); + D3DDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE); + } +#endif + parms.bilinear = false; + + D3DCOLOR color0, color1; + BufferedTris *quad = &QuadExtra[QuadBatchPos]; + + if (!SetStyle(tex, parms, color0, color1, *quad)) + { + goto done; + } + + quad->Texture = tex->Box->Owner->Tex; + if (parms.bilinear) + { + quad->Flags |= BQF_Bilinear; + } + quad->NumTris = 2; + quad->NumVerts = 4; + + yoffs = GatheringWipeScreen ? 0.5f : 0.5f - LBOffset; + +#if 0 + // Coordinates are truncated to integers, because that's effectively + // what the software renderer does. The hardware will instead round + // to nearest, it seems. + x0 = floorf(x0) - 0.5f; + y0 = floorf(y0) - yoffs; + x1 = floorf(x1) - 0.5f; + y1 = floorf(y1) - yoffs; +#else + x0 = x0 - 0.5f; + y0 = y0 - yoffs; + x1 = x1 - 0.5f; + y1 = y1 - yoffs; +#endif + + vert = &VertexData[VertexPos]; + + // Fill the vertex buffer. + vert[0].x = float(x0); + vert[0].y = float(y0); + vert[0].z = 0; + vert[0].rhw = 1; + vert[0].color0 = color0; + vert[0].color1 = color1; + vert[0].tu = u0; + vert[0].tv = v0; + + vert[1].x = float(x1); + vert[1].y = float(y0); + vert[1].z = 0; + vert[1].rhw = 1; + vert[1].color0 = color0; + vert[1].color1 = color1; + vert[1].tu = u1; + vert[1].tv = v0; + + vert[2].x = float(x1); + vert[2].y = float(y1); + vert[2].z = 0; + vert[2].rhw = 1; + vert[2].color0 = color0; + vert[2].color1 = color1; + vert[2].tu = u1; + vert[2].tv = v1; + + vert[3].x = float(x0); + vert[3].y = float(y1); + vert[3].z = 0; + vert[3].rhw = 1; + vert[3].color0 = color0; + vert[3].color1 = color1; + vert[3].tu = u0; + vert[3].tv = v1; + + // Fill the vertex index buffer. + IndexData[IndexPos ] = VertexPos; + IndexData[IndexPos + 1] = VertexPos + 1; + IndexData[IndexPos + 2] = VertexPos + 2; + IndexData[IndexPos + 3] = VertexPos; + IndexData[IndexPos + 4] = VertexPos + 2; + IndexData[IndexPos + 5] = VertexPos + 3; + + // Batch the quad. + QuadBatchPos++; + VertexPos += 4; + IndexPos += 6; +done: + if (scissoring) + { + EndQuadBatch(); + D3DDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE); + } +} + +//========================================================================== +// +// D3DFB :: FlatFill +// +// Fills an area with a repeating copy of the texture. +// +//========================================================================== + +void D3DFB::FlatFill(int left, int top, int right, int bottom, FTexture *src, bool local_origin) +{ + if (In2D < 2) + { + Super::FlatFill(left, top, right, bottom, src, local_origin); + return; + } + if (!InScene) + { + return; + } + D3DTex *tex = static_cast(src->GetNative(true)); + if (tex == NULL) + { + return; + } + float yoffs = GatheringWipeScreen ? 0.5f : 0.5f - LBOffset; + float x0 = float(left); + float y0 = float(top); + float x1 = float(right); + float y1 = float(bottom); + float itw = 1.f / float(src->GetWidth()); + float ith = 1.f / float(src->GetHeight()); + float xo = local_origin ? x0 : 0; + float yo = local_origin ? y0 : 0; + float u0 = (x0 - xo) * itw; + float v0 = (y0 - yo) * ith; + float u1 = (x1 - xo) * itw; + float v1 = (y1 - yo) * ith; + x0 -= 0.5f; + y0 -= yoffs; + x1 -= 0.5f; + y1 -= yoffs; + + CheckQuadBatch(); + + BufferedTris *quad = &QuadExtra[QuadBatchPos]; + FBVERTEX *vert = &VertexData[VertexPos]; + + quad->Group1 = 0; + if (tex->GetTexFormat() == D3DFMT_L8 && !tex->IsGray) + { + quad->Flags = BQF_WrapUV | BQF_GamePalette; // | BQF_DisableAlphaTest; + quad->ShaderNum = BQS_PalTex; + } + else + { + quad->Flags = BQF_WrapUV; // | BQF_DisableAlphaTest; + quad->ShaderNum = BQS_Plain; + } + quad->Palette = NULL; + quad->Texture = tex->Box->Owner->Tex; + quad->NumVerts = 4; + quad->NumTris = 2; + + vert[0].x = x0; + vert[0].y = y0; + vert[0].z = 0; + vert[0].rhw = 1; + vert[0].color0 = 0; + vert[0].color1 = 0xFFFFFFFF; + vert[0].tu = u0; + vert[0].tv = v0; + + vert[1].x = x1; + vert[1].y = y0; + vert[1].z = 0; + vert[1].rhw = 1; + vert[1].color0 = 0; + vert[1].color1 = 0xFFFFFFFF; + vert[1].tu = u1; + vert[1].tv = v0; + + vert[2].x = x1; + vert[2].y = y1; + vert[2].z = 0; + vert[2].rhw = 1; + vert[2].color0 = 0; + vert[2].color1 = 0xFFFFFFFF; + vert[2].tu = u1; + vert[2].tv = v1; + + vert[3].x = x0; + vert[3].y = y1; + vert[3].z = 0; + vert[3].rhw = 1; + vert[3].color0 = 0; + vert[3].color1 = 0xFFFFFFFF; + vert[3].tu = u0; + vert[3].tv = v1; + + IndexData[IndexPos ] = VertexPos; + IndexData[IndexPos + 1] = VertexPos + 1; + IndexData[IndexPos + 2] = VertexPos + 2; + IndexData[IndexPos + 3] = VertexPos; + IndexData[IndexPos + 4] = VertexPos + 2; + IndexData[IndexPos + 5] = VertexPos + 3; + + QuadBatchPos++; + VertexPos += 4; + IndexPos += 6; +} + +//========================================================================== +// +// D3DFB :: FillSimplePoly +// +// Here, "simple" means that a simple triangle fan can draw it. +// +//========================================================================== + +void D3DFB::FillSimplePoly(FTexture *texture, FVector2 *points, int npoints, + double originx, double originy, double scalex, double scaley, + DAngle rotation, FDynamicColormap *colormap, int lightlevel) +{ + // Use an equation similar to player sprites to determine shade + double fadelevel = clamp((LIGHT2SHADE(lightlevel)/65536. - 12) / NUMCOLORMAPS, 0.0, 1.0); + + BufferedTris *quad; + FBVERTEX *verts; + D3DTex *tex; + float yoffs, uscale, vscale; + int i, ipos; + D3DCOLOR color0, color1; + float ox, oy; + float cosrot, sinrot; + bool dorotate = rotation != 0; + + if (npoints < 3) + { // This is no polygon. + return; + } + if (In2D < 2) + { + Super::FillSimplePoly(texture, points, npoints, originx, originy, scalex, scaley, rotation, colormap, lightlevel); + return; + } + if (!InScene) + { + return; + } + tex = static_cast(texture->GetNative(true)); + if (tex == NULL) + { + return; + } + + cosrot = (float)cos(rotation.Radians()); + sinrot = (float)sin(rotation.Radians()); + + CheckQuadBatch(npoints - 2, npoints); + quad = &QuadExtra[QuadBatchPos]; + verts = &VertexData[VertexPos]; + + color0 = 0; + color1 = 0xFFFFFFFF; + + quad->Group1 = 0; + if (tex->GetTexFormat() == D3DFMT_L8 && !tex->IsGray) + { + quad->Flags = BQF_WrapUV | BQF_GamePalette | BQF_DisableAlphaTest; + quad->ShaderNum = BQS_PalTex; + if (colormap != NULL) + { + if (colormap->Desaturate != 0) + { + quad->Flags |= BQF_Desaturated; + } + quad->ShaderNum = BQS_InGameColormap; + quad->Desat = colormap->Desaturate; + color0 = D3DCOLOR_ARGB(255, colormap->Color.r, colormap->Color.g, colormap->Color.b); + color1 = D3DCOLOR_ARGB(DWORD((1 - fadelevel) * 255), + DWORD(colormap->Fade.r * fadelevel), + DWORD(colormap->Fade.g * fadelevel), + DWORD(colormap->Fade.b * fadelevel)); + } + } + else + { + quad->Flags = BQF_WrapUV | BQF_DisableAlphaTest; + quad->ShaderNum = BQS_Plain; + } + quad->Palette = NULL; + quad->Texture = tex->Box->Owner->Tex; + quad->NumVerts = npoints; + quad->NumTris = npoints - 2; + + yoffs = GatheringWipeScreen ? 0 : LBOffset; + uscale = float(1.f / (texture->GetScaledWidth() * scalex)); + vscale = float(1.f / (texture->GetScaledHeight() * scaley)); + ox = float(originx); + oy = float(originy); + + for (i = 0; i < npoints; ++i) + { + verts[i].x = points[i].X; + verts[i].y = points[i].Y + yoffs; + verts[i].z = 0; + verts[i].rhw = 1; + verts[i].color0 = color0; + verts[i].color1 = color1; + float u = points[i].X - 0.5f - ox; + float v = points[i].Y - 0.5f - oy; + if (dorotate) + { + float t = u; + u = t * cosrot - v * sinrot; + v = v * cosrot + t * sinrot; + } + verts[i].tu = u * uscale; + verts[i].tv = v * vscale; + } + for (ipos = IndexPos, i = 2; i < npoints; ++i, ipos += 3) + { + IndexData[ipos ] = VertexPos; + IndexData[ipos + 1] = VertexPos + i - 1; + IndexData[ipos + 2] = VertexPos + i; + } + + QuadBatchPos++; + VertexPos += npoints; + IndexPos = ipos; +} + +//========================================================================== +// +// D3DFB :: AddColorOnlyQuad +// +// Adds a single-color, untextured quad to the batch. +// +//========================================================================== + +void D3DFB::AddColorOnlyQuad(int left, int top, int width, int height, D3DCOLOR color) +{ + BufferedTris *quad; + FBVERTEX *verts; + + CheckQuadBatch(); + quad = &QuadExtra[QuadBatchPos]; + verts = &VertexData[VertexPos]; + + float x = float(left) - 0.5f; + float y = float(top) - 0.5f + (GatheringWipeScreen ? 0 : LBOffset); + + quad->Group1 = 0; + quad->ShaderNum = BQS_ColorOnly; + if ((color & 0xFF000000) != 0xFF000000) + { + quad->BlendOp = D3DBLENDOP_ADD; + quad->SrcBlend = D3DBLEND_SRCALPHA; + quad->DestBlend = D3DBLEND_INVSRCALPHA; + } + quad->Palette = NULL; + quad->Texture = NULL; + quad->NumVerts = 4; + quad->NumTris = 2; + + verts[0].x = x; + verts[0].y = y; + verts[0].z = 0; + verts[0].rhw = 1; + verts[0].color0 = color; + verts[0].color1 = 0; + verts[0].tu = 0; + verts[0].tv = 0; + + verts[1].x = x + width; + verts[1].y = y; + verts[1].z = 0; + verts[1].rhw = 1; + verts[1].color0 = color; + verts[1].color1 = 0; + verts[1].tu = 0; + verts[1].tv = 0; + + verts[2].x = x + width; + verts[2].y = y + height; + verts[2].z = 0; + verts[2].rhw = 1; + verts[2].color0 = color; + verts[2].color1 = 0; + verts[2].tu = 0; + verts[2].tv = 0; + + verts[3].x = x; + verts[3].y = y + height; + verts[3].z = 0; + verts[3].rhw = 1; + verts[3].color0 = color; + verts[3].color1 = 0; + verts[3].tu = 0; + verts[3].tv = 0; + + IndexData[IndexPos ] = VertexPos; + IndexData[IndexPos + 1] = VertexPos + 1; + IndexData[IndexPos + 2] = VertexPos + 2; + IndexData[IndexPos + 3] = VertexPos; + IndexData[IndexPos + 4] = VertexPos + 2; + IndexData[IndexPos + 5] = VertexPos + 3; + + QuadBatchPos++; + VertexPos += 4; + IndexPos += 6; +} + +//========================================================================== +// +// D3DFB :: AddColorOnlyRect +// +// Like AddColorOnlyQuad, except it's hollow. +// +//========================================================================== + +void D3DFB::AddColorOnlyRect(int left, int top, int width, int height, D3DCOLOR color) +{ + AddColorOnlyQuad(left, top, width - 1, 1, color); // top + AddColorOnlyQuad(left + width - 1, top, 1, height - 1, color); // right + AddColorOnlyQuad(left + 1, top + height - 1, width - 1, 1, color); // bottom + AddColorOnlyQuad(left, top + 1, 1, height - 1, color); // left +} + +//========================================================================== +// +// D3DFB :: CheckQuadBatch +// +// Make sure there's enough room in the batch for one more set of triangles. +// +//========================================================================== + +void D3DFB::CheckQuadBatch(int numtris, int numverts) +{ + if (BatchType == BATCH_Lines) + { + EndLineBatch(); + } + else if (QuadBatchPos == MAX_QUAD_BATCH || + VertexPos + numverts > NUM_VERTS || + IndexPos + numtris * 3 > NUM_INDEXES) + { + EndQuadBatch(); + } + if (QuadBatchPos < 0) + { + BeginQuadBatch(); + } +} + +//========================================================================== +// +// D3DFB :: BeginQuadBatch +// +// Locks the vertex buffer for quads and sets the cursor to 0. +// +//========================================================================== + +void D3DFB::BeginQuadBatch() +{ + if (In2D < 2 || !InScene || QuadBatchPos >= 0) + { + return; + } + EndLineBatch(); // Make sure all lines have been drawn first. + VertexBuffer->Lock(0, 0, (void **)&VertexData, D3DLOCK_DISCARD); + IndexBuffer->Lock(0, 0, (void **)&IndexData, D3DLOCK_DISCARD); + VertexPos = 0; + IndexPos = 0; + QuadBatchPos = 0; + BatchType = BATCH_Quads; +} + +//========================================================================== +// +// D3DFB :: EndQuadBatch +// +// Draws all the quads that have been batched up. +// +//========================================================================== + +void D3DFB::EndQuadBatch() +{ + if (In2D < 2 || !InScene || BatchType != BATCH_Quads) + { + return; + } + BatchType = BATCH_None; + VertexBuffer->Unlock(); + IndexBuffer->Unlock(); + if (QuadBatchPos == 0) + { + QuadBatchPos = -1; + VertexPos = -1; + IndexPos = -1; + return; + } + D3DDevice->SetStreamSource(0, VertexBuffer, 0, sizeof(FBVERTEX)); + D3DDevice->SetIndices(IndexBuffer); + bool uv_wrapped = false; + bool uv_should_wrap; + int indexpos, vertpos; + + indexpos = vertpos = 0; + for (int i = 0; i < QuadBatchPos; ) + { + const BufferedTris *quad = &QuadExtra[i]; + int j; + + int startindex = indexpos; + int startvertex = vertpos; + + indexpos += quad->NumTris * 3; + vertpos += quad->NumVerts; + + // Quads with matching parameters should be done with a single + // DrawPrimitive call. + for (j = i + 1; j < QuadBatchPos; ++j) + { + const BufferedTris *q2 = &QuadExtra[j]; + if (quad->Texture != q2->Texture || + quad->Group1 != q2->Group1 || + quad->Palette != q2->Palette) + { + break; + } + if (quad->ShaderNum == BQS_InGameColormap && (quad->Flags & BQF_Desaturated) && quad->Desat != q2->Desat) + { + break; + } + indexpos += q2->NumTris * 3; + vertpos += q2->NumVerts; + } + + // Set the palette (if one) + if ((quad->Flags & BQF_Paletted) == BQF_GamePalette) + { + SetPaletteTexture(PaletteTexture, 256, BorderColor); + } + else if ((quad->Flags & BQF_Paletted) == BQF_CustomPalette) + { + assert(quad->Palette != NULL); + SetPaletteTexture(quad->Palette->Tex, quad->Palette->RoundedPaletteSize, quad->Palette->BorderColor); + } +#if 0 + // Set paletted bilinear filtering (IF IT WORKED RIGHT!) + if ((quad->Flags & (BQF_Paletted | BQF_Bilinear)) == (BQF_Paletted | BQF_Bilinear)) + { + SetPalTexBilinearConstants(quad->Texture); + } +#endif + + // Set the alpha blending + SetAlphaBlend(D3DBLENDOP(quad->BlendOp), D3DBLEND(quad->SrcBlend), D3DBLEND(quad->DestBlend)); + + // Set the alpha test + EnableAlphaTest(!(quad->Flags & BQF_DisableAlphaTest)); + + // Set the pixel shader + if (quad->ShaderNum == BQS_PalTex) + { + SetPixelShader(Shaders[(quad->Flags & BQF_InvertSource) ? + SHADER_NormalColorPalInv : SHADER_NormalColorPal]); + } + else if (quad->ShaderNum == BQS_Plain) + { + SetPixelShader(Shaders[(quad->Flags & BQF_InvertSource) ? + SHADER_NormalColorInv : SHADER_NormalColor]); + } + else if (quad->ShaderNum == BQS_RedToAlpha) + { + SetPixelShader(Shaders[(quad->Flags & BQF_InvertSource) ? + SHADER_RedToAlphaInv : SHADER_RedToAlpha]); + } + else if (quad->ShaderNum == BQS_ColorOnly) + { + SetPixelShader(Shaders[SHADER_VertexColor]); + } + else if (quad->ShaderNum == BQS_SpecialColormap) + { + int select; + + select = !!(quad->Flags & BQF_Paletted); + SetPixelShader(Shaders[SHADER_SpecialColormap + select]); + } + else if (quad->ShaderNum == BQS_InGameColormap) + { + int select; + + select = !!(quad->Flags & BQF_Desaturated); + select |= !!(quad->Flags & BQF_InvertSource) << 1; + select |= !!(quad->Flags & BQF_Paletted) << 2; + if (quad->Flags & BQF_Desaturated) + { + SetConstant(PSCONST_Desaturation, quad->Desat / 255.f, (255 - quad->Desat) / 255.f, 0, 0); + } + SetPixelShader(Shaders[SHADER_InGameColormap + select]); + } + + // Set the texture clamp addressing mode + uv_should_wrap = !!(quad->Flags & BQF_WrapUV); + if (uv_wrapped != uv_should_wrap) + { + DWORD mode = uv_should_wrap ? D3DTADDRESS_WRAP : D3DTADDRESS_BORDER; + uv_wrapped = uv_should_wrap; + D3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, mode); + D3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, mode); + } + + // Set the texture + if (quad->Texture != NULL) + { + SetTexture(0, quad->Texture); + } + + // Draw the quad + D3DDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, + startvertex, // MinIndex + vertpos - startvertex, // NumVertices + startindex, // StartIndex + (indexpos - startindex) / 3 // PrimitiveCount + /*4 * i, 4 * (j - i), 6 * i, 2 * (j - i)*/); + i = j; + } + if (uv_wrapped) + { + D3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER); + D3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER); + } + QuadBatchPos = -1; + VertexPos = -1; + IndexPos = -1; +} + +//========================================================================== +// +// D3DFB :: EndBatch +// +// Draws whichever type of primitive is currently being batched. +// +//========================================================================== + +void D3DFB::EndBatch() +{ + if (BatchType == BATCH_Quads) + { + EndQuadBatch(); + } + else if (BatchType == BATCH_Lines) + { + EndLineBatch(); + } +} + +//========================================================================== +// +// D3DFB :: SetStyle +// +// Patterned after R_SetPatchStyle. +// +//========================================================================== + +bool D3DFB::SetStyle(D3DTex *tex, DrawParms &parms, D3DCOLOR &color0, D3DCOLOR &color1, BufferedTris &quad) +{ + D3DFORMAT fmt = tex->GetTexFormat(); + FRenderStyle style = parms.style; + float alpha; + bool stencilling; + + if (style.Flags & STYLEF_TransSoulsAlpha) + { + alpha = transsouls; + } + else if (style.Flags & STYLEF_Alpha1) + { + alpha = 1; + } + else + { + alpha = clamp(parms.Alpha, 0.f, 1.f); + } + + style.CheckFuzz(); + if (style.BlendOp == STYLEOP_Shadow) + { + style = LegacyRenderStyles[STYLE_TranslucentStencil]; + alpha = 0.3f; + parms.fillcolor = 0; + } + + // FIXME: Fuzz effect is not written + if (style.BlendOp == STYLEOP_FuzzOrAdd || style.BlendOp == STYLEOP_Fuzz) + { + style.BlendOp = STYLEOP_Add; + } + else if (style.BlendOp == STYLEOP_FuzzOrSub) + { + style.BlendOp = STYLEOP_Sub; + } + else if (style.BlendOp == STYLEOP_FuzzOrRevSub) + { + style.BlendOp = STYLEOP_RevSub; + } + + stencilling = false; + quad.Palette = NULL; + quad.Flags = 0; + quad.Desat = 0; + + switch (style.BlendOp) + { + default: + case STYLEOP_Add: quad.BlendOp = D3DBLENDOP_ADD; break; + case STYLEOP_Sub: quad.BlendOp = D3DBLENDOP_SUBTRACT; break; + case STYLEOP_RevSub: quad.BlendOp = D3DBLENDOP_REVSUBTRACT; break; + case STYLEOP_None: return false; + } + quad.SrcBlend = GetStyleAlpha(style.SrcAlpha); + quad.DestBlend = GetStyleAlpha(style.DestAlpha); + + if (style.Flags & STYLEF_InvertOverlay) + { + // Only the overlay color is inverted, not the overlay alpha. + parms.colorOverlay = D3DCOLOR_ARGB(APART(parms.colorOverlay), + 255 - RPART(parms.colorOverlay), 255 - GPART(parms.colorOverlay), + 255 - BPART(parms.colorOverlay)); + } + + SetColorOverlay(parms.colorOverlay, alpha, color0, color1); + + if (style.Flags & STYLEF_ColorIsFixed) + { + if (style.Flags & STYLEF_InvertSource) + { // Since the source color is a constant, we can invert it now + // without spending time doing it in the shader. + parms.fillcolor = D3DCOLOR_XRGB(255 - RPART(parms.fillcolor), + 255 - GPART(parms.fillcolor), 255 - BPART(parms.fillcolor)); + } + // Set up the color mod to replace the color from the image data. + color0 = (color0 & D3DCOLOR_RGBA(0,0,0,255)) | (parms.fillcolor & D3DCOLOR_RGBA(255,255,255,0)); + color1 &= D3DCOLOR_RGBA(0,0,0,255); + + if (style.Flags & STYLEF_RedIsAlpha) + { + // Note that if the source texture is paletted, the palette is ignored. + quad.Flags = 0; + quad.ShaderNum = BQS_RedToAlpha; + } + else if (fmt == D3DFMT_L8) + { + quad.Flags = BQF_GamePalette; + quad.ShaderNum = BQS_PalTex; + } + else + { + quad.Flags = 0; + quad.ShaderNum = BQS_Plain; + } + } + else + { + if (style.Flags & STYLEF_RedIsAlpha) + { + quad.Flags = 0; + quad.ShaderNum = BQS_RedToAlpha; + } + else if (fmt == D3DFMT_L8) + { + if (parms.remap != NULL) + { + quad.Flags = BQF_CustomPalette; + quad.Palette = reinterpret_cast(parms.remap->GetNative()); + quad.ShaderNum = BQS_PalTex; + } + else if (tex->IsGray) + { + quad.Flags = 0; + quad.ShaderNum = BQS_Plain; + } + else + { + quad.Flags = BQF_GamePalette; + quad.ShaderNum = BQS_PalTex; + } + } + else + { + quad.Flags = 0; + quad.ShaderNum = BQS_Plain; + } + if (style.Flags & STYLEF_InvertSource) + { + quad.Flags |= BQF_InvertSource; + } + + if (parms.specialcolormap != NULL) + { // Emulate an invulnerability or similar colormap. + float *start, *end; + start = parms.specialcolormap->ColorizeStart; + end = parms.specialcolormap->ColorizeEnd; + if (quad.Flags & BQF_InvertSource) + { + quad.Flags &= ~BQF_InvertSource; + swapvalues(start, end); + } + quad.ShaderNum = BQS_SpecialColormap; + color0 = D3DCOLOR_RGBA(DWORD(start[0]/2*255), DWORD(start[1]/2*255), DWORD(start[2]/2*255), color0 >> 24); + color1 = D3DCOLOR_RGBA(DWORD(end[0]/2*255), DWORD(end[1]/2*255), DWORD(end[2]/2*255), color1 >> 24); + } + else if (parms.colormapstyle != NULL) + { // Emulate the fading from an in-game colormap (colorized, faded, and desaturated) + if (parms.colormapstyle->Desaturate != 0) + { + quad.Flags |= BQF_Desaturated; + } + quad.ShaderNum = BQS_InGameColormap; + quad.Desat = parms.colormapstyle->Desaturate; + color0 = D3DCOLOR_ARGB(color1 >> 24, + parms.colormapstyle->Color.r, + parms.colormapstyle->Color.g, + parms.colormapstyle->Color.b); + double fadelevel = parms.colormapstyle->FadeLevel; + color1 = D3DCOLOR_ARGB(DWORD((1 - fadelevel) * 255), + DWORD(parms.colormapstyle->Fade.r * fadelevel), + DWORD(parms.colormapstyle->Fade.g * fadelevel), + DWORD(parms.colormapstyle->Fade.b * fadelevel)); + } + } + + // For unmasked images, force the alpha from the image data to be ignored. + if (!parms.masked && quad.ShaderNum != BQS_InGameColormap) + { + color0 = (color0 & D3DCOLOR_RGBA(255, 255, 255, 0)) | D3DCOLOR_COLORVALUE(0, 0, 0, alpha); + color1 &= D3DCOLOR_RGBA(255, 255, 255, 0); + + // If our alpha is one and we are doing normal adding, then we can turn the blend off completely. + if (quad.BlendOp == D3DBLENDOP_ADD && + ((alpha == 1 && quad.SrcBlend == D3DBLEND_SRCALPHA) || quad.SrcBlend == D3DBLEND_ONE) && + ((alpha == 1 && quad.DestBlend == D3DBLEND_INVSRCALPHA) || quad.DestBlend == D3DBLEND_ZERO)) + { + quad.BlendOp = D3DBLENDOP(0); + } + quad.Flags |= BQF_DisableAlphaTest; + } + return true; +} + +D3DBLEND D3DFB::GetStyleAlpha(int type) +{ + switch (type) + { + case STYLEALPHA_Zero: return D3DBLEND_ZERO; + case STYLEALPHA_One: return D3DBLEND_ONE; + case STYLEALPHA_Src: return D3DBLEND_SRCALPHA; + case STYLEALPHA_InvSrc: return D3DBLEND_INVSRCALPHA; + default: return D3DBLEND_ZERO; + } +} + + +void D3DFB::SetColorOverlay(DWORD color, float alpha, D3DCOLOR &color0, D3DCOLOR &color1) +{ + if (APART(color) != 0) + { + int a = APART(color) * 256 / 255; + color0 = D3DCOLOR_RGBA( + (RPART(color) * a) >> 8, + (GPART(color) * a) >> 8, + (BPART(color) * a) >> 8, + 0); + a = 256 - a; + color1 = D3DCOLOR_RGBA(a, a, a, int(alpha * 255)); + } + else + { + color0 = 0; + color1 = D3DCOLOR_COLORVALUE(1, 1, 1, alpha); + } +} + +void D3DFB::EnableAlphaTest(BOOL enabled) +{ + if (enabled != AlphaTestEnabled) + { + AlphaTestEnabled = enabled; + D3DDevice->SetRenderState(D3DRS_ALPHATESTENABLE, enabled); + } +} + +void D3DFB::SetAlphaBlend(D3DBLENDOP op, D3DBLEND srcblend, D3DBLEND destblend) +{ + if (op == 0) + { // Disable alpha blend + if (AlphaBlendEnabled) + { + AlphaBlendEnabled = FALSE; + D3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); + } + } + else + { // Enable alpha blend + assert(srcblend != 0); + assert(destblend != 0); + + if (!AlphaBlendEnabled) + { + AlphaBlendEnabled = TRUE; + D3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); + } + if (AlphaBlendOp != op) + { + AlphaBlendOp = op; + D3DDevice->SetRenderState(D3DRS_BLENDOP, op); + } + if (AlphaSrcBlend != srcblend) + { + AlphaSrcBlend = srcblend; + D3DDevice->SetRenderState(D3DRS_SRCBLEND, srcblend); + } + if (AlphaDestBlend != destblend) + { + AlphaDestBlend = destblend; + D3DDevice->SetRenderState(D3DRS_DESTBLEND, destblend); + } + } +} + +void D3DFB::SetConstant(int cnum, float r, float g, float b, float a) +{ + if (Constant[cnum][0] != r || + Constant[cnum][1] != g || + Constant[cnum][2] != b || + Constant[cnum][3] != a) + { + Constant[cnum][0] = r; + Constant[cnum][1] = g; + Constant[cnum][2] = b; + Constant[cnum][3] = a; + D3DDevice->SetPixelShaderConstantF(cnum, Constant[cnum], 1); + } +} + +void D3DFB::SetPixelShader(IDirect3DPixelShader9 *shader) +{ + if (CurPixelShader != shader) + { + CurPixelShader = shader; + D3DDevice->SetPixelShader(shader); + } +} + +void D3DFB::SetTexture(int tnum, IDirect3DTexture9 *texture) +{ + assert(unsigned(tnum) < countof(Texture)); + if (Texture[tnum] != texture) + { + Texture[tnum] = texture; + D3DDevice->SetTexture(tnum, texture); + } +} + +void D3DFB::SetPaletteTexture(IDirect3DTexture9 *texture, int count, D3DCOLOR border_color) +{ + if (SM14) + { + // Shader Model 1.4 only uses 256-color palettes. + SetConstant(PSCONST_PaletteMod, 1.f, 0.5f / 256.f, 0, 0); + if (border_color != 0 && CurBorderColor != border_color) + { + CurBorderColor = border_color; + D3DDevice->SetSamplerState(1, D3DSAMP_BORDERCOLOR, border_color); + } + } + else + { + // The pixel shader receives color indexes in the range [0.0,1.0]. + // The palette texture is also addressed in the range [0.0,1.0], + // HOWEVER the coordinate 1.0 is the right edge of the texture and + // not actually the texture itself. We need to scale and shift + // the palette indexes so they lie exactly in the center of each + // texel. For a normal palette with 256 entries, that means the + // range we use should be [0.5,255.5], adjusted so the coordinate + // is still within [0.0,1.0]. + // + // The constant register c2 is used to hold the multiplier in the + // x part and the adder in the y part. + float fcount = 1 / float(count); + SetConstant(PSCONST_PaletteMod, 255 * fcount, 0.5f * fcount, 0, 0); + } + SetTexture(1, texture); +} + +void D3DFB::SetPalTexBilinearConstants(Atlas *tex) +{ +#if 0 + float con[8]; + + // Don't bother doing anything if the constants won't be used. + if (PalTexShader == PalTexBilinearShader) + { + return; + } + + con[0] = float(tex->Width); + con[1] = float(tex->Height); + con[2] = 0; + con[3] = 1 / con[0]; + con[4] = 0; + con[5] = 1 / con[1]; + con[6] = con[5]; + con[7] = con[3]; + + D3DDevice->SetPixelShaderConstantF(3, con, 2); +#endif +} diff --git a/src/win32/fb_d3d9_wipe.cpp b/src/win32/fb_d3d9_wipe.cpp new file mode 100644 index 000000000..fb46cc215 --- /dev/null +++ b/src/win32/fb_d3d9_wipe.cpp @@ -0,0 +1,658 @@ +/* +** fb_d3d9_wipe.cpp +** Implements the different screen wipes using Direct3D calls. +** +**--------------------------------------------------------------------------- +** Copyright 1998-2008 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +// HEADER FILES ------------------------------------------------------------ + +#ifdef _DEBUG +#define D3D_DEBUG_INFO +#endif +#define DIRECT3D_VERSION 0x0900 +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include + +#define USE_WINDOWS_DWORD +#include "doomtype.h" +#include "f_wipe.h" +#include "win32iface.h" +#include "templates.h" +#include "m_random.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +class D3DFB::Wiper_Crossfade : public D3DFB::Wiper +{ +public: + Wiper_Crossfade(); + bool Run(int ticks, D3DFB *fb); + +private: + int Clock; +}; + +class D3DFB::Wiper_Melt : public D3DFB::Wiper +{ +public: + Wiper_Melt(); + bool Run(int ticks, D3DFB *fb); + +private: + // Match the strip sizes that oldschool Doom used. + static const int WIDTH = 160, HEIGHT = 200; + int y[WIDTH]; +}; + +class D3DFB::Wiper_Burn : public D3DFB::Wiper +{ +public: + Wiper_Burn(D3DFB *fb); + ~Wiper_Burn(); + bool Run(int ticks, D3DFB *fb); + +private: + static const int WIDTH = 64, HEIGHT = 64; + BYTE BurnArray[WIDTH * (HEIGHT + 5)]; + IDirect3DTexture9 *BurnTexture; + int Density; + int BurnTime; + + struct BURNVERTEX + { + FLOAT x, y, z, rhw; + FLOAT tu0, tv0; + FLOAT tu1, tv1; + }; +#define D3DFVF_BURNVERTEX (D3DFVF_XYZRHW|D3DFVF_TEX2) +}; + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// D3DFB :: WipeStartScreen +// +// Called before the current screen has started rendering. This needs to +// save what was drawn the previous frame so that it can be animated into +// what gets drawn this frame. +// +// In fullscreen mode, we use GetFrontBufferData() to grab the data that +// is visible on screen right now. +// +// In windowed mode, we can't do that because we'll get the whole desktop. +// Instead, we can conveniently use the TempRenderTexture, which is normally +// used for gamma-correcting copying the image to the back buffer. +// +//========================================================================== + +bool D3DFB::WipeStartScreen(int type) +{ + IDirect3DSurface9 *tsurf; + D3DSURFACE_DESC desc; + + if (!Accel2D) + { + return Super::WipeStartScreen(type); + } + + switch (type) + { + case wipe_Melt: + ScreenWipe = new Wiper_Melt; + break; + + case wipe_Burn: + ScreenWipe = new Wiper_Burn(this); + break; + + case wipe_Fade: + ScreenWipe = new Wiper_Crossfade; + break; + + default: + return false; + } + + InitialWipeScreen = GetCurrentScreen(D3DPOOL_DEFAULT); + + // Create another texture to copy the final wipe screen to so + // we can still gamma correct the wipe. Since this is just for + // gamma correction, it's okay to fail (though not desirable.) + if (PixelDoubling || Windowed) + { + if (SUCCEEDED(TempRenderTexture->GetSurfaceLevel(0, &tsurf))) + { + if (FAILED(tsurf->GetDesc(&desc)) || + FAILED(D3DDevice->CreateTexture(desc.Width, desc.Height, + 1, D3DUSAGE_RENDERTARGET, desc.Format, D3DPOOL_DEFAULT, + &FinalWipeScreen, NULL))) + { + (FinalWipeScreen = TempRenderTexture)->AddRef(); + } + tsurf->Release(); + } + } + else + { + (FinalWipeScreen = TempRenderTexture)->AddRef(); + } + + // Make even fullscreen model render to the TempRenderTexture, so + // we can have a copy of the new screen readily available. + GatheringWipeScreen = true; + return true; +} + +//========================================================================== +// +// D3DFB :: WipeEndScreen +// +// The screen we want to animate to has just been drawn. This function is +// called in place of Update(), so it has not been Presented yet. +// +//========================================================================== + +void D3DFB::WipeEndScreen() +{ + if (!Accel2D) + { + Super::WipeEndScreen(); + return; + } + + // Don't do anything if there is no starting point. + if (InitialWipeScreen == NULL) + { + return; + } + + // If the whole screen was drawn without 2D accel, get it in to + // video memory now. + if (!In2D) + { + Begin2D(true); + } + + EndBatch(); // Make sure all batched primitives have been drawn. + + // Don't do anything if there is no ending point. + if (OldRenderTarget == NULL) + { + return; + } + + // If these are different, reverse their roles so we don't need to + // waste time copying from TempRenderTexture to FinalWipeScreen. + if (FinalWipeScreen != TempRenderTexture) + { + swapvalues(RenderTexture[CurrRenderTexture], FinalWipeScreen); + TempRenderTexture = RenderTexture[CurrRenderTexture]; + } + + // At this point, InitialWipeScreen holds the screen we are wiping from. + // FinalWipeScreen holds the screen we are wiping to, which may be the + // same texture as TempRenderTexture. +} + +//========================================================================== +// +// D3DFB :: WipeDo +// +// Perform the actual wipe animation. The number of tics since the last +// time this function was called is passed in. Returns true when the wipe +// is over. The first time this function has been called, the screen is +// still locked from before and EndScene() still has not been called. +// Successive times need to call BeginScene(). +// +//========================================================================== + +bool D3DFB::WipeDo(int ticks) +{ + if (!Accel2D) + { + return Super::WipeDo(ticks); + } + + // Sanity checks. + if (InitialWipeScreen == NULL || FinalWipeScreen == NULL) + { + return true; + } + if (GatheringWipeScreen) + { // This is the first time we've been called for this wipe. + GatheringWipeScreen = false; + + if (OldRenderTarget == NULL) + { + return true; + } + D3DDevice->SetRenderTarget(0, OldRenderTarget); + } + else + { // This is the second or later time we've been called for this wipe. + D3DDevice->BeginScene(); + InScene = true; + } + SAFE_RELEASE( OldRenderTarget ); + if (TempRenderTexture != NULL && TempRenderTexture != FinalWipeScreen) + { + IDirect3DSurface9 *targetsurf; + if (SUCCEEDED(TempRenderTexture->GetSurfaceLevel(0, &targetsurf))) + { + if (SUCCEEDED(D3DDevice->GetRenderTarget(0, &OldRenderTarget))) + { + if (FAILED(D3DDevice->SetRenderTarget(0, targetsurf))) + { + // Setting the render target failed. + } + } + targetsurf->Release(); + } + } + In2D = 3; + + EnableAlphaTest(FALSE); + bool done = ScreenWipe->Run(ticks, this); + DrawLetterbox(); + return done; +} + +//========================================================================== +// +// D3DFB :: WipeCleanup +// +// Release any resources that were specifically created for the wipe. +// +//========================================================================== + +void D3DFB::WipeCleanup() +{ + if (ScreenWipe != NULL) + { + delete ScreenWipe; + ScreenWipe = NULL; + } + SAFE_RELEASE( InitialWipeScreen ); + SAFE_RELEASE( FinalWipeScreen ); + GatheringWipeScreen = false; + if (!Accel2D) + { + Super::WipeCleanup(); + return; + } +} + +//========================================================================== +// +// D3DFB :: Wiper Constructor +// +//========================================================================== + +D3DFB::Wiper::~Wiper() +{ +} + +//========================================================================== +// +// D3DFB :: Wiper :: DrawScreen +// +// Draw either the initial or target screen completely to the screen. +// +//========================================================================== + +void D3DFB::Wiper::DrawScreen(D3DFB *fb, IDirect3DTexture9 *tex, + D3DBLENDOP blendop, D3DCOLOR color0, D3DCOLOR color1) +{ + FBVERTEX verts[4]; + + fb->CalcFullscreenCoords(verts, false, false, color0, color1); + fb->D3DDevice->SetFVF(D3DFVF_FBVERTEX); + fb->SetTexture(0, tex); + fb->SetAlphaBlend(blendop, D3DBLEND_SRCALPHA, D3DBLEND_INVSRCALPHA); + fb->SetPixelShader(fb->Shaders[SHADER_NormalColor]); + fb->D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, verts, sizeof(FBVERTEX)); +} + +// WIPE: CROSSFADE --------------------------------------------------------- + +//========================================================================== +// +// D3DFB :: Wiper_Crossfade Constructor +// +//========================================================================== + +D3DFB::Wiper_Crossfade::Wiper_Crossfade() +: Clock(0) +{ +} + +//========================================================================== +// +// D3DFB :: Wiper_Crossfade :: Run +// +// Fades the old screen into the new one over 32 ticks. +// +//========================================================================== + +bool D3DFB::Wiper_Crossfade::Run(int ticks, D3DFB *fb) +{ + Clock += ticks; + + // Put the initial screen back to the buffer. + DrawScreen(fb, fb->InitialWipeScreen); + + // Draw the new screen on top of it. + DrawScreen(fb, fb->FinalWipeScreen, D3DBLENDOP_ADD, + D3DCOLOR_COLORVALUE(0,0,0,Clock / 32.f), D3DCOLOR_RGBA(255,255,255,0)); + + return Clock >= 32; +} + +// WIPE: MELT -------------------------------------------------------------- + +//========================================================================== +// +// D3DFB :: Wiper_Melt Constructor +// +//========================================================================== + +D3DFB::Wiper_Melt::Wiper_Melt() +{ + int i, r; + + // setup initial column positions + // (y<0 => not ready to scroll yet) + y[0] = -(M_Random() & 15); + for (i = 1; i < WIDTH; ++i) + { + r = (M_Random()%3) - 1; + y[i] = clamp(y[i-1] + r, -15, 0); + } +} + +//========================================================================== +// +// D3DFB :: Wiper_Melt :: Run +// +// Fades the old screen into the new one over 32 ticks. +// +//========================================================================== + +bool D3DFB::Wiper_Melt::Run(int ticks, D3DFB *fb) +{ + // Draw the new screen on the bottom. + DrawScreen(fb, fb->FinalWipeScreen); + + int i, dy; + int fbwidth = fb->Width; + int fbheight = fb->Height; + bool done = true; + + // Copy the old screen in vertical strips on top of the new one. + while (ticks--) + { + done = true; + for (i = 0; i < WIDTH; i++) + { + if (y[i] < 0) + { + y[i]++; + done = false; + } + else if (y[i] < HEIGHT) + { + dy = (y[i] < 16) ? y[i]+1 : 8; + y[i] = MIN(y[i] + dy, HEIGHT); + done = false; + } + if (ticks == 0) + { // Only draw for the final tick. + RECT rect; + POINT dpt; + + dpt.x = i * fbwidth / WIDTH; + dpt.y = MAX(0, y[i] * fbheight / HEIGHT); + rect.left = dpt.x; + rect.top = 0; + rect.right = (i + 1) * fbwidth / WIDTH; + rect.bottom = fbheight - dpt.y; + if (rect.bottom > rect.top) + { + fb->CheckQuadBatch(); + + BufferedTris *quad = &fb->QuadExtra[fb->QuadBatchPos]; + FBVERTEX *vert = &fb->VertexData[fb->VertexPos]; + WORD *index = &fb->IndexData[fb->IndexPos]; + + quad->Group1 = 0; + quad->Flags = BQF_DisableAlphaTest; + quad->ShaderNum = BQS_Plain; + quad->Palette = NULL; + quad->Texture = fb->InitialWipeScreen; + quad->NumVerts = 4; + quad->NumTris = 2; + + // Fill the vertex buffer. + float u0 = rect.left / float(fb->FBWidth); + float v0 = 0; + float u1 = rect.right / float(fb->FBWidth); + float v1 = (rect.bottom - rect.top) / float(fb->FBHeight); + + float x0 = float(rect.left) - 0.5f; + float x1 = float(rect.right) - 0.5f; + float y0 = float(dpt.y + fb->LBOffsetI) - 0.5f; + float y1 = float(fbheight + fb->LBOffsetI) - 0.5f; + + vert[0].x = x0; + vert[0].y = y0; + vert[0].z = 0; + vert[0].rhw = 1; + vert[0].color0 = 0; + vert[0].color1 = 0xFFFFFFF; + vert[0].tu = u0; + vert[0].tv = v0; + + vert[1].x = x1; + vert[1].y = y0; + vert[1].z = 0; + vert[1].rhw = 1; + vert[1].color0 = 0; + vert[1].color1 = 0xFFFFFFF; + vert[1].tu = u1; + vert[1].tv = v0; + + vert[2].x = x1; + vert[2].y = y1; + vert[2].z = 0; + vert[2].rhw = 1; + vert[2].color0 = 0; + vert[2].color1 = 0xFFFFFFF; + vert[2].tu = u1; + vert[2].tv = v1; + + vert[3].x = x0; + vert[3].y = y1; + vert[3].z = 0; + vert[3].rhw = 1; + vert[3].color0 = 0; + vert[3].color1 = 0xFFFFFFF; + vert[3].tu = u0; + vert[3].tv = v1; + + // Fill the vertex index buffer. + index[0] = fb->VertexPos; + index[1] = fb->VertexPos + 1; + index[2] = fb->VertexPos + 2; + index[3] = fb->VertexPos; + index[4] = fb->VertexPos + 2; + index[5] = fb->VertexPos + 3; + + // Batch the quad. + fb->QuadBatchPos++; + fb->VertexPos += 4; + fb->IndexPos += 6; + } + } + } + } + fb->EndQuadBatch(); + return done; +} + +// WIPE: BURN -------------------------------------------------------------- + +//========================================================================== +// +// D3DFB :: Wiper_Burn Constructor +// +//========================================================================== + +D3DFB::Wiper_Burn::Wiper_Burn(D3DFB *fb) +{ + Density = 4; + BurnTime = 0; + memset(BurnArray, 0, sizeof(BurnArray)); + if (fb->Shaders[SHADER_BurnWipe] == NULL || FAILED(fb->D3DDevice->CreateTexture(WIDTH, HEIGHT, 1, + D3DUSAGE_DYNAMIC, D3DFMT_L8, D3DPOOL_DEFAULT, &BurnTexture, NULL))) + { + BurnTexture = NULL; + } +} + +//========================================================================== +// +// D3DFB :: Wiper_Burn Destructor +// +//========================================================================== + +D3DFB::Wiper_Burn::~Wiper_Burn() +{ + SAFE_RELEASE( BurnTexture ); +} + +//========================================================================== +// +// D3DFB :: Wiper_Burn :: Run +// +//========================================================================== + +bool D3DFB::Wiper_Burn::Run(int ticks, D3DFB *fb) +{ + bool done; + + BurnTime += ticks; + ticks *= 2; + + // Make the fire burn + done = false; + while (!done && ticks--) + { + Density = wipe_CalcBurn(BurnArray, WIDTH, HEIGHT, Density); + done = (Density < 0); + } + + // Update the burn texture with the new burn data + D3DLOCKED_RECT lrect; + if (SUCCEEDED(BurnTexture->LockRect(0, &lrect, NULL, D3DLOCK_DISCARD))) + { + const BYTE *src = BurnArray; + BYTE *dest = (BYTE *)lrect.pBits; + for (int y = HEIGHT; y != 0; --y) + { + for (int x = WIDTH; x != 0; --x) + { + *dest++ = *src++; + } + dest += lrect.Pitch - WIDTH; + } + BurnTexture->UnlockRect(0); + } + + // Put the initial screen back to the buffer. + DrawScreen(fb, fb->InitialWipeScreen); + + // Burn the new screen on top of it. + float top = fb->LBOffset - 0.5f; + float right = float(fb->Width) - 0.5f; + float bot = float(fb->Height) + top; + float texright = float(fb->Width) / float(fb->FBWidth); + float texbot = float(fb->Height) / float(fb->FBHeight); + + BURNVERTEX verts[4] = + { + { -0.5f, top, 0.5f, 1.f, 0.f, 0.f, 0, 0 }, + { right, top, 0.5f, 1.f, texright, 0.f, 1, 0 }, + { right, bot, 0.5f, 1.f, texright, texbot, 1, 1 }, + { -0.5f, bot, 0.5f, 1.f, 0.f, texbot, 0, 1 } + }; + + fb->D3DDevice->SetFVF(D3DFVF_BURNVERTEX); + fb->SetTexture(0, fb->FinalWipeScreen); + fb->SetTexture(1, BurnTexture); + fb->SetAlphaBlend(D3DBLENDOP_ADD, D3DBLEND_SRCALPHA, D3DBLEND_INVSRCALPHA); + fb->SetPixelShader(fb->Shaders[SHADER_BurnWipe]); + fb->D3DDevice->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); + if (fb->SM14) + { + fb->D3DDevice->SetSamplerState(1, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP); + fb->D3DDevice->SetSamplerState(1, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP); + } + fb->D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, verts, sizeof(BURNVERTEX)); + fb->D3DDevice->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_POINT); + if (fb->SM14) + { + fb->D3DDevice->SetSamplerState(1, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER); + fb->D3DDevice->SetSamplerState(1, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER); + } + fb->D3DDevice->SetFVF(D3DFVF_FBVERTEX); + + // The fire may not always stabilize, so the wipe is forced to end + // after an arbitrary maximum time. + return done || (BurnTime > 40); +} diff --git a/src/win32/fb_ddraw.cpp b/src/win32/fb_ddraw.cpp new file mode 100644 index 000000000..7cc603786 --- /dev/null +++ b/src/win32/fb_ddraw.cpp @@ -0,0 +1,1336 @@ +/* +** fb_ddraw.cpp +** Code to let ZDoom use DirectDraw 3 +** +**--------------------------------------------------------------------------- +** Copyright 1998-2008 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + + +// HEADER FILES ------------------------------------------------------------ + +#define DIRECTDRAW_VERSION 0x0300 +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include + +#define USE_WINDOWS_DWORD +#include "doomtype.h" + +#include "c_dispatch.h" +#include "templates.h" +#include "i_system.h" +#include "i_video.h" +#include "v_video.h" +#include "v_pfx.h" +#include "stats.h" +#include "doomerrors.h" + +#include "win32iface.h" +#include "v_palette.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +IMPLEMENT_CLASS(DDrawFB) + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +void DoBlending (const PalEntry *from, PalEntry *to, int count, int r, int g, int b, int a); + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +extern HWND Window; +extern IVideo *Video; +extern BOOL AppActive; +extern int SessionState; +extern bool VidResizing; + +EXTERN_CVAR (Bool, fullscreen) +EXTERN_CVAR (Float, Gamma) +EXTERN_CVAR (Int, vid_refreshrate) + +extern IDirectDraw2 *DDraw; + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +CVAR (Bool, vid_palettehack, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (Bool, vid_attachedsurfaces, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (Bool, vid_noblitter, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (Int, vid_displaybits, 8, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CUSTOM_CVAR (Float, rgamma, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (screen != NULL) + { + screen->SetGamma (Gamma); + } +} +CUSTOM_CVAR (Float, ggamma, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (screen != NULL) + { + screen->SetGamma (Gamma); + } +} +CUSTOM_CVAR (Float, bgamma, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (screen != NULL) + { + screen->SetGamma (Gamma); + } +} + +cycle_t BlitCycles; + +// CODE -------------------------------------------------------------------- + +DDrawFB::DDrawFB (int width, int height, bool fullscreen) + : BaseWinFB (width, height) +{ + int i; + + LastHR = 0; + + Palette = NULL; + PrimarySurf = NULL; + BackSurf = NULL; + BackSurf2 = NULL; + BlitSurf = NULL; + Clipper = NULL; + GDIPalette = NULL; + ClipSize = 0; + BufferCount = 1; + Gamma = 1.0; + BufferPitch = Pitch; + FlipFlags = vid_vsync ? DDFLIP_WAIT : DDFLIP_WAIT|DDFLIP_NOVSYNC; + PixelDoubling = 0; + + NeedGammaUpdate = false; + NeedPalUpdate = false; + NeedResRecreate = false; + PaletteChangeExpected = false; + MustBuffer = false; + BufferingNow = false; + WasBuffering = false; + Write8bit = false; + UpdatePending = false; + UseBlitter = false; + + FlashAmount = 0; + + if (MemBuffer == NULL) + { + return; + } + + for (i = 0; i < 256; i++) + { + PalEntries[i].peRed = GPalette.BaseColors[i].r; + PalEntries[i].peGreen = GPalette.BaseColors[i].g; + PalEntries[i].peBlue = GPalette.BaseColors[i].b; + GammaTable[0][i] = GammaTable[1][i] = GammaTable[2][i] = (BYTE)i; + } + memcpy (SourcePalette, GPalette.BaseColors, sizeof(PalEntry)*256); + + MustBuffer = false; + + Windowed = !(static_cast(Video)->GoFullscreen (fullscreen)); + + if (vid_noblitter) + { + LOG ("Blitter forced off\n"); + } + else + { + DDCAPS hwcaps = { sizeof(DDCAPS) }; + HRESULT hr = DDraw->GetCaps (&hwcaps, NULL); + if (SUCCEEDED(hr)) + { + LOG2 ("dwCaps = %08lx, dwSVBCaps = %08lx\n", hwcaps.dwCaps, hwcaps.dwSVBCaps); + if (hwcaps.dwCaps & DDCAPS_BLT) + { + LOG ("Driver supports blits\n"); + if (hwcaps.dwSVBCaps & DDCAPS_CANBLTSYSMEM) + { + LOG ("Driver can blit from system memory\n"); + if (hwcaps.dwCaps & DDCAPS_BLTQUEUE) + { + LOG ("Driver supports asynchronous blits\n"); + UseBlitter = true; + } + else + { + LOG ("Driver does not support asynchronous blits\n"); + } + } + else + { + LOG ("Driver cannot blit from system memory\n"); + } + } + else + { + LOG ("Driver does not support blits\n"); + } + } + } + + if (!CreateResources ()) + { + SAFE_RELEASE( PrimarySurf ); + } +} + +DDrawFB::~DDrawFB () +{ + I_SaveWindowedPos (); + ReleaseResources (); +} + +bool DDrawFB::CreateResources () +{ + DDSURFACEDESC ddsd = { sizeof(ddsd), }; + HRESULT hr; + int bits; + + BufferCount = 1; + + if (!Windowed) + { + // Remove the window border in fullscreen mode + SetWindowLong (Window, GWL_STYLE, WS_POPUP|WS_VISIBLE|WS_SYSMENU); + + TrueHeight = Height; + for (Win32Video::ModeInfo *mode = static_cast(Video)->m_Modes; mode != NULL; mode = mode->next) + { + if (mode->width == Width && mode->height == Height) + { + TrueHeight = mode->realheight; + PixelDoubling = mode->doubling; + break; + } + } + hr = DDraw->SetDisplayMode (Width << PixelDoubling, TrueHeight << PixelDoubling, bits = vid_displaybits, vid_refreshrate, 0); + if (FAILED(hr)) + { + hr = DDraw->SetDisplayMode (Width << PixelDoubling, TrueHeight << PixelDoubling, bits = vid_displaybits, 0, 0); + bits = 32; + while (FAILED(hr) && bits >= 8) + { + hr = DDraw->SetDisplayMode (Width << PixelDoubling, Height << PixelDoubling, bits, vid_refreshrate, 0); + if (FAILED(hr)) + { + hr = DDraw->SetDisplayMode (Width << PixelDoubling, Height << PixelDoubling, bits, 0, 0); + } + bits -= 8; + } + if (FAILED(hr)) + { + LastHR = hr; + return false; + } + } + LOG3 ("Mode set to %d x %d x %d\n", Width, Height, bits); + + if (vid_attachedsurfaces && OSPlatform == os_WinNT4) + { + if (!CreateSurfacesAttached ()) + return false; + } + else + { + if (!CreateSurfacesComplex ()) + return false; + } + + if (UseBlitter) + { + UseBlitter = CreateBlitterSource (); + } + } + else + { + MustBuffer = true; + + LOG ("Running in a window\n"); + TrueHeight = Height; + + // Create the primary surface + ddsd.dwFlags = DDSD_CAPS; + ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; +// PixelDoubling = 1; + do + { + hr = DDraw->CreateSurface (&ddsd, &PrimarySurf, NULL); + LOG1 ("Create primary: %08lx\n", hr); + } while (0); + if (FAILED(hr)) + { + LastHR = hr; + return false; + } + + MaybeCreatePalette (); + + // Resize the window to match desired dimensions + RECT rect = { 0, 0, Width << PixelDoubling, Height << PixelDoubling }; + AdjustWindowRectEx(&rect, WS_VISIBLE|WS_OVERLAPPEDWINDOW, FALSE, WS_EX_APPWINDOW); + int sizew = rect.right - rect.left; + int sizeh = rect.bottom - rect.top; + LOG2 ("Resize window to %dx%d\n", sizew, sizeh); + VidResizing = true; + // Make sure the window has a border in windowed mode + SetWindowLong (Window, GWL_STYLE, WS_VISIBLE|WS_OVERLAPPEDWINDOW); + if (!SetWindowPos (Window, NULL, 0, 0, sizew, sizeh, + SWP_DRAWFRAME | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOZORDER)) + { + LOG1 ("SetWindowPos failed because %08lx\n", GetLastError()); + } + I_RestoreWindowedPos (); + VidResizing = false; + + // Create the clipper + hr = DDraw->CreateClipper (0, &Clipper, NULL); + LOG1 ("Create clipper: %08lx\n", hr); + if (FAILED(hr)) + { + LastHR = hr; + return false; + } + // Associate the clipper with the window + Clipper->SetHWnd (0, Window); + PrimarySurf->SetClipper (Clipper); + LOG1 ("Clipper @ %p set\n", Clipper); + + // Create the backbuffer + ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS; + ddsd.dwWidth = Width << PixelDoubling; + ddsd.dwHeight = Height << PixelDoubling; + ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | (UseBlitter ? DDSCAPS_SYSTEMMEMORY : 0); + + hr = DDraw->CreateSurface (&ddsd, &BackSurf, NULL); + LOG1 ("Create backbuffer: %08lx\n", hr); + if (FAILED(hr)) + { + LastHR = hr; + return false; + } + LockingSurf = BackSurf; + LOG1 ("LockingSurf and BackSurf @ %p\n", BackSurf); + LOG ("Created backbuf\n"); + } + SetGamma (Gamma); + SetFlash (Flash, FlashAmount); + return true; +} + +bool DDrawFB::CreateSurfacesAttached () +{ + DDSURFACEDESC ddsd = { sizeof(ddsd), }; + HRESULT hr; + + LOG ("creating surfaces using AddAttachedSurface\n"); + + ddsd.dwFlags = DDSD_CAPS; + ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_VIDEOMEMORY; + hr = DDraw->CreateSurface (&ddsd, &PrimarySurf, NULL); + if (FAILED(hr)) + { + LastHR = hr; + return false; + } + + LOG1 ("Primary surface @ %p\n", PrimarySurf); + + // Under NT 4 and with bad DDraw drivers under 9x (and maybe Win2k?) + // if the palette is not attached to the primary surface before any + // back buffers are added to it, colors 0 and 255 will remain black + // and white respectively. + MaybeCreatePalette (); + + // Try for triple buffering. Unbuffered output is only allowed if + // we manage to get triple buffering. Even with double buffering, + // framerate can slow significantly compared to triple buffering, + // so we force buffering in that case, which effectively emulates + // triple buffering (after a fashion). + if (!AddBackBuf (&BackSurf, 1) || !AddBackBuf (&BackSurf2, 2)) + { +// MustBuffer = true; + } + if (BackSurf != NULL) + { + DDSCAPS caps = { DDSCAPS_BACKBUFFER, }; + hr = PrimarySurf->GetAttachedSurface (&caps, &LockingSurf); + if (FAILED (hr)) + { + LOG1 ("Could not get attached surface: %08lx\n", hr); + if (BackSurf2 != NULL) + { + PrimarySurf->DeleteAttachedSurface (0, BackSurf2); + BackSurf2->Release (); + BackSurf2 = NULL; + } + PrimarySurf->DeleteAttachedSurface (0, BackSurf); + BackSurf->Release (); + BackSurf = NULL; +// MustBuffer = true; + LockingSurf = PrimarySurf; + } + else + { + BufferCount = (BackSurf2 != NULL) ? 3 : 2; + LOG ("Got attached surface\n"); + } + } + else + { + LOG ("No flip chain\n"); + LockingSurf = PrimarySurf; + } + return true; +} + +bool DDrawFB::AddBackBuf (LPDIRECTDRAWSURFACE *surface, int num) +{ + DDSURFACEDESC ddsd = { sizeof(ddsd), }; + HRESULT hr; + + ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT; + ddsd.dwWidth = Width; + ddsd.dwHeight = Height; + ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY; + hr = DDraw->CreateSurface (&ddsd, surface, NULL); + if (FAILED(hr)) + { + LOG2 ("could not create back buf %d: %08lx\n", num, hr); + return false; + } + else + { + LOG2 ("BackBuf %d created @ %p\n", num, *surface); + hr = PrimarySurf->AddAttachedSurface (*surface); + if (FAILED(hr)) + { + LOG2 ("could not add back buf %d: %08lx\n", num, hr); + (*surface)->Release (); + *surface = NULL; + return false; + } + else + { + LOG1 ("Attachment of back buf %d succeeded\n", num); + } + } + return true; +} + +bool DDrawFB::CreateSurfacesComplex () +{ + DDSURFACEDESC ddsd = { sizeof(ddsd), }; + HRESULT hr; + int tries = 2; + + LOG ("creating surfaces using a complex primary\n"); + + // Try for triple buffering first. + // If that fails, try for double buffering. + // If that fails, settle for single buffering. + // If that fails, then give up. + // + // However, if using the blitter, then do not triple buffer the + // primary surface, because that is effectively like quadruple + // buffering and player response starts feeling too sluggish. + ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT; + ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_VIDEOMEMORY + | DDSCAPS_FLIP | DDSCAPS_COMPLEX; + do + { + LOG1 ("Try #%d\n", tries); + ddsd.dwBackBufferCount = UseBlitter ? 1 : 2; + hr = DDraw->CreateSurface (&ddsd, &PrimarySurf, NULL); + if (FAILED(hr)) + { + if (hr == DDERR_NOEXCLUSIVEMODE) + { + LOG ("Exclusive mode was lost, so restoring it now.\n"); + hr = DDraw->SetCooperativeLevel (Window, DDSCL_ALLOWMODEX | DDSCL_ALLOWREBOOT | DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN); + LOG1 ("SetCooperativeLevel result: %08lx\n", hr); + hr = DDraw->SetDisplayMode (Width, Height, 8, 0, 0); + //hr = DDraw->RestoreDisplayMode (); + LOG1 ("SetDisplayMode result: %08lx\n", hr); + ++tries; + hr = E_FAIL; + continue; + } + + LOG1 ("Could not create with 2 backbuffers: %lx\n", hr); + ddsd.dwBackBufferCount = 1; + hr = DDraw->CreateSurface (&ddsd, &PrimarySurf, NULL); + if (FAILED(hr)) + { + LOG1 ("Could not create with 1 backbuffer: %lx\n", hr); + ddsd.ddsCaps.dwCaps &= ~DDSCAPS_FLIP | DDSCAPS_COMPLEX; + ddsd.dwBackBufferCount = 0; + hr = DDraw->CreateSurface (&ddsd, &PrimarySurf, NULL); + if (FAILED (hr)) + { + LOG1 ("Could not create with 0 backbuffers: %lx\n", hr); + if (tries == 2) + { + LOG ("Retrying without DDSCAPS_VIDEOMEMORY\n"); + ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE + | DDSCAPS_FLIP | DDSCAPS_COMPLEX; + } + } + } + } + } while (FAILED(hr) && --tries); + + if (FAILED(hr)) + { + LastHR = hr; + return false; + } + + LOG1 ("Complex surface chain @ %p\n", PrimarySurf); + if (PrimarySurf == NULL) + { + LOG ("It's NULL but it didn't fail?!?\n"); + LastHR = E_FAIL; + return false; + } + + if (ddsd.dwBackBufferCount == 0) + { + LOG ("No flip chain\n"); +// MustBuffer = true; + LockingSurf = PrimarySurf; + } + else + { + DDSCAPS caps = { DDSCAPS_BACKBUFFER, }; + hr = PrimarySurf->GetAttachedSurface (&caps, &LockingSurf); + if (FAILED (hr)) + { + LOG1 ("Could not get attached surface: %08lx\n", hr); +// MustBuffer = true; + LockingSurf = PrimarySurf; + } + else + { + BufferCount = ddsd.dwBackBufferCount + 1; + LOG1 ("Got attached surface. %d buffers\n", BufferCount); + } + } + + MaybeCreatePalette (); + return true; +} + +bool DDrawFB::CreateBlitterSource () +{ + DDSURFACEDESC ddsd = { sizeof(ddsd), }; + HRESULT hr; + + LOG ("Creating surface for blitter source\n"); + ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_BACKBUFFERCOUNT; + ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY + | DDSCAPS_FLIP | DDSCAPS_COMPLEX; + ddsd.dwBackBufferCount = 2; + ddsd.dwWidth = (Width==1024?1024+16:Width); + ddsd.dwHeight = Height; + hr = DDraw->CreateSurface (&ddsd, &BlitSurf, NULL); + if (FAILED(hr)) + { + LOG1 ("Trying to create blitter source with only one surface (%08lx)\n", hr); + ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT; + ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY; + hr = DDraw->CreateSurface (&ddsd, &BlitSurf, NULL); + if (FAILED(hr)) + { + LOG1 ("Could not create blitter source: %08lx\n", hr); + MustBuffer = true; + return false; + } + BufferCount = MAX (BufferCount, 1); + } + else + { + BufferCount = MAX (BufferCount, 2); + } + LOG1 ("Blitter source created successfully @ %p\n", BlitSurf); + return true; +} + +void DDrawFB::MaybeCreatePalette () +{ + DDPIXELFORMAT fmt = { sizeof(fmt), }; + HRESULT hr; + int i; + + UsePfx = false; + + // If the surface needs a palette, try to create one. If the palette + // cannot be created, the result is ugly but non-fatal. + hr = PrimarySurf->GetPixelFormat (&fmt); + if (SUCCEEDED (hr) && (fmt.dwFlags & DDPF_PALETTEINDEXED8)) + { + LOG ("Surface is paletted\n"); + GPfx.SetFormat (fmt.dwRGBBitCount, + fmt.dwRBitMask, fmt.dwGBitMask, fmt.dwBBitMask); + + if (Windowed) + { + struct { LOGPALETTE head; PALETTEENTRY filler[255]; } pal; + + LOG ("Writing in a window\n"); + Write8bit = true; + pal.head.palVersion = 0x300; + pal.head.palNumEntries = 256; + memcpy (pal.head.palPalEntry, PalEntries, 256*sizeof(PalEntries[0])); + for (i = 0; i < 256; i++) + { + pal.head.palPalEntry[i].peFlags = 0; + } + GDIPalette = ::CreatePalette (&pal.head); + LOG ("Created GDI palette\n"); + if (GDIPalette != NULL) + { + HDC dc = GetDC (Window); + SelectPalette (dc, GDIPalette, FALSE); + RealizePalette (dc); + ReleaseDC (Window, dc); + RebuildColorTable (); + } + } + else + { + hr = DDraw->CreatePalette (DDPCAPS_8BIT|DDPCAPS_ALLOW256, PalEntries, &Palette, NULL); + if (FAILED(hr)) + { + LOG ("Could not create palette\n"); + Palette = NULL; // NULL it just to be safe + } + else + { + hr = PrimarySurf->SetPalette (Palette); + if (FAILED(hr)) + { + LOG ("Could not attach palette to surface\n"); + Palette->Release (); + Palette = NULL; + } + else + { + // The palette was supposed to have been initialized with + // the correct colors, but some drivers don't do that. + // (On the other hand, the docs for the SetPalette method + // don't state that the surface will be set to the + // palette's colors when it gets set, so this might be + // legal behavior. Wish I knew...) + NeedPalUpdate = true; + } + } + if (PixelDoubling) + { + UsePfx = true; + GPfx.SetFormat (-8, 0, 0, 0); + } + } + } + else + { + LOG ("Surface is direct color\n"); + UsePfx = true; + GPfx.SetFormat (fmt.dwRGBBitCount, + fmt.dwRBitMask, fmt.dwGBitMask, fmt.dwBBitMask); + GPfx.SetPalette (GPalette.BaseColors); + } +} + +void DDrawFB::ReleaseResources () +{ + if (LockCount) + { + LockCount = 1; + Unlock (); + } + + SAFE_RELEASE( Clipper ); + SAFE_RELEASE( PrimarySurf ); + SAFE_RELEASE( BackSurf ); + SAFE_RELEASE( BackSurf2 ); + SAFE_RELEASE( BlitSurf ); + SAFE_RELEASE( Palette ); + if (GDIPalette != NULL) + { + HDC dc = GetDC (Window); + SelectPalette (dc, (HPALETTE)GetStockObject (DEFAULT_PALETTE), TRUE); + DeleteObject (GDIPalette); + ReleaseDC (Window, dc); + GDIPalette = NULL; + } + LockingSurf = NULL; +} + +int DDrawFB::GetPageCount () +{ + return MustBuffer ? 1 : BufferCount+1; +} + +void DDrawFB::PaletteChanged () +{ + // Somebody else changed the palette. If we are running fullscreen, + // they are obviously jerks, and we need to restore our own palette. + if (!Windowed) + { + if (!PaletteChangeExpected && Palette != NULL) + { + // It is not enough to set NeedPalUpdate to true. Some palette + // entries might now be reserved for system usage, and nothing + // we do will change them. The only way I have found to fix this + // is to recreate all our surfaces and the palette from scratch. + + // IMPORTANT: Do not recreate the resources here. The screen might + // be locked for a drawing operation. Do it later the next time + // somebody tries to lock it. + NeedResRecreate = true; + } + PaletteChangeExpected = false; + } + else + { + QueryNewPalette (); + } +} + +int DDrawFB::QueryNewPalette () +{ + LOG ("QueryNewPalette\n"); + if (GDIPalette == NULL && Windowed) + { + if (Write8bit) + { + RebuildColorTable (); + } + return 0; + } + + HDC dc = GetDC (Window); + HPALETTE oldPal = SelectPalette (dc, GDIPalette, FALSE); + int i = RealizePalette (dc); + SelectPalette (dc, oldPal, TRUE); + RealizePalette (dc); + ReleaseDC (Window, dc); + if (i != 0) + { + RebuildColorTable (); + } + return i; +} + +void DDrawFB::RebuildColorTable () +{ + int i; + + if (Write8bit) + { + PALETTEENTRY syspal[256]; + HDC dc = GetDC (Window); + + GetSystemPaletteEntries (dc, 0, 256, syspal); + + for (i = 0; i < 256; i++) + { + swapvalues (syspal[i].peRed, syspal[i].peBlue); + } + for (i = 0; i < 256; i++) + { + GPfxPal.Pal8[i] = (BYTE)BestColor ((uint32 *)syspal, PalEntries[i].peRed, + PalEntries[i].peGreen, PalEntries[i].peBlue); + } + } +} + +bool DDrawFB::Is8BitMode() +{ + if (Windowed) + { + return Write8bit; + } + DDPIXELFORMAT fmt = { sizeof(fmt), }; + HRESULT hr; + + hr = PrimarySurf->GetPixelFormat(&fmt); + if (SUCCEEDED(hr)) + { + return !!(fmt.dwFlags & DDPF_PALETTEINDEXED8); + } + // Can't get the primary surface's pixel format, so assume + // vid_displaybits is accurate. + return vid_displaybits == 8; +} + +bool DDrawFB::IsValid () +{ + return PrimarySurf != NULL; +} + +HRESULT DDrawFB::GetHR () +{ + return LastHR; +} + +bool DDrawFB::Lock (bool useSimpleCanvas) +{ + static int lock_num; + bool wasLost; + +// LOG2 (" Lock (%d) <%d>\n", buffered, LockCount); + + LOG3("Lock %5x <%d> %d\n", (AppActive << 16) | (SessionState << 12) | (MustBuffer << 8) | + (useSimpleCanvas << 4) | (int)UseBlitter, LockCount, lock_num++); + + if (LockCount++ > 0) + { + return false; + } + + wasLost = false; + + if (NeedResRecreate && LockCount == 1) + { + LOG("Recreating resources\n"); + NeedResRecreate = false; + ReleaseResources (); + CreateResources (); + // ReleaseResources sets LockCount to 0. + LockCount = 1; + } + + if (!AppActive || SessionState || MustBuffer || useSimpleCanvas || !UseBlitter) + { + Buffer = MemBuffer; + Pitch = BufferPitch; + BufferingNow = true; + } + else + { + HRESULT hr GCCNOWARN = BlitSurf->Flip (NULL, DDFLIP_WAIT); + hr; + LOG1 ("Blit flip = %08lx\n", hr); + LockSurfRes res = LockSurf (NULL, BlitSurf); + + if (res == NoGood) + { // We must have a surface locked before returning, + // but we could not lock the hardware surface, so buffer + // for this frame. + Buffer = MemBuffer; + Pitch = BufferPitch; + BufferingNow = true; + } + else + { + wasLost = (res == GoodWasLost); + } + } + + wasLost = wasLost || (BufferingNow != WasBuffering); + WasBuffering = BufferingNow; + return wasLost; +} + +void DDrawFB::Unlock () +{ + LOG1 ("Unlock <%d>\n", LockCount); + + if (LockCount == 0) + { + LOG("Unlock called when already unlocked\n"); + return; + } + + if (UpdatePending && LockCount == 1) + { + Update (); + } + else if (--LockCount == 0) + { + if (!BufferingNow) + { + if (BlitSurf == NULL) + { + LockingSurf->Unlock (NULL); + } + else + { + BlitSurf->Unlock (NULL); + } + } + Buffer = NULL; + } +} + +DDrawFB::LockSurfRes DDrawFB::LockSurf (LPRECT lockrect, LPDIRECTDRAWSURFACE toLock) +{ + HRESULT hr; + DDSURFACEDESC desc = { sizeof(desc), }; + bool wasLost = false; + bool lockingLocker = false; + + if (toLock == NULL) + { + lockingLocker = true; + if (LockingSurf == NULL) + { + LOG("LockingSurf lost\n"); + if (!CreateResources ()) + { + if (LastHR != DDERR_UNSUPPORTEDMODE) + { + I_FatalError ("Could not rebuild framebuffer: %08lx", LastHR); + } + else + { + LOG ("Display is in unsupported mode right now.\n"); + return NoGood; + } + } + } + toLock = LockingSurf; + } + + hr = toLock->Lock (lockrect, &desc, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); + LOG3 ("LockSurf %p (%d): %08lx\n", toLock, lockingLocker, hr); + + if (hr == DDERR_SURFACELOST) + { + wasLost = true; + if (FAILED (AttemptRestore ())) + { + return NoGood; + } + if (BlitSurf && FAILED(BlitSurf->IsLost ())) + { + LOG ("Restore blitter surface\n"); + hr = BlitSurf->Restore (); + if (FAILED (hr)) + { + LOG1 ("Could not restore blitter surface: %08lx", hr); + BlitSurf->Release (); + if (BlitSurf == toLock) + { + BlitSurf = NULL; + return NoGood; + } + BlitSurf = NULL; + } + } + if (lockingLocker) + { + toLock = LockingSurf; + } + LOG ("Trying to lock again\n"); + hr = toLock->Lock (lockrect, &desc, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); + if (hr == DDERR_SURFACELOST && Windowed) + { // If this is NT, the user probably opened the Windows NT Security dialog. + // If this is not NT, trying to recreate everything from scratch won't hurt. + ReleaseResources (); + if (!CreateResources ()) + { + if (LastHR != DDERR_UNSUPPORTEDMODE) + { + I_FatalError ("Could not rebuild framebuffer: %08lx", LastHR); + } + else + { + LOG ("Display is in unsupported mode right now.\n"); + return NoGood; + } + } + if (lockingLocker) + { + toLock = LockingSurf; + } + hr = toLock->Lock (lockrect, &desc, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); + } + } + if (FAILED (hr)) + { // Still could not restore the surface, so don't draw anything + //I_FatalError ("Could not lock framebuffer: %08lx", hr); + LOG1 ("Final result after restoration attempts: %08lx\n", hr); + return NoGood; + } + Buffer = (BYTE *)desc.lpSurface; + Pitch = desc.lPitch; + BufferingNow = false; + return wasLost ? GoodWasLost : Good; +} + +HRESULT DDrawFB::AttemptRestore () +{ + LOG ("Restore primary\n"); + HRESULT hr = PrimarySurf->Restore (); + if (hr == DDERR_WRONGMODE && Windowed) + { // The user changed the screen mode + LOG ("DDERR_WRONGMODE and windowed, so recreating all resources\n"); + ReleaseResources (); + if (!CreateResources ()) + { + LOG1 ("Could not recreate framebuffer: %08lx", LastHR); + return LastHR; + } + } + else if (FAILED (hr)) + { + LOG1 ("Could not restore primary surface: %08lx", hr); + return hr; + } + if (BackSurf && FAILED(BackSurf->IsLost ())) + { + LOG ("Restore backbuffer\n"); + hr = BackSurf->Restore (); + if (FAILED (hr)) + { + I_FatalError ("Could not restore backbuffer: %08lx", hr); + } + } + if (BackSurf2 && FAILED(BackSurf2->IsLost ())) + { + LOG ("Restore backbuffer 2\n"); + hr = BackSurf2->Restore (); + if (FAILED (hr)) + { + I_FatalError ("Could not restore backbuffer 2: %08lx", hr); + } + } + return 0; +} + +void DDrawFB::Update () +{ + bool pchanged = false; + int i; + + LOG3 ("Update <%d,%c:%d>\n", LockCount, AppActive?'Y':'N', SessionState); + + if (LockCount != 1) + { + //I_FatalError ("Framebuffer must have exactly 1 lock to be updated"); + if (LockCount > 0) + { + UpdatePending = true; + --LockCount; + } + return; + } + + DrawRateStuff (); + + if (NeedGammaUpdate) + { + NeedGammaUpdate = false; + CalcGamma (Windowed || rgamma == 0.f ? Gamma : Gamma * rgamma, GammaTable[0]); + CalcGamma (Windowed || ggamma == 0.f ? Gamma : Gamma * ggamma, GammaTable[1]); + CalcGamma (Windowed || bgamma == 0.f ? Gamma : Gamma * bgamma, GammaTable[2]); + NeedPalUpdate = true; + } + + if (NeedPalUpdate || vid_palettehack) + { + NeedPalUpdate = false; + if (Palette != NULL || GDIPalette != NULL) + { + for (i = 0; i < 256; i++) + { + PalEntries[i].peRed = GammaTable[0][SourcePalette[i].r]; + PalEntries[i].peGreen = GammaTable[1][SourcePalette[i].g]; + PalEntries[i].peBlue = GammaTable[2][SourcePalette[i].b]; + } + if (FlashAmount) + { + DoBlending ((PalEntry *)PalEntries, (PalEntry *)PalEntries, + 256, GammaTable[2][Flash.b], GammaTable[1][Flash.g], GammaTable[0][Flash.r], + FlashAmount); + } + if (Palette != NULL) + { + pchanged = true; + } + else + { + /* Argh! Too slow! + SetPaletteEntries (GDIPalette, 0, 256, PalEntries); + HDC dc = GetDC (Window); + SelectPalette (dc, GDIPalette, FALSE); + RealizePalette (dc); + ReleaseDC (Window, dc); + */ + RebuildColorTable (); + } + } + else + { + for (i = 0; i < 256; i++) + { + ((PalEntry *)PalEntries)[i].r = GammaTable[0][SourcePalette[i].r]; + ((PalEntry *)PalEntries)[i].g = GammaTable[1][SourcePalette[i].g]; + ((PalEntry *)PalEntries)[i].b = GammaTable[2][SourcePalette[i].b]; + } + if (FlashAmount) + { + DoBlending ((PalEntry *)PalEntries, (PalEntry *)PalEntries, + 256, GammaTable[0][Flash.r], GammaTable[1][Flash.g], GammaTable[2][Flash.b], + FlashAmount); + } + GPfx.SetPalette ((PalEntry *)PalEntries); + } + } + + BlitCycles.Reset(); + BlitCycles.Clock(); + + if (BufferingNow) + { + LockCount = 0; + if ((Windowed || AppActive) && !SessionState && !PaintToWindow()) + { + if (LockSurf (NULL, NULL) != NoGood) + { + BYTE *writept = Buffer + (TrueHeight - Height)/2*Pitch; + LOG3 ("Copy %dx%d (%d)\n", Width, Height, BufferPitch); + if (UsePfx) + { + GPfx.Convert (MemBuffer, BufferPitch, + writept, Pitch, Width << PixelDoubling, Height << PixelDoubling, + FRACUNIT >> PixelDoubling, FRACUNIT >> PixelDoubling, 0, 0); + } + else + { + CopyFromBuff (MemBuffer, BufferPitch, Width, Height, writept); + } + if (TrueHeight != Height) + { + // Letterbox time! Draw black top and bottom borders. + int topborder = (TrueHeight - Height) / 2; + int botborder = TrueHeight - topborder - Height; + memset (Buffer, 0, Pitch*topborder); + memset (writept + Height*Pitch, 0, Pitch*botborder); + } + LockingSurf->Unlock (NULL); + } + } + } + else + { + if (BlitSurf != NULL) + { + HRESULT hr; + BlitSurf->Unlock (NULL); + RECT srcRect = { 0, 0, Width, Height }; + hr = LockingSurf->BltFast (0, 0, BlitSurf, &srcRect, DDBLTFAST_NOCOLORKEY|DDBLTFAST_WAIT); + if (FAILED (hr)) + { + LOG1 ("Could not blit: %08lx\n", hr); + if (hr == DDERR_SURFACELOST) + { + if (SUCCEEDED (AttemptRestore ())) + { + hr = LockingSurf->BltFast (0, 0, BlitSurf, &srcRect, DDBLTFAST_NOCOLORKEY|DDBLTFAST_WAIT); + if (FAILED (hr)) + { + LOG1 ("Blit retry also failed: %08lx\n", hr); + } + } + } + } + else + { + LOG ("Blit ok\n"); + } + } + else + { + LockingSurf->Unlock (NULL); + } + } + + BlitCycles.Unclock(); + LOG1 ("cycles = %.1f ms\n", BlitCycles.TimeMS()); + + Buffer = NULL; + LockCount = 0; + UpdatePending = false; + + if (FPSLimitEvent != NULL) + { + WaitForSingleObject(FPSLimitEvent, 1000); + } + if (!Windowed && AppActive && !SessionState /*&& !UseBlitter && !MustBuffer*/) + { + HRESULT hr = PrimarySurf->Flip (NULL, FlipFlags); + LOG1 ("Flip = %08lx\n", hr); + if (hr == DDERR_INVALIDPARAMS) + { + if (FlipFlags & DDFLIP_NOVSYNC) + { + FlipFlags &= ~DDFLIP_NOVSYNC; + Printf ("Can't disable vsync\n"); + PrimarySurf->Flip (NULL, FlipFlags); + } + } + } + + if (pchanged && AppActive && !SessionState) + { + PaletteChangeExpected = true; + Palette->SetEntries (0, 0, 256, PalEntries); + } +} + +bool DDrawFB::PaintToWindow () +{ + if (Windowed && LockCount == 0) + { + HRESULT hr; + RECT rect; + GetClientRect (Window, &rect); + if (rect.right != 0 && rect.bottom != 0) + { + // Use blit to copy/stretch to window's client rect + ClientToScreen (Window, (POINT*)&rect.left); + ClientToScreen (Window, (POINT*)&rect.right); + LOG ("Paint to window\n"); + if (LockSurf (NULL, NULL) != NoGood) + { + GPfx.Convert (MemBuffer, BufferPitch, + Buffer, Pitch, Width << PixelDoubling, Height << PixelDoubling, + FRACUNIT >> PixelDoubling, FRACUNIT >> PixelDoubling, 0, 0); + LockingSurf->Unlock (NULL); + if (FAILED (hr = PrimarySurf->Blt (&rect, BackSurf, NULL, DDBLT_WAIT|DDBLT_ASYNC, NULL))) + { + if (hr == DDERR_SURFACELOST) + { + PrimarySurf->Restore (); + } + PrimarySurf->Blt (&rect, BackSurf, NULL, DDBLT_WAIT, NULL); + } + } + Buffer = NULL; + LOG ("Did paint to window\n"); + } + return true; + } + return false; +} + +PalEntry *DDrawFB::GetPalette () +{ + return SourcePalette; +} + +void DDrawFB::UpdatePalette () +{ + NeedPalUpdate = true; +} + +bool DDrawFB::SetGamma (float gamma) +{ + LOG1 ("SetGamma %g\n", gamma); + Gamma = gamma; + NeedGammaUpdate = true; + return true; +} + +bool DDrawFB::SetFlash (PalEntry rgb, int amount) +{ + Flash = rgb; + FlashAmount = amount; + NeedPalUpdate = true; + return true; +} + +void DDrawFB::GetFlash (PalEntry &rgb, int &amount) +{ + rgb = Flash; + amount = FlashAmount; +} + +// Q: Should I gamma adjust the returned palette? +// A: No. PNG screenshots save the gamma value, so there is no need. +void DDrawFB::GetFlashedPalette (PalEntry pal[256]) +{ + memcpy (pal, SourcePalette, 256*sizeof(PalEntry)); + if (FlashAmount) + { + DoBlending (pal, pal, 256, Flash.r, Flash.g, Flash.b, FlashAmount); + } +} + +void DDrawFB::SetVSync (bool vsync) +{ + LOG1 ("vid_vsync set to %d\n", vsync); + FlipFlags = vsync ? DDFLIP_WAIT : DDFLIP_WAIT|DDFLIP_NOVSYNC; +} + +void DDrawFB::NewRefreshRate() +{ + if (!Windowed) + { + NeedResRecreate = true; + } +} + +void DDrawFB::Blank () +{ + if (IsFullscreen ()) + { + DDBLTFX blitFX = { sizeof(blitFX) }; + + blitFX.dwFillColor = 0; + DDraw->FlipToGDISurface (); + PrimarySurf->Blt (NULL, NULL, NULL, DDBLT_COLORFILL, &blitFX); + } +} + +ADD_STAT (blit) +{ + FString out; + out.Format ("blit=%04.1f ms", BlitCycles.TimeMS()); + return out; +} diff --git a/src/win32/hardware.cpp b/src/win32/hardware.cpp index 4ccee2af9..1554a0396 100644 --- a/src/win32/hardware.cpp +++ b/src/win32/hardware.cpp @@ -131,7 +131,8 @@ void I_InitGraphics () ticker.SetGenericRepDefault (val, CVAR_Bool); //currentrenderer = vid_renderer; - Video = gl_CreateVideo(); + if (currentrenderer==1) Video = gl_CreateVideo(); + else Video = new Win32Video (0); if (Video == NULL) I_FatalError ("Failed to initialize display"); diff --git a/src/win32/win32video.cpp b/src/win32/win32video.cpp index b8b89fd64..f08b6126f 100644 --- a/src/win32/win32video.cpp +++ b/src/win32/win32video.cpp @@ -142,6 +142,604 @@ FILE *dbg; // CODE -------------------------------------------------------------------- +Win32Video::Win32Video (int parm) +: m_Modes (NULL), + m_IsFullscreen (false), + m_Adapter (D3DADAPTER_DEFAULT) +{ + I_SetWndProc(); + if (!InitD3D9()) + { + InitDDraw(); + } +} + +Win32Video::~Win32Video () +{ + FreeModes (); + + if (DDraw != NULL) + { + if (m_IsFullscreen) + { + DDraw->SetCooperativeLevel (NULL, DDSCL_NORMAL); + } + DDraw->Release(); + DDraw = NULL; + } + if (D3D != NULL) + { + D3D->Release(); + D3D = NULL; + } + + STOPLOG; +} + +bool Win32Video::InitD3D9 () +{ + DIRECT3DCREATE9FUNC direct3d_create_9; + + if (vid_forceddraw) + { + return false; + } + + // Load the Direct3D 9 library. + if ((D3D9_dll = LoadLibraryA ("d3d9.dll")) == NULL) + { + return false; + } + + // Obtain an IDirect3D interface. + if ((direct3d_create_9 = (DIRECT3DCREATE9FUNC)GetProcAddress (D3D9_dll, "Direct3DCreate9")) == NULL) + { + goto closelib; + } + if ((D3D = direct3d_create_9 (D3D_SDK_VERSION)) == NULL) + { + goto closelib; + } + + // Select adapter. + m_Adapter = (vid_adapter < 1 || (UINT)vid_adapter > D3D->GetAdapterCount()) + ? D3DADAPTER_DEFAULT : (UINT)vid_adapter - 1u; + + // Check that we have at least PS 1.4 available. + D3DCAPS9 devcaps; + if (FAILED(D3D->GetDeviceCaps (m_Adapter, D3DDEVTYPE_HAL, &devcaps))) + { + goto d3drelease; + } + if ((devcaps.PixelShaderVersion & 0xFFFF) < 0x104) + { + goto d3drelease; + } + if (!(devcaps.Caps2 & D3DCAPS2_DYNAMICTEXTURES)) + { + goto d3drelease; + } + + // Enumerate available display modes. + FreeModes (); + AddD3DModes (m_Adapter, D3DFMT_X8R8G8B8); + AddD3DModes (m_Adapter, D3DFMT_R5G6B5); + if (Args->CheckParm ("-2")) + { // Force all modes to be pixel-doubled. + ScaleModes (1); + } + else if (Args->CheckParm ("-4")) + { // Force all modes to be pixel-quadrupled. + ScaleModes (2); + } + else + { + AddLowResModes (); + } + AddLetterboxModes (); + if (m_Modes == NULL) + { // Too bad. We didn't find any modes for D3D9. We probably won't find any + // for DDraw either... + goto d3drelease; + } + return true; + +d3drelease: + D3D->Release(); + D3D = NULL; +closelib: + FreeLibrary (D3D9_dll); + return false; +} + +void Win32Video::InitDDraw () +{ + DIRECTDRAWCREATEFUNC directdraw_create; + LPDIRECTDRAW ddraw1; + STARTLOG; + + HRESULT dderr; + + // Load the DirectDraw library. + if ((DDraw_dll = LoadLibraryA ("ddraw.dll")) == NULL) + { + I_FatalError ("Could not load ddraw.dll"); + } + + // Obtain an IDirectDraw interface. + if ((directdraw_create = (DIRECTDRAWCREATEFUNC)GetProcAddress (DDraw_dll, "DirectDrawCreate")) == NULL) + { + I_FatalError ("The system file ddraw.dll is missing the DirectDrawCreate export"); + } + + dderr = directdraw_create (NULL, &ddraw1, NULL); + + if (FAILED(dderr)) + I_FatalError ("Could not create DirectDraw object: %08lx", dderr); + + dderr = ddraw1->QueryInterface (IID_IDirectDraw2, (LPVOID*)&DDraw); + if (FAILED(dderr)) + { + ddraw1->Release (); + DDraw = NULL; + I_FatalError ("Could not initialize IDirectDraw2 interface: %08lx", dderr); + } + + // Okay, we have the IDirectDraw2 interface now, so we can release the + // really old-fashioned IDirectDraw one. + ddraw1->Release (); + + DDraw->SetCooperativeLevel (Window, DDSCL_NORMAL); + FreeModes (); + dderr = DDraw->EnumDisplayModes (0, NULL, this, EnumDDModesCB); + if (FAILED(dderr)) + { + DDraw->Release (); + DDraw = NULL; + I_FatalError ("Could not enumerate display modes: %08lx", dderr); + } + if (m_Modes == NULL) + { + DDraw->Release (); + DDraw = NULL; + I_FatalError ("DirectDraw returned no display modes.\n\n" + "If you started " GAMENAME " from a fullscreen DOS box, run it from " + "a DOS window instead. If that does not work, you may need to reboot."); + } + if (Args->CheckParm ("-2")) + { // Force all modes to be pixel-doubled. + ScaleModes(1); + } + else if (Args->CheckParm ("-4")) + { // Force all modes to be pixel-quadrupled. + ScaleModes(2); + } + else + { + if (OSPlatform == os_Win95) + { + // Windows 95 will let us use Mode X. If we didn't find any linear + // modes in the loop above, add the Mode X modes here. + AddMode (320, 200, 8, 200, 0); + AddMode (320, 240, 8, 240, 0); + } + AddLowResModes (); + } + AddLetterboxModes (); +} + +// Returns true if fullscreen, false otherwise +bool Win32Video::GoFullscreen (bool yes) +{ + static const char *const yestypes[2] = { "windowed", "fullscreen" }; + HRESULT hr[2]; + int count; + + // FIXME: Do this right for D3D. (This function is only called by the movie player when using D3D.) + if (D3D != NULL) + { + return yes; + } + + if (m_IsFullscreen == yes) + return yes; + + for (count = 0; count < 2; ++count) + { + LOG1 ("fullscreen: %d\n", yes); + hr[count] = DDraw->SetCooperativeLevel (Window, !yes ? DDSCL_NORMAL : + DDSCL_ALLOWMODEX | DDSCL_ALLOWREBOOT | DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN); + if (SUCCEEDED(hr[count])) + { + if (count != 0) + { +// Ack! Cannot print because the screen does not exist right now. +// Printf ("Setting %s mode failed. Error %08lx\n", +// yestypes[!yes], hr[0]); + } + m_IsFullscreen = yes; + return yes; + } + yes = !yes; + } + + I_FatalError ("Could not set %s mode: %08lx\n" + "Could not set %s mode: %08lx\n", + yestypes[yes], hr[0], yestypes[!yes], hr[1]); + + // Appease the compiler, even though we never return if we get here. + return false; +} + +// Flips to the GDI surface and clears it; used by the movie player +void Win32Video::BlankForGDI () +{ + static_cast (screen)->Blank (); +} + +//========================================================================== +// +// Win32Video :: DumpAdapters +// +// Dumps the list of display adapters to the console. Only meaningful for +// Direct3D. +// +//========================================================================== + +void Win32Video::DumpAdapters() +{ + if (D3D == NULL) + { + Printf("Multi-monitor support requires Direct3D.\n"); + return; + } + + UINT num_adapters = D3D->GetAdapterCount(); + + for (UINT i = 0; i < num_adapters; ++i) + { + D3DADAPTER_IDENTIFIER9 ai; + char moreinfo[64] = ""; + + if (FAILED(D3D->GetAdapterIdentifier(i, 0, &ai))) + { + continue; + } + // Strip trailing whitespace from adapter description. + for (char *p = ai.Description + strlen(ai.Description) - 1; + p >= ai.Description && isspace(*p); + --p) + { + *p = '\0'; + } + HMONITOR hm = D3D->GetAdapterMonitor(i); + MONITORINFOEX mi; + mi.cbSize = sizeof(mi); + + TOptWin32Proc GetMonitorInfo("user32.dll", "GetMonitorInfoW"); + assert(GetMonitorInfo != NULL); // Missing in NT4, but so is D3D + if (GetMonitorInfo.Call(hm, &mi)) + { + mysnprintf(moreinfo, countof(moreinfo), " [%ldx%ld @ (%ld,%ld)]%s", + mi.rcMonitor.right - mi.rcMonitor.left, + mi.rcMonitor.bottom - mi.rcMonitor.top, + mi.rcMonitor.left, mi.rcMonitor.top, + mi.dwFlags & MONITORINFOF_PRIMARY ? " (Primary)" : ""); + } + Printf("%s%u. %s%s\n", + i == m_Adapter ? TEXTCOLOR_BOLD : "", + i + 1, ai.Description, moreinfo); + } +} + +// Mode enumeration -------------------------------------------------------- + +HRESULT WINAPI Win32Video::EnumDDModesCB (LPDDSURFACEDESC desc, void *data) +{ + ((Win32Video *)data)->AddMode (desc->dwWidth, desc->dwHeight, 8, desc->dwHeight, 0); + return DDENUMRET_OK; +} + +void Win32Video::AddD3DModes (UINT adapter, D3DFORMAT format) +{ + UINT modecount, i; + D3DDISPLAYMODE mode; + + modecount = D3D->GetAdapterModeCount (adapter, format); + for (i = 0; i < modecount; ++i) + { + if (D3D_OK == D3D->EnumAdapterModes (adapter, format, i, &mode)) + { + AddMode (mode.Width, mode.Height, 8, mode.Height, 0); + } + } +} + +//========================================================================== +// +// Win32Video :: AddLowResModes +// +// Recent NVidia drivers no longer support resolutions below 640x480, even +// if you try to add them as a custom resolution. With D3DFB, pixel doubling +// is quite easy to do and hardware-accelerated. If you have 1280x800, then +// you can have 320x200, but don't be surprised if it shows up as widescreen +// on a widescreen monitor, since that's what it is. +// +//========================================================================== + +void Win32Video::AddLowResModes() +{ + ModeInfo *mode, *nextmode; + + for (mode = m_Modes; mode != NULL; mode = nextmode) + { + nextmode = mode->next; + if (mode->realheight == mode->height && + mode->doubling == 0 && + mode->height >= 200*2 && + mode->height <= 480*2 && + mode->width >= 320*2 && + mode->width <= 640*2) + { + AddMode (mode->width / 2, mode->height / 2, mode->bits, mode->height / 2, 1); + } + } + for (mode = m_Modes; mode != NULL; mode = nextmode) + { + nextmode = mode->next; + if (mode->realheight == mode->height && + mode->doubling == 0 && + mode->height >= 200*4 && + mode->height <= 480*4 && + mode->width >= 320*4 && + mode->width <= 640*4) + { + AddMode (mode->width / 4, mode->height / 4, mode->bits, mode->height / 4, 2); + } + } +} + +// Add 16:9 and 16:10 resolutions you can use in a window or letterboxed +void Win32Video::AddLetterboxModes () +{ + ModeInfo *mode, *nextmode; + + for (mode = m_Modes; mode != NULL; mode = nextmode) + { + nextmode = mode->next; + if (mode->realheight == mode->height && mode->height * 4/3 == mode->width) + { + if (mode->width >= 360) + { + AddMode (mode->width, mode->width * 9/16, mode->bits, mode->height, mode->doubling); + } + if (mode->width > 640) + { + AddMode (mode->width, mode->width * 10/16, mode->bits, mode->height, mode->doubling); + } + } + } +} + +void Win32Video::AddMode (int x, int y, int bits, int y2, int doubling) +{ + // Reject modes that do not meet certain criteria. + if ((x & 1) != 0 || + y > MAXHEIGHT || + x > MAXWIDTH || + y < 200 || + x < 320) + { + return; + } + + ModeInfo **probep = &m_Modes; + ModeInfo *probe = m_Modes; + + // This mode may have been already added to the list because it is + // enumerated multiple times at different refresh rates. If it's + // not present, add it to the right spot in the list; otherwise, do nothing. + // Modes are sorted first by width, then by height, then by depth. In each + // case the order is ascending. + for (; probe != 0; probep = &probe->next, probe = probe->next) + { + if (probe->width > x) break; + if (probe->width < x) continue; + // Width is equal + if (probe->height > y) break; + if (probe->height < y) continue; + // Height is equal + if (probe->bits > bits) break; + if (probe->bits < bits) continue; + // Bits is equal + return; + } + + *probep = new ModeInfo (x, y, bits, y2, doubling); + (*probep)->next = probe; +} + +void Win32Video::FreeModes () +{ + ModeInfo *mode = m_Modes; + + while (mode) + { + ModeInfo *tempmode = mode; + mode = mode->next; + delete tempmode; + } + m_Modes = NULL; +} + +// For every mode, set its scaling factor. Modes that end up with too +// small a display area are discarded. + +void Win32Video::ScaleModes (int doubling) +{ + ModeInfo *mode, **prev; + + prev = &m_Modes; + mode = m_Modes; + + while (mode != NULL) + { + assert(mode->doubling == 0); + mode->width >>= doubling; + mode->height >>= doubling; + mode->realheight >>= doubling; + mode->doubling = doubling; + if ((mode->width & 7) != 0 || mode->width < 320 || mode->height < 200) + { // Mode became too small. Delete it. + *prev = mode->next; + delete mode; + } + else + { + prev = &mode->next; + } + mode = *prev; + } +} + +void Win32Video::StartModeIterator (int bits, bool fs) +{ + m_IteratorMode = m_Modes; + m_IteratorBits = bits; + m_IteratorFS = fs; +} + +bool Win32Video::NextMode (int *width, int *height, bool *letterbox) +{ + if (m_IteratorMode) + { + while (m_IteratorMode && m_IteratorMode->bits != m_IteratorBits) + { + m_IteratorMode = m_IteratorMode->next; + } + + if (m_IteratorMode) + { + *width = m_IteratorMode->width; + *height = m_IteratorMode->height; + if (letterbox != NULL) *letterbox = m_IteratorMode->realheight != m_IteratorMode->height; + m_IteratorMode = m_IteratorMode->next; + return true; + } + } + return false; +} + +DFrameBuffer *Win32Video::CreateFrameBuffer (int width, int height, bool fullscreen, DFrameBuffer *old) +{ + static int retry = 0; + static int owidth, oheight; + + BaseWinFB *fb; + PalEntry flashColor; + int flashAmount; + + LOG4 ("CreateFB %d %d %d %p\n", width, height, fullscreen, old); + + if (old != NULL) + { // Reuse the old framebuffer if its attributes are the same + BaseWinFB *fb = static_cast (old); + if (fb->Width == width && + fb->Height == height && + fb->Windowed == !fullscreen) + { + return old; + } + old->GetFlash (flashColor, flashAmount); + old->ObjectFlags |= OF_YesReallyDelete; + if (old == screen) screen = NULL; + delete old; + } + else + { + flashColor = 0; + flashAmount = 0; + } + + if (D3D != NULL) + { + fb = new D3DFB (m_Adapter, width, height, fullscreen); + } + else + { + fb = new DDrawFB (width, height, fullscreen); + } + LOG1 ("New fb created @ %p\n", fb); + + // If we could not create the framebuffer, try again with slightly + // different parameters in this order: + // 1. Try with the closest size + // 2. Try in the opposite screen mode with the original size + // 3. Try in the opposite screen mode with the closest size + // This is a somewhat confusing mass of recursion here. + + while (fb == NULL || !fb->IsValid ()) + { + static HRESULT hr; + + if (fb != NULL) + { + if (retry == 0) + { + hr = fb->GetHR (); + } + fb->ObjectFlags |= OF_YesReallyDelete; + delete fb; + + LOG1 ("fb is bad: %08lx\n", hr); + } + else + { + LOG ("Could not create fb at all\n"); + } + screen = NULL; + + LOG1 ("Retry number %d\n", retry); + + switch (retry) + { + case 0: + owidth = width; + oheight = height; + case 2: + // Try a different resolution. Hopefully that will work. + I_ClosestResolution (&width, &height, 8); + LOG2 ("Retry with size %d,%d\n", width, height); + break; + + case 1: + // Try changing fullscreen mode. Maybe that will work. + width = owidth; + height = oheight; + fullscreen = !fullscreen; + LOG1 ("Retry with fullscreen %d\n", fullscreen); + break; + + default: + // I give up! + LOG3 ("Could not create new screen (%d x %d): %08lx", owidth, oheight, hr); + I_FatalError ("Could not create new screen (%d x %d): %08lx", owidth, oheight, hr); + } + + ++retry; + fb = static_cast(CreateFrameBuffer (width, height, fullscreen, NULL)); + } + retry = 0; + + fb->SetFlash (flashColor, flashAmount); + return fb; +} + +void Win32Video::SetWindowedScale (float scale) +{ + // FIXME +} + //========================================================================== // // BaseWinFB :: ScaleCoordsFromWindow