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