From 0d9610ddb6d17d943c59b9406f54f3a55c50e795 Mon Sep 17 00:00:00 2001 From: Muzychenko Andrey <33288308+k4zmu2a@users.noreply.github.com> Date: Tue, 12 Oct 2021 16:30:20 +0300 Subject: [PATCH] Added new render mode with reduced tearing. Available under new option Window->Alternative Rendering. Issue #29. --- SpaceCadetPinball/SpaceCadetPinball.rc | 1 + SpaceCadetPinball/options.cpp | 10 ++ SpaceCadetPinball/options.h | 1 + SpaceCadetPinball/pb.cpp | 36 +++++-- SpaceCadetPinball/pb.h | 2 +- SpaceCadetPinball/render.cpp | 125 +++++++++++-------------- SpaceCadetPinball/render.h | 3 +- SpaceCadetPinball/resource.h | 1 + SpaceCadetPinball/winmain.cpp | 12 ++- 9 files changed, 112 insertions(+), 79 deletions(-) diff --git a/SpaceCadetPinball/SpaceCadetPinball.rc b/SpaceCadetPinball/SpaceCadetPinball.rc index 93754b9f3..cf997a6dc 100644 --- a/SpaceCadetPinball/SpaceCadetPinball.rc +++ b/SpaceCadetPinball/SpaceCadetPinball.rc @@ -98,6 +98,7 @@ BEGIN POPUP "&Window" BEGIN MENUITEM "&Uniform Scaling", Menu1_WindowUniformScale + MENUITEM "&Alternative Rendering", Menu1_AlternativeRender END END POPUP "&Help" diff --git a/SpaceCadetPinball/options.cpp b/SpaceCadetPinball/options.cpp index 45c77fe1c..fc7550c6d 100644 --- a/SpaceCadetPinball/options.cpp +++ b/SpaceCadetPinball/options.cpp @@ -108,6 +108,8 @@ void options::init(HMENU menuHandle) Options.RightTableBumpKey = get_int(nullptr, "Right Table Bump key", Options.RightTableBumpKey); Options.BottomTableBumpKey = get_int(nullptr, "Bottom Table Bump key", Options.BottomTableBumpKey); Options.UniformScaling = get_int(nullptr, "Uniform scaling", true); + Options.AlternativeRender = get_int(nullptr, "Alternative Render", false); + menu_check(Menu1_Sounds, Options.Sounds); Sound::Enable(0, 7, Options.Sounds); menu_check(Menu1_Music, Options.Music); @@ -117,6 +119,7 @@ void options::init(HMENU menuHandle) menu_check(Menu1_3Players, Options.Players == 3); menu_check(Menu1_4Players, Options.Players == 4); menu_check(Menu1_WindowUniformScale, Options.UniformScaling); + menu_check(Menu1_AlternativeRender, Options.AlternativeRender); auto tmpBuf = memory::allocate(0x1F4u); if (tmpBuf) { @@ -149,6 +152,7 @@ void options::uninit() set_int(nullptr, "Bottom Table Bump key", Options.BottomTableBumpKey); set_int(nullptr, "Screen Resolution", Options.Resolution); set_int(nullptr, "Uniform scaling", Options.UniformScaling); + set_int(nullptr, "Alternative Render", Options.AlternativeRender); } void options::path_init(LPCSTR regPath) @@ -343,6 +347,12 @@ void options::toggle(UINT uIDCheckItem) fullscrn::window_size_changed(); fullscrn::paint(); break; + case Menu1_AlternativeRender: + Options.AlternativeRender ^= true; + menu_check(Menu1_AlternativeRender, Options.AlternativeRender); + fullscrn::window_size_changed(); + fullscrn::paint(); + break; default: break; } diff --git a/SpaceCadetPinball/options.h b/SpaceCadetPinball/options.h index 73b4353e8..ec3fabe5c 100644 --- a/SpaceCadetPinball/options.h +++ b/SpaceCadetPinball/options.h @@ -23,6 +23,7 @@ struct optionsStruct int BottomTableBumpKeyDft; int Resolution; bool UniformScaling; + bool AlternativeRender; }; diff --git a/SpaceCadetPinball/pb.cpp b/SpaceCadetPinball/pb.cpp index 238b105b1..a8d4a507a 100644 --- a/SpaceCadetPinball/pb.cpp +++ b/SpaceCadetPinball/pb.cpp @@ -26,7 +26,7 @@ TPinballTable* pb::MainTable = nullptr; datFileStruct* pb::record_table = nullptr; -int pb::time_ticks = 0, pb::demo_mode = 0, pb::game_mode = 2, pb::mode_countdown_, pb::state; +int pb::time_ticks = 0, pb::demo_mode = 0, pb::game_mode = 2, pb::mode_countdown_, pb::state, pb::frameCounter = 0; float pb::time_now, pb::time_next, pb::ball_speed_limit; high_score_struct pb::highscore_table[5]; bool pb::FullTiltMode = false, pb::cheat_mode = false; @@ -129,9 +129,7 @@ void pb::reset_table() void pb::firsttime_setup() { - render::blit = 0; - render::update(); - render::blit = 1; + render::update(false); } void pb::paint() @@ -223,6 +221,8 @@ void pb::ballset(int x, int y) int pb::frame(int time) { + static int frameTime = 0; + if (time > 100) time = 100; float timeMul = time * 0.001f; @@ -244,7 +244,29 @@ int pb::frame(int time) nudge::nudge_count = nudgeDec; } timer::check(); - render::update(); + + if (!options::Options.AlternativeRender) + { + render::update(true); + } + else + { + // Screen update at UPS > screen refresh rate cause tearing. + // Especially noticeable on fast moving ball in scaled up window. + // Retained render prevents frame skip. The next best thing - complete refresh at fixed rate. + render::update(false); + + // Frame time at 60 FPS = 16.(6) ms = (16 + 17 + 17) / 3 + auto targetTime = frameCounter % 3 == 0 ? 16 : 17; + frameTime += time; + if (frameTime >= targetTime) + { + frameTime = min(frameTime - targetTime, 100); + render::shift(0, 0, 0, 0, MainTable->Width, MainTable->Height); + frameCounter++; + } + } + score::update(MainTable->CurScoreStruct); if (!MainTable->TiltLockFlag) { @@ -288,8 +310,8 @@ void pb::timed_frame(float timeNow, float timeDelta, bool drawBalls) ball->Acceleration.Y = ball->Speed * ball->Acceleration.Y; maths::vector_add(&ball->Acceleration, &vec2); ball->Speed = maths::normalize_2d(&ball->Acceleration); - ball->InvAcceleration.X = ball->Acceleration.X == 0.0f ? 1000000000.0f : 1.0f / ball->Acceleration.X; - ball->InvAcceleration.Y = ball->Acceleration.Y == 0.0f ? 1000000000.0f : 1.0f / ball->Acceleration.Y; + ball->InvAcceleration.X = ball->Acceleration.X == 0.0f ? 1.0e9f : 1.0f / ball->Acceleration.X; + ball->InvAcceleration.Y = ball->Acceleration.Y == 0.0f ? 1.0e9f : 1.0f / ball->Acceleration.Y; } auto timeDelta2 = timeDelta; diff --git a/SpaceCadetPinball/pb.h b/SpaceCadetPinball/pb.h index 9e690e2e7..97106e6a4 100644 --- a/SpaceCadetPinball/pb.h +++ b/SpaceCadetPinball/pb.h @@ -10,7 +10,7 @@ class pb public: static int time_ticks; static float ball_speed_limit, time_now, time_next; - static int game_mode; + static int game_mode, frameCounter; static bool cheat_mode; static datFileStruct* record_table; static TPinballTable* MainTable; diff --git a/SpaceCadetPinball/render.cpp b/SpaceCadetPinball/render.cpp index 61eeb7b1e..19e120ed9 100644 --- a/SpaceCadetPinball/render.cpp +++ b/SpaceCadetPinball/render.cpp @@ -2,7 +2,6 @@ #include "render.h" #include "memory.h" -int render::blit = 0; int render::many_dirty, render::many_sprites, render::many_balls; render_sprite_type_struct **render::dirty_list, **render::sprite_list, **render::ball_list; zmap_header_type* render::background_zmap; @@ -60,83 +59,69 @@ void render::uninit() many_balls = 0; } -void render::update() +void render::update(bool blit) { rectangle_type overlapRect{}; - auto dirtyPtr = dirty_list; - for (int index = 0; index < many_dirty; ++dirtyPtr, ++index) + for (int index = 0; index < many_dirty; ++index) { - auto curSprite = *dirtyPtr; - if ((*dirtyPtr)->VisualType != VisualType::None) + auto curSprite = dirty_list[index]; + bool clearSprite = false; + switch (curSprite->VisualType) { - if ((*dirtyPtr)->VisualType == VisualType::Sprite) - { - if (curSprite->BmpRectCopy.Width > 0) - maths::enclosing_box(&curSprite->BmpRectCopy, &curSprite->BmpRect, &curSprite->DirtyRect); - - if (!maths::rectangle_clip(&curSprite->DirtyRect, &vscreen_rect, &curSprite->DirtyRect)) - { - curSprite->DirtyRect.Width = -1; - continue; - } + case VisualType::Sprite: + if (curSprite->BmpRectCopy.Width > 0) + maths::enclosing_box(&curSprite->BmpRectCopy, &curSprite->BmpRect, &curSprite->DirtyRect); - 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); - } + if (maths::rectangle_clip(&curSprite->DirtyRect, &vscreen_rect, &curSprite->DirtyRect)) + clearSprite = true; + else + curSprite->DirtyRect.Width = -1; + break; + case VisualType::None: + if (maths::rectangle_clip(&curSprite->BmpRect, &vscreen_rect, &curSprite->DirtyRect)) + clearSprite = !curSprite->Bmp; + else + curSprite->DirtyRect.Width = -1; + break; + default: break; } - else + + if (clearSprite) { - if (!maths::rectangle_clip(&curSprite->BmpRect, &vscreen_rect, &curSprite->DirtyRect)) - { - curSprite->DirtyRect.Width = -1; - continue; - } - if (!curSprite->Bmp) - { - 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); - } + 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); } } - dirtyPtr = dirty_list; for (int index = 0; index < many_dirty; ++index) { - auto sprite = *dirtyPtr; - if ((*dirtyPtr)->DirtyRect.Width > 0 && (sprite->VisualType == VisualType::None || sprite->VisualType == + auto sprite = dirty_list[index]; + if (sprite->DirtyRect.Width > 0 && (sprite->VisualType == VisualType::None || sprite->VisualType == VisualType::Sprite)) - repaint(*dirtyPtr); - ++dirtyPtr; + repaint(sprite); } - paint_balls(); if (blit) { + paint_balls(); gdrv::start_blit_sequence(); auto xPos = vscreen.XPosition + offset_x; auto yPos = vscreen.YPosition + offset_y; - dirtyPtr = dirty_list; - for (int index = 0; index < many_dirty; ++dirtyPtr, ++index) + + for (int index = 0; index < many_dirty; ++index) { - auto sprite = *dirtyPtr; - auto dirtyRect = &(*dirtyPtr)->DirtyRect; - auto width2 = (*dirtyPtr)->DirtyRect.Width; + auto sprite = dirty_list[index]; + auto dirtyRect = &sprite->DirtyRect; + auto width2 = sprite->DirtyRect.Width; if (width2 > 0) gdrv::blit_sequence( &vscreen, @@ -147,21 +132,16 @@ void render::update() width2, dirtyRect->Height); - auto rect = &sprite->BmpRectCopy; - rect->XPosition = dirtyRect->XPosition; - rect->YPosition = dirtyRect->YPosition; - rect->Width = dirtyRect->Width; - rect->Height = dirtyRect->Height; - + sprite->BmpRectCopy = *dirtyRect; if (sprite->UnknownFlag != 0) remove_sprite(sprite); } - dirtyPtr = ball_list; - for (int index = 0; index < many_balls; ++dirtyPtr, ++index) + for (int index = 0; index < many_balls; ++index) { - auto rectCopy = &(*dirtyPtr)->BmpRectCopy; - auto dirtyRect = &(*dirtyPtr)->DirtyRect; + auto sprite = ball_list[index]; + auto rectCopy = &sprite->BmpRectCopy; + auto dirtyRect = &sprite->DirtyRect; if (maths::overlapping_box(dirtyRect, rectCopy, &overlapRect) && dirtyRect->Width > 0) { if (overlapRect.Width > 0) @@ -198,13 +178,22 @@ void render::update() } gdrv::end_blit_sequence(); + unpaint_balls(); + } + else + { + for (int index = 0; index < many_dirty; ++index) + { + auto sprite = dirty_list[index]; + sprite->BmpRectCopy = sprite->DirtyRect; + if (sprite->UnknownFlag != 0) + remove_sprite(sprite); + } } many_dirty = 0; - unpaint_balls(); } - void render::paint() { paint_balls(); @@ -473,7 +462,7 @@ void render::paint_balls() void render::unpaint_balls() { - for (int index = many_balls-1; index >= 0; index--) + for (int index = many_balls - 1; index >= 0; index--) { auto curBall = ball_list[index]; if (curBall->DirtyRect.Width > 0) diff --git a/SpaceCadetPinball/render.h b/SpaceCadetPinball/render.h index 88173bdf2..7e85fd98e 100644 --- a/SpaceCadetPinball/render.h +++ b/SpaceCadetPinball/render.h @@ -31,7 +31,6 @@ struct render_sprite_type_struct class render { public: - static int blit; static int many_dirty, many_sprites, many_balls; static render_sprite_type_struct **dirty_list, **sprite_list, **ball_list; static zmap_header_type* background_zmap; @@ -43,7 +42,7 @@ class render static void init(gdrv_bitmap8* bmp, float zMin, float zScaler, int width, int height); static void uninit(); - static void update(); + static void update(bool blit); static void paint(); static void sprite_modified(render_sprite_type_struct* sprite); static render_sprite_type_struct* create_sprite(VisualType visualType, gdrv_bitmap8* bmp, diff --git a/SpaceCadetPinball/resource.h b/SpaceCadetPinball/resource.h index 5ba78906d..837ee9265 100644 --- a/SpaceCadetPinball/resource.h +++ b/SpaceCadetPinball/resource.h @@ -240,6 +240,7 @@ #define DLG_HIGHSCORES_EditName3 603 #define DLG_HIGHSCORES_EditName4 604 #define DLG_HIGHSCORES_EditName5 605 +#define Menu1_AlternativeRender 601 // Next default values for new objects // diff --git a/SpaceCadetPinball/winmain.cpp b/SpaceCadetPinball/winmain.cpp index 5e9544eae..f1b2b19cd 100644 --- a/SpaceCadetPinball/winmain.cpp +++ b/SpaceCadetPinball/winmain.cpp @@ -220,7 +220,16 @@ int winmain::WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi if (prevTime) { char buf[60]; - sprintf_s(buf, "Frames/sec = %02.02f", 300.0f / (static_cast(curTime - prevTime) * 0.001f)); + auto dt = static_cast(curTime - prevTime) * 0.001f; + if (!options::Options.AlternativeRender) + sprintf_s(buf, "Frames/sec = %02.02f", 300.0f / dt); + else + { + sprintf_s(buf, "Updates/sec = %02.02f Frames/sec = %02.02f", + 300.0f / dt, pb::frameCounter / dt); + pb::frameCounter = 0; + } + SetWindowTextA(hwnd_frame, buf); if (DispGRhistory) @@ -610,6 +619,7 @@ LRESULT CALLBACK winmain::message_handler(HWND hWnd, UINT Msg, WPARAM wParam, LP case Menu1_800x600: case Menu1_1024x768: case Menu1_WindowUniformScale: + case Menu1_AlternativeRender: options::toggle(wParamI); break; case Menu1_Help_Topics: