Files
SpaceCadetPinball/SpaceCadetPinball/render.cpp
T
toxie 57af3af800 Increase precision of mode_countdown_ handling (#52)
* fix harmless warnings and properly try/catch allocations via new

otherwise the error handling will never be triggered

* increase precision of mode_countdown_ handling

potentially there could be modes running a bit too long, depending on passed in ms (which were implicitly truncated before when passing in)

also fix some harmless warnings

* document warnings that i cannot handle on my own

* revert changes to have a new cleaner PR after review/cherry picks

* increase precision of mode_countdown_ handling

potentially there could be modes running a bit too long, depending on passed in ms (which were implicitly truncated before when passing in)

also fix some harmless warnings and add comments where original code is 'correct' but weird
2021-10-23 07:33:04 +03:00

605 lines
14 KiB
C++

#include "pch.h"
#include "render.h"
#include "GroupData.h"
#include "options.h"
#include "pb.h"
#include "score.h"
#include "TPinballTable.h"
#include "winmain.h"
std::vector<render_sprite_type_struct*> render::dirty_list, render::sprite_list, render::ball_list;
zmap_header_type* render::background_zmap;
int render::zmap_offset, render::zmap_offsetY, render::offset_x, render::offset_y;
float render::zscaler, render::zmin, render::zmax;
rectangle_type render::vscreen_rect;
gdrv_bitmap8 *render::vscreen, *render::background_bitmap, *render::ball_bitmap[20];
zmap_header_type* render::zscreen;
SDL_Texture* render::vScreenTex = nullptr;
SDL_Rect render::DestinationRect{};
void render::init(gdrv_bitmap8* bmp, float zMin, float zScaler, int width, int height)
{
zscaler = zScaler;
zmin = zMin;
zmax = 4294967300.0f / zScaler + zMin;
vscreen = new gdrv_bitmap8(width, height, false);
zscreen = new zmap_header_type(width, height, width);
zdrv::fill(zscreen, zscreen->Width, zscreen->Height, 0, 0, 0xFFFF);
vscreen_rect.YPosition = 0;
vscreen_rect.XPosition = 0;
vscreen_rect.Width = width;
vscreen_rect.Height = height;
vscreen->YPosition = 0;
vscreen->XPosition = 0;
for (auto& ballBmp : ball_bitmap)
ballBmp = new gdrv_bitmap8(64, 64, false);
background_bitmap = bmp;
if (bmp)
gdrv::copy_bitmap(vscreen, width, height, 0, 0, bmp, 0, 0);
else
gdrv::fill_bitmap(vscreen, vscreen->Width, vscreen->Height, 0, 0, 0);
{
UsingSdlHint hint{SDL_HINT_RENDER_SCALE_QUALITY, options::Options.LinearFiltering ? "linear" : "nearest"};
vScreenTex = SDL_CreateTexture
(
winmain::Renderer,
SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING,
width, height
);
SDL_SetTextureBlendMode(vScreenTex, SDL_BLENDMODE_NONE);
}
}
void render::uninit()
{
delete vscreen;
delete zscreen;
for (auto sprite : sprite_list)
remove_sprite(sprite, false);
for (auto ball : ball_list)
remove_ball(ball, false);
for (auto& ballBmp : ball_bitmap)
delete ballBmp;
ball_list.clear();
dirty_list.clear();
sprite_list.clear();
SDL_DestroyTexture(vScreenTex);
}
void render::update()
{
unpaint_balls();
// Clip dirty sprites with vScreen, clear clipping (dirty) rectangles
for (auto curSprite : dirty_list)
{
bool clearSprite = false;
switch (curSprite->VisualType)
{
case VisualTypes::Sprite:
if (curSprite->DirtyRectPrev.Width > 0)
maths::enclosing_box(&curSprite->DirtyRectPrev, &curSprite->BmpRect, &curSprite->DirtyRect);
if (maths::rectangle_clip(&curSprite->DirtyRect, &vscreen_rect, &curSprite->DirtyRect))
clearSprite = true;
else
curSprite->DirtyRect.Width = -1;
break;
case VisualTypes::None:
if (maths::rectangle_clip(&curSprite->BmpRect, &vscreen_rect, &curSprite->DirtyRect))
clearSprite = !curSprite->Bmp;
else
curSprite->DirtyRect.Width = -1;
break;
default: break;
}
if (clearSprite)
{
auto yPos = curSprite->DirtyRect.YPosition;
auto width = curSprite->DirtyRect.Width;
auto xPos = curSprite->DirtyRect.XPosition;
auto height = curSprite->DirtyRect.Height;
zdrv::fill(zscreen, width, height, xPos, yPos, 0xFFFF);
if (background_bitmap)
gdrv::copy_bitmap(vscreen, width, height, xPos, yPos, background_bitmap, xPos, yPos);
else
gdrv::fill_bitmap(vscreen, width, height, xPos, yPos, 0);
}
}
// Paint dirty rectangles of dirty sprites
for (auto sprite : dirty_list)
{
if (sprite->DirtyRect.Width > 0 && (sprite->VisualType == VisualTypes::None || sprite->VisualType ==
VisualTypes::Sprite))
repaint(sprite);
}
paint_balls();
// In the original, this used to blit dirty sprites and balls
for (auto sprite : dirty_list)
{
sprite->DirtyRectPrev = sprite->DirtyRect;
if (sprite->UnknownFlag != 0)
remove_sprite(sprite, true);
}
dirty_list.clear();
}
void render::sprite_modified(render_sprite_type_struct* sprite)
{
if (sprite->VisualType != VisualTypes::Ball && dirty_list.size() < 999)
dirty_list.push_back(sprite);
}
render_sprite_type_struct* render::create_sprite(VisualTypes visualType, gdrv_bitmap8* bmp, zmap_header_type* zMap,
int xPosition, int yPosition, rectangle_type* rect)
{
auto sprite = new render_sprite_type_struct();
if (!sprite)
return nullptr;
sprite->BmpRect.YPosition = yPosition;
sprite->BmpRect.XPosition = xPosition;
sprite->Bmp = bmp;
sprite->VisualType = visualType;
sprite->UnknownFlag = 0;
sprite->SpriteArray = nullptr;
sprite->DirtyRect = rectangle_type{};
if (rect)
{
sprite->BoundingRect = *rect;
}
else
{
sprite->BoundingRect.Width = -1;
sprite->BoundingRect.Height = -1;
sprite->BoundingRect.XPosition = 0;
sprite->BoundingRect.YPosition = 0;
}
if (bmp)
{
sprite->BmpRect.Width = bmp->Width;
sprite->BmpRect.Height = bmp->Height;
}
else
{
sprite->BmpRect.Width = 0;
sprite->BmpRect.Height = 0;
}
sprite->ZMap = zMap;
sprite->ZMapOffestX = 0;
sprite->ZMapOffestY = 0;
if (!zMap && visualType != VisualTypes::Ball)
{
sprite->ZMap = background_zmap;
sprite->ZMapOffestY = xPosition - zmap_offset;
sprite->ZMapOffestX = yPosition - zmap_offsetY;
}
sprite->DirtyRectPrev = sprite->BmpRect;
if (visualType == VisualTypes::Ball)
{
ball_list.push_back(sprite);
}
else
{
sprite_list.push_back(sprite);
sprite_modified(sprite);
}
return sprite;
}
void render::remove_sprite(render_sprite_type_struct* sprite, bool removeFromList)
{
if (removeFromList)
{
auto it = std::find(sprite_list.begin(), sprite_list.end(), sprite);
if (it != sprite_list.end())
sprite_list.erase(it);
}
delete sprite->SpriteArray;
delete sprite;
}
void render::remove_ball(render_sprite_type_struct* ball, bool removeFromList)
{
if (removeFromList)
{
auto it = std::find(ball_list.begin(), ball_list.end(), ball);
if (it != ball_list.end())
ball_list.erase(it);
}
delete ball->SpriteArray;
delete ball;
}
void render::sprite_set(render_sprite_type_struct* sprite, gdrv_bitmap8* bmp, zmap_header_type* zMap, int xPos,
int yPos)
{
if (sprite)
{
sprite->BmpRect.XPosition = xPos;
sprite->BmpRect.YPosition = yPos;
sprite->Bmp = bmp;
if (bmp)
{
sprite->BmpRect.Width = bmp->Width;
sprite->BmpRect.Height = bmp->Height;
}
sprite->ZMap = zMap;
sprite_modified(sprite);
}
}
void render::sprite_set_bitmap(render_sprite_type_struct* sprite, gdrv_bitmap8* bmp)
{
if (sprite && sprite->Bmp != bmp)
{
sprite->Bmp = bmp;
if (bmp)
{
sprite->BmpRect.Width = bmp->Width;
sprite->BmpRect.Height = bmp->Height;
}
sprite_modified(sprite);
}
}
void render::set_background_zmap(struct zmap_header_type* zMap, int offsetX, int offsetY)
{
background_zmap = zMap;
zmap_offset = offsetX;
zmap_offsetY = offsetY;
}
void render::ball_set(render_sprite_type_struct* sprite, gdrv_bitmap8* bmp, float depth, int xPos, int yPos)
{
if (sprite)
{
sprite->Bmp = bmp;
if (bmp)
{
sprite->BmpRect.XPosition = xPos;
sprite->BmpRect.YPosition = yPos;
sprite->BmpRect.Width = bmp->Width;
sprite->BmpRect.Height = bmp->Height;
}
if (depth >= zmin)
{
float depth2 = (depth - zmin) * zscaler;
if (depth2 <= zmax)
sprite->Depth = static_cast<short>(depth2);
else
sprite->Depth = -1;
}
else
{
sprite->Depth = 0;
}
}
}
void render::repaint(struct render_sprite_type_struct* sprite)
{
rectangle_type clipRect{};
if (!sprite->SpriteArray)
return;
for (auto refSprite : *sprite->SpriteArray)
{
if (!refSprite->UnknownFlag && refSprite->Bmp)
{
if (maths::rectangle_clip(&refSprite->BmpRect, &sprite->DirtyRect, &clipRect))
zdrv::paint(
clipRect.Width,
clipRect.Height,
vscreen,
clipRect.XPosition,
clipRect.YPosition,
zscreen,
clipRect.XPosition,
clipRect.YPosition,
refSprite->Bmp,
clipRect.XPosition - refSprite->BmpRect.XPosition,
clipRect.YPosition - refSprite->BmpRect.YPosition,
refSprite->ZMap,
clipRect.XPosition + refSprite->ZMapOffestY - refSprite->BmpRect.XPosition,
clipRect.YPosition + refSprite->ZMapOffestX - refSprite->BmpRect.YPosition);
}
}
}
void render::paint_balls()
{
// Sort ball sprites by depth
for (auto i = 0u; i < ball_list.size(); i++)
{
for (auto j = i; j < ball_list.size() / 2; ++j)
{
auto ballA = ball_list[j];
auto ballB = ball_list[i];
if (ballB->Depth > ballA->Depth)
{
ball_list[i] = ballA;
ball_list[j] = ballB;
}
}
}
// For balls that clip vScreen: save original vScreen contents and paint ball bitmap.
for (auto index = 0u; index < ball_list.size(); ++index)
{
auto ball = ball_list[index];
auto dirty = &ball->DirtyRect;
if (ball->Bmp && maths::rectangle_clip(&ball->BmpRect, &vscreen_rect, &ball->DirtyRect))
{
int xPos = dirty->XPosition;
int yPos = dirty->YPosition;
gdrv::copy_bitmap(ball_bitmap[index], dirty->Width, dirty->Height, 0, 0, vscreen, xPos, yPos);
zdrv::paint_flat(
dirty->Width,
dirty->Height,
vscreen,
xPos,
yPos,
zscreen,
xPos,
yPos,
ball->Bmp,
xPos - ball->BmpRect.XPosition,
yPos - ball->BmpRect.YPosition,
ball->Depth);
}
else
{
dirty->Width = -1;
}
}
}
void render::unpaint_balls()
{
// Restore portions of vScreen saved during previous paint_balls call.
for (int index = static_cast<int>(ball_list.size()) - 1; index >= 0; index--)
{
auto curBall = ball_list[index];
if (curBall->DirtyRect.Width > 0)
gdrv::copy_bitmap(
vscreen,
curBall->DirtyRect.Width,
curBall->DirtyRect.Height,
curBall->DirtyRect.XPosition,
curBall->DirtyRect.YPosition,
ball_bitmap[index],
0,
0);
curBall->DirtyRectPrev = curBall->DirtyRect;
}
}
void render::shift(int offsetX, int offsetY)
{
offset_x += offsetX;
offset_y += offsetY;
}
void render::build_occlude_list()
{
std::vector<render_sprite_type_struct*>* spriteArr = nullptr;
for (auto mainSprite : sprite_list)
{
if (mainSprite->SpriteArray)
{
delete mainSprite->SpriteArray;
mainSprite->SpriteArray = nullptr;
}
if (!mainSprite->UnknownFlag && mainSprite->BoundingRect.Width != -1)
{
if (!spriteArr)
spriteArr = new std::vector<render_sprite_type_struct*>();
for (auto refSprite : sprite_list)
{
if (!refSprite->UnknownFlag
&& refSprite->BoundingRect.Width != -1
&& maths::rectangle_clip(&mainSprite->BoundingRect, &refSprite->BoundingRect, nullptr)
&& spriteArr)
{
spriteArr->push_back(refSprite);
}
}
if (mainSprite->Bmp && spriteArr->size() < 2)
spriteArr->clear();
if (!spriteArr->empty())
{
mainSprite->SpriteArray = spriteArr;
spriteArr = nullptr;
}
}
}
delete spriteArr;
}
void render::SpriteViewer(bool* show)
{
static const char* BitmapTypes[] =
{
"None",
"RawBitmap",
"DibBitmap",
"Spliced",
};
static float scale = 1.0f;
auto uv_min = ImVec2(0.0f, 0.0f); // Top-left
auto uv_max = ImVec2(1.0f, 1.0f); // Lower-right
auto tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // No tint
auto border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); // 50% opaque white
if (ImGui::Begin("Sprite viewer", show, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_MenuBar))
{
if (ImGui::BeginMenuBar())
{
ImGui::SliderFloat("Sprite scale", &scale, 0.1f, 10.0f, "scale = %.3f");
ImGui::EndMenuBar();
}
for (const auto group : pb::record_table->Groups)
{
bool emptyGroup = true;
for (int i = 0; i <= 2; i++)
{
auto bmp = group->GetBitmap(i);
if (bmp)
{
emptyGroup = false;
break;
}
}
if (emptyGroup)
continue;
ImGui::Text("Group: %d, name:%s", group->GroupId, group->GroupName.c_str());
for (int i = 0; i <= 2; i++)
{
auto bmp = group->GetBitmap(i);
if (!bmp)
continue;
auto type = BitmapTypes[static_cast<uint8_t>(bmp->BitmapType)];
ImGui::Text("type:%s, size:%d, resolution: %dx%d, offset:%dx%d", type,
bmp->Resolution,
bmp->Width, bmp->Height, bmp->XPosition, bmp->YPosition);
}
for (int same = 0, i = 0; i <= 2; i++)
{
auto bmp = group->GetBitmap(i);
if (!bmp)
continue;
gdrv::CreatePreview(*bmp);
if (bmp->Texture)
{
if (!same)
same = true;
else
ImGui::SameLine();
ImGui::Image(bmp->Texture, ImVec2(bmp->Width * scale, bmp->Height * scale),
uv_min, uv_max, tint_col, border_col);
}
}
for (int same = 0, i = 0; i <= 2; i++)
{
auto zMap = group->GetZMap(i);
if (!zMap)
continue;
zdrv::CreatePreview(*zMap);
if (zMap->Texture)
{
if (!same)
same = true;
else
ImGui::SameLine();
ImGui::Image(zMap->Texture, ImVec2(zMap->Width * scale, zMap->Height * scale),
uv_min, uv_max, tint_col, border_col);
}
}
}
}
ImGui::End();
}
void render::BlitVScreen()
{
int pitch = 0;
ColorRgba* lockedPixels;
SDL_LockTexture
(
vScreenTex,
nullptr,
reinterpret_cast<void**>(&lockedPixels),
&pitch
);
assertm(static_cast<unsigned>(pitch) == vscreen->Width * sizeof(ColorRgba), "Padding on vScreen texture");
if (offset_x == 0 && offset_y == 0)
{
// No offset - direct copy
std::memcpy(lockedPixels, vscreen->BmpBufPtr1, vscreen->Width * vscreen->Height * sizeof(ColorRgba));
}
else
{
// Copy offset table and fixed side bar
auto tableWidth = pb::MainTable->Width;
auto scoreWidth = vscreen->Width - pb::MainTable->Width;
auto tableStride = tableWidth * sizeof(ColorRgba);
auto scoreStride = scoreWidth * sizeof(ColorRgba);
auto srcScorePtr = &vscreen->BmpBufPtr1[tableWidth];
auto xSrc = 0, ySrc = 0, xDst = offset_x, yDst = offset_y, height = vscreen->Height;
// Negative dst == positive src offset
if (xDst < 0)
{
xSrc -= xDst;
xDst = 0;
}
if (yDst < 0)
{
ySrc -= yDst;
yDst = 0;
}
if (xSrc)
{
tableStride -= xSrc * sizeof(ColorRgba);
}
if (xDst)
{
tableStride -= xDst * sizeof(ColorRgba);
tableWidth -= xDst;
scoreWidth += xDst;
}
if (ySrc)
height -= ySrc;
auto srcBmpPtr = &vscreen->BmpBufPtr1[vscreen->Width * ySrc + xSrc];
auto dstPtr = &lockedPixels[vscreen->Width * yDst + xDst];
for (int y = height; y > 0; --y)
{
std::memcpy(dstPtr, srcBmpPtr, tableStride);
dstPtr += tableWidth;
std::memcpy(dstPtr, srcScorePtr, scoreStride);
dstPtr += scoreWidth;
srcBmpPtr += vscreen->Stride;
srcScorePtr += vscreen->Stride;
}
}
SDL_UnlockTexture(vScreenTex);
}
void render::PresentVScreen()
{
BlitVScreen();
SDL_RenderCopy(winmain::Renderer, vScreenTex, nullptr, &DestinationRect);
}