Skip to content

Commit 9f901cf

Browse files
committed
Implement cursor blink
1 parent aac19bb commit 9f901cf

File tree

12 files changed

+166
-15
lines changed

12 files changed

+166
-15
lines changed

benchmarks/benchmarkRenderers.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ static void benchmarkRenderer(std::string_view name)
2525
renderer.m_codingMethod[B] = CODINGB;
2626
renderer.m_bps[A] = BPS;
2727
renderer.m_bps[B] = BPS;
28-
renderer.SetDisplayFormat(Video::Renderer::DisplayFormat::PAL);
28+
renderer.SetDisplayFormat(Video::Renderer::DisplayFormat::PAL, false);
2929
renderer.m_mix = true;
3030

3131
uint16_t lineNumber = 0;
@@ -57,7 +57,7 @@ template<typename RENDERER>
5757
static void benchmarkRendererCursor(std::string_view name)
5858
{
5959
RENDERER renderer;
60-
renderer.SetDisplayFormat(Video::Renderer::DisplayFormat::PAL);
60+
renderer.SetDisplayFormat(Video::Renderer::DisplayFormat::PAL, false);
6161

6262
// Benchmark
6363
const std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();

src/CDI/Video/Pixel.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ struct alignas(uint32_t) Pixel
4747
const ARGB32* AsU32Pointer() const noexcept { return reinterpret_cast<const ARGB32*>(this); }
4848
ARGB32* AsU32Pointer() noexcept { return reinterpret_cast<ARGB32*>(this); }
4949

50+
constexpr Pixel Complement() const noexcept
51+
{
52+
return (~AsU32() & 0x00'FF'FF'FF) | (AsU32() & 0xFF'00'00'00);
53+
}
54+
5055
constexpr bool operator==(const Pixel& other) const = default;
5156

5257
constexpr Pixel& operator=(const ARGB32 argb) noexcept

src/CDI/Video/PixelTest.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ static consteval void testPixelStaticAssert()
1818
static_assert(p.a == 0x11);
1919
static_assert(p.r == 0x22);
2020
static_assert(p.g == 0x33);
21+
static_assert(p.Complement() == 0x11DDCCBB);
2122
static_assert(p.b == 0x44);
2223

2324
static_assert((p & 0xF0F0F0F0) == 0x10203040);

src/CDI/Video/Renderer.cpp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,43 @@ namespace Video
99
/** \brief Configures the display format.
1010
* The input must only be a valid enum value.
1111
*/
12-
void Renderer::SetDisplayFormat(const DisplayFormat display, const bool highResolution) noexcept
12+
void Renderer::SetDisplayFormat(const DisplayFormat display, const bool highResolution, bool fps60) noexcept
1313
{
1414
if(!isValidDisplayFormat(display))
1515
panic("Invalid display format {}", static_cast<int>(display));
1616

1717
m_displayFormat = display;
1818
m_highResolution = highResolution;
19+
m_60FPS = fps60;
1920
}
2021

2122
bool Renderer::isValidDisplayFormat(const DisplayFormat display) noexcept
2223
{
2324
return display == DisplayFormat::NTSCMonitor || display == DisplayFormat::NTSCTV || display == DisplayFormat::PAL;
2425
}
2526

27+
/** \brief Increments the internal time of the cursor blink.
28+
* \param ns The time in nanosecond.
29+
*/
30+
void Renderer::IncrementCursorTime(const double ns) noexcept
31+
{
32+
if(!m_cursorEnabled || m_cursorBlinkOff == 0)
33+
return; // OFF == 0 means ON indefinitely.
34+
35+
double delta = m_60FPS ? DELTA_60FPS : DELTA_50FPS;
36+
if(m_cursorIsOn)
37+
delta *= m_cursorBlinkOn;
38+
else
39+
delta *= m_cursorBlinkOff;
40+
41+
m_cursorTime += ns;
42+
if(m_cursorTime >= delta)
43+
{
44+
m_cursorTime -= delta;
45+
m_cursorIsOn = !m_cursorIsOn;
46+
}
47+
}
48+
2649
/** \brief To be called when the whole frame is drawn.
2750
* \return The final screen.
2851
*

src/CDI/Video/Renderer.hpp

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,7 @@ static constexpr bool matteMF(const uint32_t matteCommand) noexcept
2121
* Every array member with 2 elements means its meant to be index based on the plane number \ref ImagePlane.
2222
*
2323
* TODO:
24-
* - Should the renderer manage the line count and draw itself the final frame and the cursor when reached,
25-
* or let it to the user of the renderer with a `Plane RenderFrame();` method ?
2624
* - V.25/V.26 Pixel repeat on pixel decoding, pixel hold on overlay.
27-
* - Implement cursor blink.
2825
* - Should there be a reset method?
2926
*
3027
* TODO optimizations:
@@ -80,8 +77,9 @@ class Renderer
8077
Renderer& operator=(Renderer&&) = delete;
8178

8279
constexpr DisplayFormat GetDisplayFormat() const noexcept { return m_displayFormat; }
83-
void SetDisplayFormat(DisplayFormat display, bool highResolution) noexcept;
80+
void SetDisplayFormat(DisplayFormat display, bool highResolution, bool fps60) noexcept;
8481
static bool isValidDisplayFormat(DisplayFormat display) noexcept;
82+
8583
static constexpr uint16_t getDisplayWidth(DisplayFormat display) noexcept
8684
{
8785
return display == DisplayFormat::NTSCMonitor ? 360 : 384;
@@ -103,6 +101,7 @@ class Renderer
103101
return m_screen.m_width == 720;
104102
}
105103

104+
void IncrementCursorTime(double ns) noexcept;
106105
virtual std::pair<uint16_t, uint16_t> DrawLine(const uint8_t* lineA, const uint8_t* lineB, uint16_t lineNumber) noexcept = 0;
107106
const Plane& RenderFrame() noexcept;
108107

@@ -155,7 +154,7 @@ class Renderer
155154
uint8_t m_cursorColor : 4{}; /**< YRGB color code. */
156155
std::array<uint16_t, 16> m_cursorPatterns{};
157156
bool m_cursorBlinkType{}; /**< false is on/off, true is on/complement. */
158-
uint8_t m_cursorBlinkOn : 3{}; /**< ON period (zero not allowed). */
157+
uint8_t m_cursorBlinkOn : 3{1}; /**< ON period (zero not allowed). */
159158
uint8_t m_cursorBlinkOff : 3{}; /**< OFF period (if zero, blink is disabled). */
160159

161160
// Image Contribution Factor.
@@ -229,9 +228,16 @@ class Renderer
229228
};
230229

