1 #include "pch.h"
2 #include "render.h"
3
4 #include "fullscrn.h"
5 #include "GroupData.h"
6 #include "options.h"
7 #include "pb.h"
8 #include "score.h"
9 #include "TPinballTable.h"
10 #include "winmain.h"
11
12 std::vector<render_sprite_type_struct*> render::dirty_list, render::sprite_list, render::ball_list;
13 zmap_header_type* render::background_zmap;
14 int render::zmap_offset, render::zmap_offsetY, render::offset_x, render::offset_y;
15 float render::zscaler, render::zmin, render::zmax;
16 rectangle_type render::vscreen_rect;
17 gdrv_bitmap8 *render::vscreen, *render::background_bitmap, *render::ball_bitmap[20];
18 zmap_header_type* render::zscreen;
19 SDL_Texture* render::vScreenTex = nullptr;
20 SDL_Rect render::DestinationRect{};
21
init(gdrv_bitmap8 * bmp,float zMin,float zScaler,int width,int height)22 void render::init(gdrv_bitmap8* bmp, float zMin, float zScaler, int width, int height)
23 {
24 zscaler = zScaler;
25 zmin = zMin;
26 zmax = 4294967300.0f / zScaler + zMin;
27 vscreen = new gdrv_bitmap8(width, height, false);
28 zscreen = new zmap_header_type(width, height, width);
29 zdrv::fill(zscreen, zscreen->Width, zscreen->Height, 0, 0, 0xFFFF);
30 vscreen_rect.YPosition = 0;
31 vscreen_rect.XPosition = 0;
32 vscreen_rect.Width = width;
33 vscreen_rect.Height = height;
34 vscreen->YPosition = 0;
35 vscreen->XPosition = 0;
36 for (auto& ballBmp : ball_bitmap)
37 ballBmp = new gdrv_bitmap8(64, 64, false);
38
39 background_bitmap = bmp;
40 if (bmp)
41 gdrv::copy_bitmap(vscreen, width, height, 0, 0, bmp, 0, 0);
42 else
43 gdrv::fill_bitmap(vscreen, vscreen->Width, vscreen->Height, 0, 0, 0);
44
45 recreate_screen_texture();
46 }
47
uninit()48 void render::uninit()
49 {
50 delete vscreen;
51 delete zscreen;
52 for (auto sprite : sprite_list)
53 remove_sprite(sprite, false);
54 for (auto ball : ball_list)
55 remove_ball(ball, false);
56 for (auto& ballBmp : ball_bitmap)
57 delete ballBmp;
58 ball_list.clear();
59 dirty_list.clear();
60 sprite_list.clear();
61 SDL_DestroyTexture(vScreenTex);
62 vScreenTex = nullptr;
63 }
64
recreate_screen_texture()65 void render::recreate_screen_texture()
66 {
67 if (vScreenTex != nullptr)
68 {
69 SDL_DestroyTexture(vScreenTex);
70 }
71
72 UsingSdlHint hint{ SDL_HINT_RENDER_SCALE_QUALITY, options::Options.LinearFiltering ? "linear" : "nearest" };
73 vScreenTex = SDL_CreateTexture
74 (
75 winmain::Renderer,
76 SDL_PIXELFORMAT_ARGB8888,
77 SDL_TEXTUREACCESS_STREAMING,
78 vscreen_rect.Width, vscreen_rect.Height
79 );
80 SDL_SetTextureBlendMode(vScreenTex, SDL_BLENDMODE_NONE);
81 }
82
update()83 void render::update()
84 {
85 unpaint_balls();
86
87 // Clip dirty sprites with vScreen, clear clipping (dirty) rectangles
88 for (auto curSprite : dirty_list)
89 {
90 bool clearSprite = false;
91 switch (curSprite->VisualType)
92 {
93 case VisualTypes::Sprite:
94 if (curSprite->DirtyRectPrev.Width > 0)
95 maths::enclosing_box(&curSprite->DirtyRectPrev, &curSprite->BmpRect, &curSprite->DirtyRect);
96
97 if (maths::rectangle_clip(&curSprite->DirtyRect, &vscreen_rect, &curSprite->DirtyRect))
98 clearSprite = true;
99 else
100 curSprite->DirtyRect.Width = -1;
101 break;
102 case VisualTypes::None:
103 if (maths::rectangle_clip(&curSprite->BmpRect, &vscreen_rect, &curSprite->DirtyRect))
104 clearSprite = !curSprite->Bmp;
105 else
106 curSprite->DirtyRect.Width = -1;
107 break;
108 default: break;
109 }
110
111 if (clearSprite)
112 {
113 auto yPos = curSprite->DirtyRect.YPosition;
114 auto width = curSprite->DirtyRect.Width;
115 auto xPos = curSprite->DirtyRect.XPosition;
116 auto height = curSprite->DirtyRect.Height;
117 zdrv::fill(zscreen, width, height, xPos, yPos, 0xFFFF);
118 if (background_bitmap)
119 gdrv::copy_bitmap(vscreen, width, height, xPos, yPos, background_bitmap, xPos, yPos);
120 else
121 gdrv::fill_bitmap(vscreen, width, height, xPos, yPos, 0);
122 }
123 }
124
125 // Paint dirty rectangles of dirty sprites
126 for (auto sprite : dirty_list)
127 {
128 if (sprite->DirtyRect.Width > 0 && (sprite->VisualType == VisualTypes::None || sprite->VisualType ==
129 VisualTypes::Sprite))
130 repaint(sprite);
131 }
132
133 paint_balls();
134
135 // In the original, this used to blit dirty sprites and balls
136 for (auto sprite : dirty_list)
137 {
138 sprite->DirtyRectPrev = sprite->DirtyRect;
139 if (sprite->UnknownFlag != 0)
140 remove_sprite(sprite, true);
141 }
142
143 dirty_list.clear();
144 }
145
sprite_modified(render_sprite_type_struct * sprite)146 void render::sprite_modified(render_sprite_type_struct* sprite)
147 {
148 if (sprite->VisualType != VisualTypes::Ball && dirty_list.size() < 999)
149 dirty_list.push_back(sprite);
150 }
151
create_sprite(VisualTypes visualType,gdrv_bitmap8 * bmp,zmap_header_type * zMap,int xPosition,int yPosition,rectangle_type * rect)152 render_sprite_type_struct* render::create_sprite(VisualTypes visualType, gdrv_bitmap8* bmp, zmap_header_type* zMap,
153 int xPosition, int yPosition, rectangle_type* rect)
154 {
155 auto sprite = new render_sprite_type_struct();
156 if (!sprite)
157 return nullptr;
158 sprite->BmpRect.YPosition = yPosition;
159 sprite->BmpRect.XPosition = xPosition;
160 sprite->Bmp = bmp;
161 sprite->VisualType = visualType;
162 sprite->UnknownFlag = 0;
163 sprite->SpriteArray = nullptr;
164 sprite->DirtyRect = rectangle_type{};
165 if (rect)
166 {
167 sprite->BoundingRect = *rect;
168 }
169 else
170 {
171 sprite->BoundingRect.Width = -1;
172 sprite->BoundingRect.Height = -1;
173 sprite->BoundingRect.XPosition = 0;
174 sprite->BoundingRect.YPosition = 0;
175 }
176 if (bmp)
177 {
178 sprite->BmpRect.Width = bmp->Width;
179 sprite->BmpRect.Height = bmp->Height;
180 }
181 else
182 {
183 sprite->BmpRect.Width = 0;
184 sprite->BmpRect.Height = 0;
185 }
186 sprite->ZMap = zMap;
187 sprite->ZMapOffestX = 0;
188 sprite->ZMapOffestY = 0;
189 if (!zMap && visualType != VisualTypes::Ball)
190 {
191 sprite->ZMap = background_zmap;
192 sprite->ZMapOffestY = xPosition - zmap_offset;
193 sprite->ZMapOffestX = yPosition - zmap_offsetY;
194 }
195 sprite->DirtyRectPrev = sprite->BmpRect;
196 if (visualType == VisualTypes::Ball)
197 {
198 ball_list.push_back(sprite);
199 }
200 else
201 {
202 sprite_list.push_back(sprite);
203 sprite_modified(sprite);
204 }
205 return sprite;
206 }
207
208
remove_sprite(render_sprite_type_struct * sprite,bool removeFromList)209 void render::remove_sprite(render_sprite_type_struct* sprite, bool removeFromList)
210 {
211 if (removeFromList)
212 {
213 auto it = std::find(sprite_list.begin(), sprite_list.end(), sprite);
214 if (it != sprite_list.end())
215 sprite_list.erase(it);
216 }
217
218 delete sprite->SpriteArray;
219 delete sprite;
220 }
221
remove_ball(render_sprite_type_struct * ball,bool removeFromList)222 void render::remove_ball(render_sprite_type_struct* ball, bool removeFromList)
223 {
224 if (removeFromList)
225 {
226 auto it = std::find(ball_list.begin(), ball_list.end(), ball);
227 if (it != ball_list.end())
228 ball_list.erase(it);
229 }
230
231 delete ball->SpriteArray;
232 delete ball;
233 }
234
sprite_set(render_sprite_type_struct * sprite,gdrv_bitmap8 * bmp,zmap_header_type * zMap,int xPos,int yPos)235 void render::sprite_set(render_sprite_type_struct* sprite, gdrv_bitmap8* bmp, zmap_header_type* zMap, int xPos,
236 int yPos)
237 {
238 if (sprite)
239 {
240 sprite->BmpRect.XPosition = xPos;
241 sprite->BmpRect.YPosition = yPos;
242 sprite->Bmp = bmp;
243 if (bmp)
244 {
245 sprite->BmpRect.Width = bmp->Width;
246 sprite->BmpRect.Height = bmp->Height;
247 }
248 sprite->ZMap = zMap;
249 sprite_modified(sprite);
250 }
251 }
252
sprite_set_bitmap(render_sprite_type_struct * sprite,gdrv_bitmap8 * bmp)253 void render::sprite_set_bitmap(render_sprite_type_struct* sprite, gdrv_bitmap8* bmp)
254 {
255 if (sprite && sprite->Bmp != bmp)
256 {
257 sprite->Bmp = bmp;
258 if (bmp)
259 {
260 sprite->BmpRect.Width = bmp->Width;
261 sprite->BmpRect.Height = bmp->Height;
262 }
263 sprite_modified(sprite);
264 }
265 }
266
set_background_zmap(struct zmap_header_type * zMap,int offsetX,int offsetY)267 void render::set_background_zmap(struct zmap_header_type* zMap, int offsetX, int offsetY)
268 {
269 background_zmap = zMap;
270 zmap_offset = offsetX;
271 zmap_offsetY = offsetY;
272 }
273
ball_set(render_sprite_type_struct * sprite,gdrv_bitmap8 * bmp,float depth,int xPos,int yPos)274 void render::ball_set(render_sprite_type_struct* sprite, gdrv_bitmap8* bmp, float depth, int xPos, int yPos)
275 {
276 if (sprite)
277 {
278 sprite->Bmp = bmp;
279 if (bmp)
280 {
281 sprite->BmpRect.XPosition = xPos;
282 sprite->BmpRect.YPosition = yPos;
283 sprite->BmpRect.Width = bmp->Width;
284 sprite->BmpRect.Height = bmp->Height;
285 }
286 if (depth >= zmin)
287 {
288 float depth2 = (depth - zmin) * zscaler;
289 if (depth2 <= zmax)
290 sprite->Depth = static_cast<short>(depth2);
291 else
292 sprite->Depth = -1;
293 }
294 else
295 {
296 sprite->Depth = 0;
297 }
298 }
299 }
300
repaint(struct render_sprite_type_struct * sprite)301 void render::repaint(struct render_sprite_type_struct* sprite)
302 {
303 rectangle_type clipRect{};
304 if (!sprite->SpriteArray)
305 return;
306 for (auto refSprite : *sprite->SpriteArray)
307 {
308 if (!refSprite->UnknownFlag && refSprite->Bmp)
309 {
310 if (maths::rectangle_clip(&refSprite->BmpRect, &sprite->DirtyRect, &clipRect))
311 zdrv::paint(
312 clipRect.Width,
313 clipRect.Height,
314 vscreen,
315 clipRect.XPosition,
316 clipRect.YPosition,
317 zscreen,
318 clipRect.XPosition,
319 clipRect.YPosition,
320 refSprite->Bmp,
321 clipRect.XPosition - refSprite->BmpRect.XPosition,
322 clipRect.YPosition - refSprite->BmpRect.YPosition,
323 refSprite->ZMap,
324 clipRect.XPosition + refSprite->ZMapOffestY - refSprite->BmpRect.XPosition,
325 clipRect.YPosition + refSprite->ZMapOffestX - refSprite->BmpRect.YPosition);
326 }
327 }
328 }
329
330
paint_balls()331 void render::paint_balls()
332 {
333 // Sort ball sprites by depth
334 for (auto i = 0u; i < ball_list.size(); i++)
335 {
336 for (auto j = i; j < ball_list.size() / 2; ++j)
337 {
338 auto ballA = ball_list[j];
339 auto ballB = ball_list[i];
340 if (ballB->Depth > ballA->Depth)
341 {
342 ball_list[i] = ballA;
343 ball_list[j] = ballB;
344 }
345 }
346 }
347
348 // For balls that clip vScreen: save original vScreen contents and paint ball bitmap.
349 for (auto index = 0u; index < ball_list.size(); ++index)
350 {
351 auto ball = ball_list[index];
352 auto dirty = &ball->DirtyRect;
353 if (ball->Bmp && maths::rectangle_clip(&ball->BmpRect, &vscreen_rect, &ball->DirtyRect))
354 {
355 int xPos = dirty->XPosition;
356 int yPos = dirty->YPosition;
357 gdrv::copy_bitmap(ball_bitmap[index], dirty->Width, dirty->Height, 0, 0, vscreen, xPos, yPos);
358 zdrv::paint_flat(
359 dirty->Width,
360 dirty->Height,
361 vscreen,
362 xPos,
363 yPos,
364 zscreen,
365 xPos,
366 yPos,
367 ball->Bmp,
368 xPos - ball->BmpRect.XPosition,
369 yPos - ball->BmpRect.YPosition,
370 ball->Depth);
371 }
372 else
373 {
374 dirty->Width = -1;
375 }
376 }
377 }
378
unpaint_balls()379 void render::unpaint_balls()
380 {
381 // Restore portions of vScreen saved during previous paint_balls call.
382 for (int index = static_cast<int>(ball_list.size()) - 1; index >= 0; index--)
383 {
384 auto curBall = ball_list[index];
385 if (curBall->DirtyRect.Width > 0)
386 gdrv::copy_bitmap(
387 vscreen,
388 curBall->DirtyRect.Width,
389 curBall->DirtyRect.Height,
390 curBall->DirtyRect.XPosition,
391 curBall->DirtyRect.YPosition,
392 ball_bitmap[index],
393 0,
394 0);
395
396 curBall->DirtyRectPrev = curBall->DirtyRect;
397 }
398 }
399
shift(int offsetX,int offsetY)400 void render::shift(int offsetX, int offsetY)
401 {
402 offset_x += offsetX;
403 offset_y += offsetY;
404 }
405
build_occlude_list()406 void render::build_occlude_list()
407 {
408 std::vector<render_sprite_type_struct*>* spriteArr = nullptr;
409 for (auto mainSprite : sprite_list)
410 {
411 if (mainSprite->SpriteArray)
412 {
413 delete mainSprite->SpriteArray;
414 mainSprite->SpriteArray = nullptr;
415 }
416
417 if (!mainSprite->UnknownFlag && mainSprite->BoundingRect.Width != -1)
418 {
419 if (!spriteArr)
420 spriteArr = new std::vector<render_sprite_type_struct*>();
421
422 for (auto refSprite : sprite_list)
423 {
424 if (!refSprite->UnknownFlag
425 && refSprite->BoundingRect.Width != -1
426 && maths::rectangle_clip(&mainSprite->BoundingRect, &refSprite->BoundingRect, nullptr)
427 && spriteArr)
428 {
429 spriteArr->push_back(refSprite);
430 }
431 }
432
433 if (mainSprite->Bmp && spriteArr->size() < 2)
434 spriteArr->clear();
435 if (!spriteArr->empty())
436 {
437 mainSprite->SpriteArray = spriteArr;
438 spriteArr = nullptr;
439 }
440 }
441 }
442
443 delete spriteArr;
444 }
445
SpriteViewer(bool * show)446 void render::SpriteViewer(bool* show)
447 {
448 static const char* BitmapTypes[] =
449 {
450 "None",
451 "RawBitmap",
452 "DibBitmap",
453 "Spliced",
454 };
455 static float scale = 1.0f;
456 auto uv_min = ImVec2(0.0f, 0.0f); // Top-left
457 auto uv_max = ImVec2(1.0f, 1.0f); // Lower-right
458 auto tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // No tint
459 auto border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); // 50% opaque white
460
461 if (ImGui::Begin("Sprite viewer", show, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_MenuBar))
462 {
463 if (ImGui::BeginMenuBar())
464 {
465 ImGui::SliderFloat("Sprite scale", &scale, 0.1f, 10.0f, "scale = %.3f");
466 ImGui::EndMenuBar();
467 }
468
469 for (const auto group : pb::record_table->Groups)
470 {
471 bool emptyGroup = true;
472 for (int i = 0; i <= 2; i++)
473 {
474 auto bmp = group->GetBitmap(i);
475 if (bmp)
476 {
477 emptyGroup = false;
478 break;
479 }
480 }
481 if (emptyGroup)
482 continue;
483
484 ImGui::Text("Group: %d, name:%s", group->GroupId, group->GroupName.c_str());
485 for (int i = 0; i <= 2; i++)
486 {
487 auto bmp = group->GetBitmap(i);
488 if (!bmp)
489 continue;
490
491 auto type = BitmapTypes[static_cast<uint8_t>(bmp->BitmapType)];
492 ImGui::Text("type:%s, size:%d, resolution: %dx%d, offset:%dx%d", type,
493 bmp->Resolution,
494 bmp->Width, bmp->Height, bmp->XPosition, bmp->YPosition);
495 }
496
497 for (int same = 0, i = 0; i <= 2; i++)
498 {
499 auto bmp = group->GetBitmap(i);
500 if (!bmp)
501 continue;
502
503 gdrv::CreatePreview(*bmp);
504 if (bmp->Texture)
505 {
506 if (!same)
507 same = true;
508 else
509 ImGui::SameLine();
510
511 ImGui::Image(bmp->Texture, ImVec2(bmp->Width * scale, bmp->Height * scale),
512 uv_min, uv_max, tint_col, border_col);
513 }
514 }
515
516 for (int same = 0, i = 0; i <= 2; i++)
517 {
518 auto zMap = group->GetZMap(i);
519 if (!zMap)
520 continue;
521
522 zdrv::CreatePreview(*zMap);
523 if (zMap->Texture)
524 {
525 if (!same)
526 same = true;
527 else
528 ImGui::SameLine();
529 ImGui::Image(zMap->Texture, ImVec2(zMap->Width * scale, zMap->Height * scale),
530 uv_min, uv_max, tint_col, border_col);
531 }
532 }
533 }
534 }
535 ImGui::End();
536 }
537
BlitVScreen()538 void render::BlitVScreen()
539 {
540 int pitch = 0;
541 ColorRgba* lockedPixels;
542 SDL_LockTexture
543 (
544 vScreenTex,
545 nullptr,
546 reinterpret_cast<void**>(&lockedPixels),
547 &pitch
548 );
549 assertm(static_cast<unsigned>(pitch) == vscreen->Width * sizeof(ColorRgba), "Padding on vScreen texture");
550
551 std::memcpy(lockedPixels, vscreen->BmpBufPtr1, vscreen->Width * vscreen->Height * sizeof(ColorRgba));
552
553 SDL_UnlockTexture(vScreenTex);
554 }
555
PresentVScreen()556 void render::PresentVScreen()
557 {
558 BlitVScreen();
559
560 if (offset_x == 0 && offset_y == 0)
561 {
562 SDL_RenderCopy(winmain::Renderer, vScreenTex, nullptr, &DestinationRect);
563 }
564 else
565 {
566 auto tableWidthCoef = static_cast<float>(pb::MainTable->Width) / vscreen->Width;
567 auto srcSeparationX = static_cast<int>(round(vscreen->Width * tableWidthCoef));
568 auto srcBoardRect = SDL_Rect
569 {
570 0, 0,
571 srcSeparationX, vscreen->Height
572 };
573 auto srcSidebarRect = SDL_Rect
574 {
575 srcSeparationX, 0,
576 vscreen->Width - srcSeparationX, vscreen->Height
577 };
578
579 #if SDL_VERSION_ATLEAST(2, 0, 10)
580 // SDL_RenderCopyF was added in 2.0.10
581 auto dstSeparationX = DestinationRect.w * tableWidthCoef;
582 auto dstBoardRect = SDL_FRect
583 {
584 DestinationRect.x + offset_x * fullscrn::ScaleX,
585 DestinationRect.y + offset_y * fullscrn::ScaleY,
586 dstSeparationX, static_cast<float>(DestinationRect.h)
587 };
588 auto dstSidebarRect = SDL_FRect
589 {
590 DestinationRect.x + dstSeparationX, static_cast<float>(DestinationRect.y),
591 DestinationRect.w - dstSeparationX, static_cast<float>(DestinationRect.h)
592 };
593
594 SDL_RenderCopyF(winmain::Renderer, vScreenTex, &srcBoardRect, &dstBoardRect);
595 SDL_RenderCopyF(winmain::Renderer, vScreenTex, &srcSidebarRect, &dstSidebarRect);
596 #else
597 // SDL_RenderCopy cannot express sub pixel offset.
598 // Vscreen shift is required for that.
599 auto dstSeparationX = static_cast<int>(DestinationRect.w * tableWidthCoef);
600 auto scaledOffX = static_cast<int>(round(offset_x * fullscrn::ScaleX));
601 if (offset_x != 0 && scaledOffX == 0)
602 scaledOffX = Sign(offset_x);
603 auto scaledOffY = static_cast<int>(round(offset_y * fullscrn::ScaleY));
604 if (offset_y != 0 && scaledOffX == 0)
605 scaledOffY = Sign(offset_y);
606
607 auto dstBoardRect = SDL_Rect
608 {
609 DestinationRect.x + scaledOffX, DestinationRect.y + scaledOffY,
610 dstSeparationX, DestinationRect.h
611 };
612 auto dstSidebarRect = SDL_Rect
613 {
614 DestinationRect.x + dstSeparationX, DestinationRect.y,
615 DestinationRect.w - dstSeparationX, DestinationRect.h
616 };
617
618 SDL_RenderCopy(winmain::Renderer, vScreenTex, &srcBoardRect, &dstBoardRect);
619 SDL_RenderCopy(winmain::Renderer, vScreenTex, &srcSidebarRect, &dstSidebarRect);
620 #endif
621 }
622 }
623