commits
tags
/*
** r_spritedrawer.cpp
**
**---------------------------------------------------------------------------
** Copyright 1998-2016 Randy Heit
** Copyright 2016 Magnus Norddahl
** 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.
**---------------------------------------------------------------------------
**
*/
#include <stddef.h>
#include "r_spritedrawer.h"
#include "swrenderer/r_renderthread.h"
namespace swrenderer
{
SpriteDrawerArgs::SpriteDrawerArgs()
{
colfunc = &SWPixelFormatDrawers::DrawColumn;
}
void SpriteDrawerArgs::DrawMasked(RenderThread* thread, double topZ, double scale, bool flipX, bool flipY, const FWallCoords& WallC, int clipx1, int clipx2, const ProjectedWallLight& light, FSoftwareTexture* tex, const short* mfloorclip, const short* mceilingclip, FRenderStyle style)
{
auto viewport = thread->Viewport.get();
auto cameraLight = CameraLight::Instance();
bool calclighting = cameraLight->FixedLightLevel() < 0 && !cameraLight->FixedColormap() && !light.IsSpriteLight();
float wpos = 1.0f / WallC.sz1;
float wstepX = (1.0f / WallC.sz2 - wpos) / (WallC.sx2 - WallC.sx1);
float upos, ustepX;
if (flipX)
{
upos = (1.0f - WallC.tx1) / WallC.sz1;
ustepX = ((1.0f - WallC.tx2) / WallC.sz2 - upos) / (WallC.sx2 - WallC.sx1);
}
else
{
upos = WallC.tx1 / WallC.sz1;
ustepX = (WallC.tx2 / WallC.sz2 - upos) / (WallC.sx2 - WallC.sx1);
}
float iscale = 1.0f / scale;
if (flipY)
iscale = -iscale;
float vstepY = iscale / WallC.sz1 / (viewport->InvZtoScale / WallC.sz1);
int x1 = max<int>(WallC.sx1, clipx1);
int x2 = min<int>(WallC.sx2, clipx2);
if (x1 >= x2)
return;
float centerY = thread->Viewport->CenterY;
topZ -= thread->Viewport->viewpoint.Pos.Z;
if (flipY)
topZ -= tex->GetHeight() * scale;
int texwidth = tex->GetPhysicalWidth();
int texheight = tex->GetPhysicalHeight();
float lightpos = light.GetLightPos(x1);
float lightstep = light.GetLightStep();
if (x1 > WallC.sx1)
{
int dx = x1 - WallC.sx1;
upos += ustepX * dx;
wpos += wstepX * dx;
lightpos += lightstep * dx;
}
dc_viewport = viewport;
dc_textureheight = texheight;
bool bgra = viewport->RenderTarget->IsBgra() && !drawer_needs_pal_input;
for (int x = x1; x < x2; x++)
{
if (calclighting)
{
SetLight(lightpos, light.GetLightLevel(), light.GetFoggy(), thread->Viewport.get());
}
float w = 1.0f / wpos;
float y1 = centerY - topZ * wpos * viewport->InvZtoScale;
float u = upos * w;
float scaleU = ustepX * w;
float scaleV = vstepY * w;
uint32_t texelX = (uint32_t)(int64_t)((u - std::floor(u)) * 0x1'0000'0000LL);
uint32_t texelStepX = (uint32_t)(int64_t)(scaleU * 0x1'0000'0000LL);
uint32_t texelStepY = (uint32_t)(int64_t)(scaleV * 0x1'0000'0000LL);
DrawMaskedColumn(thread, x, y1, mceilingclip[x], mfloorclip[x], texelX, texelStepX, texelStepY, scaleV, flipY, tex, texwidth, texheight, bgra, style);
upos += ustepX;
wpos += wstepX;
lightpos += lightstep;
}
}
void SpriteDrawerArgs::DrawMasked2D(RenderThread* thread, double x0, double x1, double y0, double y1, FSoftwareTexture* tex, FRenderStyle style)
{
int sx0 = max((int)x0, 0);
int sx1 = min((int)x1, viewwidth);
int sy0 = max((int)y0, 0);
int sy1 = min((int)y1, viewheight);
if (sx0 >= sx1 || sy0 >= sy1)
return;
float ustepX = 1.0f / (x1 - x0);
float vstepY = 1.0f / (y1 - y0);
float upos = ustepX * (sx0 + 0.5f - x0);
uint32_t texelStepX = (uint32_t)(int64_t)(ustepX * 0x1'0000'0000LL);
uint32_t texelStepY = (uint32_t)(int64_t)(vstepY * 0x1'0000'0000LL);
bool bgra = thread->Viewport->RenderTarget->IsBgra() && !drawer_needs_pal_input;
int texwidth = tex->GetPhysicalWidth();
int texheight = tex->GetPhysicalHeight();
dc_viewport = thread->Viewport.get();
dc_textureheight = texheight;
vstepY *= texheight;
for (int x = sx0; x < sx1; x++)
{
float u = upos;
uint32_t texelX = (uint32_t)(int64_t)((u - std::floor(u)) * 0x1'0000'0000LL);
DrawMaskedColumn(thread, x, sy0, sy0, sy1, texelX, texelStepX, texelStepY, vstepY, false, tex, texwidth, texheight, bgra, style);
upos += ustepX;
}
}
void SpriteDrawerArgs::DrawMaskedColumn(RenderThread* thread, int x, float y1, int cliptop, int clipbottom, uint32_t texelX, uint32_t texelStepX, uint32_t texelStepY, float scaleV, bool flipY, FSoftwareTexture* tex, int texwidth, int texheight, bool bgra, FRenderStyle style)
{
const FSoftwareTextureSpan* span;
if (bgra)
{
double xmagnitude = fabs(static_cast<int32_t>(texelStepX)* (1.0 / 0x1'0000'0000LL));
double ymagnitude = fabs(static_cast<int32_t>(texelStepY)* (1.0 / 0x1'0000'0000LL));
double magnitude = max(ymagnitude, xmagnitude);
double min_lod = -1000.0;
double lod = max(log2(magnitude) + r_lod_bias, min_lod);
bool magnifying = lod < 0.0f;
int mipmap_offset = 0;
int mip_width = texwidth;
int mip_height = texheight;
if (r_mipmap && tex->Mipmapped() && mip_width > 1 && mip_height > 1)
{
int level = (int)lod;
while (level > 0 && mip_width > 1 && mip_height > 1)
{
mipmap_offset += mip_width * mip_height;
level--;
mip_width = max(mip_width >> 1, 1);
mip_height = max(mip_height >> 1, 1);
}
}
const uint32_t* pixels = tex->GetPixelsBgra() + mipmap_offset;
fixed_t xoffset = (texelX >> 16)* mip_width;
bool filter_nearest = (magnifying && !r_magfilter) || (!magnifying && !r_minfilter);
if (filter_nearest)
{
xoffset = max(min(xoffset, (mip_width << FRACBITS) - 1), 0);
int tx = xoffset >> FRACBITS;
dc_source = (uint8_t*)(pixels + tx * mip_height);
dc_source2 = nullptr;
dc_textureheight = mip_height;
dc_texturefracx = 0;
}
else
{
xoffset = max(min(xoffset - (FRACUNIT / 2), (mip_width << FRACBITS) - 1), 0);
int tx0 = xoffset >> FRACBITS;
int tx1 = min(tx0 + 1, mip_width - 1);
dc_source = (uint8_t*)(pixels + tx0 * mip_height);
dc_source2 = (uint8_t*)(pixels + tx1 * mip_height);
dc_textureheight = mip_height;
dc_texturefracx = (xoffset >> (FRACBITS - 4)) & 15;
}
int col = ((texelX >> 16)* texwidth) >> 16;
tex->GetColumnBgra(col, &span);
dc_iscale = (uint32_t)(int64_t)(scaleV / texheight * (1 << 30));
dc_x = x;
while (span->Length != 0)
{
const int length = span->Length;
const int top = span->TopOffset;
// calculate unclipped screen coordinates for post
dc_yl = xs_RoundToInt(y1 + top / scaleV);
dc_yh = xs_RoundToInt(y1 + (top + length) / scaleV);
if (flipY)
std::swap(dc_yl, dc_yh);
dc_yl = max(dc_yl, cliptop);
dc_yh = min(dc_yh, clipbottom);
if (dc_yl <= dc_yh)
{
double v = ((dc_yl + 0.5f - y1) * scaleV) / texheight;
dc_texturefrac = (uint32_t)(v * (1 << 30));
SetDest(dc_viewport, dc_x, dc_yl);
dc_count = dc_yh - dc_yl;
dc_yl--;
(thread->Drawers(dc_viewport)->*colfunc)(*this);
}
span++;
}
}
else
{
int col = ((texelX >> 16)* texwidth) >> 16;
dc_source = tex->GetColumn(style, col, &span);
dc_source2 = nullptr;
dc_iscale = FLOAT2FIXED(scaleV);
dc_x = x;
while (span->Length != 0)
{
const int length = span->Length;
const int top = span->TopOffset;
// calculate unclipped screen coordinates for post
dc_yl = xs_RoundToInt(y1 + top / scaleV);
dc_yh = xs_RoundToInt(y1 + (top + length) / scaleV);
if (flipY)
std::swap(dc_yl, dc_yh);
dc_yl = max(dc_yl, cliptop);
dc_yh = min(dc_yh, clipbottom);
if (dc_yl < dc_yh)
{
dc_texturefrac = FLOAT2FIXED((dc_yl + 0.5f - y1) * scaleV);
SetDest(thread->Viewport.get(), dc_x, dc_yl);
dc_count = dc_yh - dc_yl;
dc_yl--;
fixed_t maxfrac = ((top + length) << FRACBITS) - 1;
dc_texturefrac = max(dc_texturefrac, 0);
dc_texturefrac = min(dc_texturefrac, maxfrac);
if (dc_iscale > 0)
dc_count = min(dc_count, (maxfrac - dc_texturefrac + dc_iscale - 1) / dc_iscale);
else if (dc_iscale < 0)
dc_count = min(dc_count, (dc_texturefrac - dc_iscale) / (-dc_iscale));
(thread->Drawers(dc_viewport)->*colfunc)(*this);
}
span++;
}
}
}
bool SpriteDrawerArgs::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 = &SWPixelFormatDrawers::FillColumn;
}
else if (TranslationMap() == nullptr)
{
colfunc = &SWPixelFormatDrawers::DrawColumn;
}
else
{
colfunc = &SWPixelFormatDrawers::DrawTranslatedColumn;
drawer_needs_pal_input = true;
}
return true;
}
if (flags & STYLEF_InvertSource)
{
dc_srcblend = Col2RGB8_Inverse[fglevel >> 10];
dc_destblend = Col2RGB8_LessPrecision[bglevel >> 10];
dc_srcalpha = fglevel;
dc_destalpha = bglevel;
}
else if (op == STYLEOP_Add && fglevel + bglevel <= FRACUNIT)
{
dc_srcblend = Col2RGB8[fglevel >> 10];
dc_destblend = Col2RGB8[bglevel >> 10];
dc_srcalpha = fglevel;
dc_destalpha = bglevel;
}
else
{
dc_srcblend = Col2RGB8_LessPrecision[fglevel >> 10];
dc_destblend = Col2RGB8_LessPrecision[bglevel >> 10];
dc_srcalpha = fglevel;
dc_destalpha = bglevel;
}
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 = &SWPixelFormatDrawers::FillAddColumn;
}
else if (TranslationMap() == nullptr)
{
colfunc = &SWPixelFormatDrawers::DrawAddColumn;
}
else
{
colfunc = &SWPixelFormatDrawers::DrawTranslatedAddColumn;
drawer_needs_pal_input = true;
}
}
else
{ // Colors might overflow when added
if (flags & STYLEF_ColorIsFixed)
{
colfunc = &SWPixelFormatDrawers::FillAddClampColumn;
}
else if (TranslationMap() == nullptr)
{
colfunc = &SWPixelFormatDrawers::DrawAddClampColumn;
}
else
{
colfunc = &SWPixelFormatDrawers::DrawAddClampTranslatedColumn;
drawer_needs_pal_input = true;
}
}
return true;
case STYLEOP_Sub:
if (flags & STYLEF_ColorIsFixed)
{
colfunc = &SWPixelFormatDrawers::FillSubClampColumn;
}
else if (TranslationMap() == nullptr)
{
colfunc = &SWPixelFormatDrawers::DrawSubClampColumn;
}
else
{
colfunc = &SWPixelFormatDrawers::DrawSubClampTranslatedColumn;
drawer_needs_pal_input = true;
}
return true;
case STYLEOP_RevSub:
if (fglevel == 0 && bglevel == FRACUNIT)
{
return false;
}
if (flags & STYLEF_ColorIsFixed)
{
colfunc = &SWPixelFormatDrawers::FillRevSubClampColumn;
}
else if (TranslationMap() == nullptr)
{
colfunc = &SWPixelFormatDrawers::DrawRevSubClampColumn;
}
else
{
colfunc = &SWPixelFormatDrawers::DrawRevSubClampTranslatedColumn;
drawer_needs_pal_input = true;
}
return true;
default:
return false;
}
}
fixed_t SpriteDrawerArgs::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;
}
}
bool SpriteDrawerArgs::SetStyle(RenderViewport *viewport, FRenderStyle style, fixed_t alpha, FTranslationID translation, uint32_t color, const ColormapLight &light)
{
if (light.BaseColormap)
SetLight(light);
FDynamicColormap *basecolormap = light.BaseColormap ? static_cast<FDynamicColormap*>(light.BaseColormap) : nullptr;
fixed_t shadedlightshade = light.ColormapNum << FRACBITS;
fixed_t fglevel, bglevel;
drawer_needs_pal_input = false;
style.CheckFuzz();
if (style.BlendOp == STYLEOP_Shadow)
{
style = LegacyRenderStyles[STYLE_TranslucentStencil];
alpha = OPAQUE / 3;
color = 0;
}
if (style.Flags & STYLEF_TransSoulsAlpha)
{
alpha = fixed_t(transsouls * OPAQUE);
}
else if (style.Flags & STYLEF_Alpha1)
{
alpha = OPAQUE;
}
else
{
alpha = clamp<fixed_t>(alpha, 0, OPAQUE);
}
SetTranslationMap(nullptr);
if (translation != NO_TRANSLATION)
{
FRemapTable *table = GPalette.TranslationToTable(translation);
if (table != NULL)
{
if (viewport->RenderTarget->IsBgra())
SetTranslationMap((uint8_t*)table->Palette);
else
SetTranslationMap(table->Remap);
}
}
// Check for special modes
if (style.BlendOp == STYLEOP_Fuzz)
{
colfunc = &SWPixelFormatDrawers::DrawFuzzColumn;
return true;
}
else if (style == LegacyRenderStyles[STYLE_Shaded] || style == LegacyRenderStyles[STYLE_AddShaded])
{
// Shaded drawer only gets 16 levels of alpha because it saves memory.
if ((alpha >>= 12) == 0 || basecolormap == nullptr)
return false;
if (style == LegacyRenderStyles[STYLE_Shaded])
colfunc = &SWPixelFormatDrawers::DrawShadedColumn;
else
colfunc = &SWPixelFormatDrawers::DrawAddClampShadedColumn;
drawer_needs_pal_input = true;
CameraLight *cameraLight = CameraLight::Instance();
dc_color = cameraLight->FixedColormap() ? cameraLight->FixedColormap()->Maps[APART(color)] : basecolormap->Maps[APART(color)];
dc_color_bgra = color;
basecolormap = &ShadeFakeColormap[16 - alpha];
if (cameraLight->FixedLightLevel() >= 0 && !cameraLight->FixedColormap())
{
fixed_t shade = shadedlightshade;
if (shade == 0) shade = cameraLight->FixedLightLevelShade();
SetBaseColormap(basecolormap);
SetLight(0, shade);
}
else
{
SetBaseColormap(basecolormap);
SetLight(0, shadedlightshade);
}
return true;
}
fglevel = GetAlpha(style.SrcAlpha, alpha);
bglevel = GetAlpha(style.DestAlpha, alpha);
if (style.Flags & STYLEF_ColorIsFixed)
{
uint32_t x = fglevel >> 10;
uint32_t r = RPART(color);
uint32_t g = GPART(color);
uint32_t b = BPART(color);
// dc_color is used by the rt_* routines. It is indexed into dc_srcblend.
dc_color = RGB256k.RGB[r >> 2][g >> 2][b >> 2];
dc_color_bgra = color;
if (style.Flags & STYLEF_InvertSource)
{
r = 255 - r;
g = 255 - g;
b = 255 - b;
}
uint32_t alpha = clamp(fglevel >> (FRACBITS - 8), 0, 255);
dc_srccolor_bgra = (alpha << 24) | (r << 16) | (g << 8) | 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;
SetBaseColormap(&identitycolormap);
SetLight(0, 0);
}
return SpriteDrawerArgs::SetBlendFunc(style.BlendOp, fglevel, bglevel, style.Flags);
}
bool SpriteDrawerArgs::SetStyle(RenderViewport *viewport, FRenderStyle style, float alpha, FTranslationID translation, uint32_t color, const ColormapLight &light)
{
return SetStyle(viewport, style, FLOAT2FIXED(alpha), translation, color, light);
}
void SpriteDrawerArgs::DrawVoxelBlocks(RenderThread *thread, const VoxelBlock *blocks, int blockcount)
{
SetDest(thread->Viewport.get(), 0, 0);
thread->Drawers(dc_viewport)->DrawVoxelBlocks(*this, blocks, blockcount);
}
void SpriteDrawerArgs::SetDest(RenderViewport *viewport, int x, int y)
{
dc_dest = viewport->GetDest(x, y);
dc_dest_y = y;
dc_viewport = viewport;
}
}