231230
protected:
231+
static constexpr double DELTA_50FPS = 240'000'000.; /**< GB VII.2.3.4.2 GC_Blnk. */
232+
static constexpr double DELTA_60FPS = 200'000'000.; /**< GB VII.2.3.4.2 GC_Blnk. */
233+
234+
double m_cursorTime{0.0}; /**< Keeps track of the emulated time for cursor blink. */
235+
bool m_cursorIsOn{true}; /**< Keeps the state of the cursor (ON or OFF/complement). true when ON. */
236+
232237
uint16_t m_lineNumber{}; /**< Current line being drawn, starts at 0. Handled by the caller. */
233238
DisplayFormat m_displayFormat{DisplayFormat::PAL}; /**< Used to select 360/384 width and 240/280 height. */
234239
bool m_highResolution{false}; /**< True for 480/560, false for 240/280 pixels height. */
240+
bool m_60FPS{false}; /**< False for 50FPS, true for 60FPS. */
235241

236242
virtual void DrawCursor() noexcept = 0;
237243
void DrawLineBackdrop() noexcept

src/CDI/Video/RendererSIMD.cpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,20 @@ void RendererSIMD::DrawCursor() noexcept
130130
// is outputted continuously line by line).
131131
// But for here maybe we don't care.
132132

133-
const Pixel color = backdropCursorColorToPixel(m_cursorColor);
133+
Pixel color = backdropCursorColorToPixel(m_cursorColor);
134+
if(!m_cursorIsOn)
135+
{
136+
if(m_cursorBlinkType) // Complement.
137+
color = color.Complement();
138+
else
139+
color = BLACK_PIXEL;
140+
}
134141

135142
using SIMDCursorLine = stdx::fixed_size_simd<uint32_t, 16>;
136143
using SIMDCursorLineMask = SIMDCursorLine::mask_type;
137144
static constexpr SIMDCursorLine SHIFTER([] (uint32_t i) { return 15 - i; });
138145

146+
const SIMDCursorLine colorSimd{color.AsU32()};
139147
int patternIndex = 0;
140148
for(Plane::iterator dst = m_cursorPlane.begin(); dst < m_cursorPlane.end(); dst += SIMDCursorLine::size(), ++patternIndex)
141149
{
@@ -145,8 +153,6 @@ void RendererSIMD::DrawCursor() noexcept
145153
patternSimd &= 1;
146154
const SIMDCursorLineMask patternMask = patternSimd == 1;
147155

148-
const SIMDCursorLine colorSimd{color.AsU32()};
149-
150156
SIMDCursorLine cursorPixels = BLACK_PIXEL.AsU32();
151157
stdx::where(patternMask, cursorPixels) = colorSimd;
152158
cursorPixels.copy_to(dst->AsU32Pointer(), stdx::element_aligned);

src/CDI/Video/RendererSoftware.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,14 @@ void RendererSoftware::DrawCursor() noexcept
119119
// Technically speaking the cursor is drawn when the drawing line number is the cursor's one (because video
120120
// is outputted continuously line by line).
121121
// But for here maybe we don't care.
122-
const Pixel color = backdropCursorColorToPixel(m_cursorColor);
122+
Pixel color = backdropCursorColorToPixel(m_cursorColor);
123+
if(!m_cursorIsOn)
124+
{
125+
if(m_cursorBlinkType) // Complement.
126+
color = color.Complement();
127+
else
128+
color = BLACK_PIXEL;
129+
}
123130

124131
Plane::iterator it = m_cursorPlane.begin();
125132
for(size_t y = 0; y < m_cursorPlane.m_height; ++y)

src/CDI/cores/MCD212/Display.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,21 @@ void MCD212::DrawVideoLine()
2828
if(m_lineNumber == 0)
2929
{
3030
m_lineNumber = 1;
31-
m_renderer.SetDisplayFormat(GetDisplayFormat(), true);
31+
m_renderer.SetDisplayFormat(GetDisplayFormat(), true, Is60FPS());
3232
}
3333
}
3434
else // Odd frames, even lines.
3535
{
3636
SetPA();
3737
if(m_lineNumber == 0)
38-
m_renderer.SetDisplayFormat(GetDisplayFormat(), true);
38+
m_renderer.SetDisplayFormat(GetDisplayFormat(), true, Is60FPS());
3939
}
4040
}
4141
else // Non-interlaced, PA is always set.
4242
{
4343
SetPA();
4444
if(m_lineNumber == 0)
45-
m_renderer.SetDisplayFormat(GetDisplayFormat(), false);
45+
m_renderer.SetDisplayFormat(GetDisplayFormat(), false, Is60FPS());
4646
}
4747

4848
if(GetDE())

src/CDI/cores/MCD212/MCD212.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ void MCD212::IncrementTime(const double ns)
4646
const size_t lineDisplayTime = GetLineDisplayTime();
4747
if(m_timeNs >= lineDisplayTime)
4848
{
49+
m_renderer.IncrementCursorTime(m_timeNs);
4950
DrawVideoLine();
5051
m_timeNs -= lineDisplayTime;
5152
}

src/CDI/cores/MCD212/MCD212.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ class MCD212
167167
return m_isPAL || !GetCF() ? 64000 : 63560;
168168
}
169169

170+
bool Is60FPS() const noexcept
171+
{
172+
return GetFD();
173+
}
174+
170175
Video::Renderer::DisplayFormat GetDisplayFormat() const noexcept
171176
{
172177
using enum Video::Renderer::DisplayFormat;

0 commit comments

Comments
 (0)