openMSX
V9990SDLRasterizer.cc
Go to the documentation of this file.
2#include "V9990.hh"
3#include "RawFrame.hh"
4#include "PostProcessor.hh"
5#include "Display.hh"
6#include "OutputSurface.hh"
7#include "RenderSettings.hh"
8#include "MemoryOps.hh"
9#include "enumerate.hh"
10#include "narrow.hh"
11#include "one_of.hh"
12#include "xrange.hh"
13#include "build-info.hh"
14#include "components.hh"
15#include <algorithm>
16#include <array>
17#include <cstdint>
18#include <memory>
19
20namespace openmsx {
21
22template<std::unsigned_integral Pixel>
24 V9990& vdp_, Display& display, OutputSurface& screen_,
25 std::unique_ptr<PostProcessor> postProcessor_)
26 : vdp(vdp_), vram(vdp.getVRAM())
27 , screen(screen_)
28 , workFrame(std::make_unique<RawFrame>(screen.getPixelFormat(), 1280, 240))
29 , renderSettings(display.getRenderSettings())
30 , postProcessor(std::move(postProcessor_))
31 , bitmapConverter(vdp, palette64, palette64_32768, palette256, palette256_32768, palette32768)
32 , p1Converter(vdp, palette64)
33 , p2Converter(vdp, palette64)
34{
35 // Fill palettes
37
38 renderSettings.getGammaSetting() .attach(*this);
39 renderSettings.getBrightnessSetting() .attach(*this);
40 renderSettings.getContrastSetting() .attach(*this);
41 renderSettings.getColorMatrixSetting().attach(*this);
42}
43
44template<std::unsigned_integral Pixel>
46{
47 renderSettings.getColorMatrixSetting().detach(*this);
48 renderSettings.getGammaSetting() .detach(*this);
49 renderSettings.getBrightnessSetting() .detach(*this);
50 renderSettings.getContrastSetting() .detach(*this);
51}
52
53template<std::unsigned_integral Pixel>
55{
56 return postProcessor.get();
57}
58
59template<std::unsigned_integral Pixel>
61{
62 return postProcessor->needRender() &&
63 vdp.getMotherBoard().isActive() &&
64 !vdp.getMotherBoard().isFastForwarding();
65}
66
67template<std::unsigned_integral Pixel>
69{
70 setDisplayMode(vdp.getDisplayMode());
71 setColorMode(vdp.getColorMode());
72 resetPalette();
73}
74
75template<std::unsigned_integral Pixel>
77{
78 const V9990DisplayPeriod& horTiming = vdp.getHorizontalTiming();
79 const V9990DisplayPeriod& verTiming = vdp.getVerticalTiming();
80
81 // Center image on the window.
82
83 // In SDLLo, one window pixel represents 8 UC clock ticks, so the
84 // window = 320 * 8 UC ticks wide. In SDLHi, one pixel is 4 clock-
85 // ticks and the window 640 pixels wide -- same amount of UC ticks.
86 colZero = horTiming.blank + horTiming.border1 +
87 (horTiming.display - SCREEN_WIDTH * 8) / 2;
88
89 // 240 display lines can be shown. In SDLHi, we can do interlace,
90 // but still 240 lines per frame.
91 lineRenderTop = verTiming.blank + verTiming.border1 +
92 (verTiming.display - SCREEN_HEIGHT) / 2;
93}
94
95template<std::unsigned_integral Pixel>
96void V9990SDLRasterizer<Pixel>::frameEnd(EmuTime::param time)
97{
98 workFrame = postProcessor->rotateFrames(std::move(workFrame), time);
99 workFrame->init(
100 vdp.isInterlaced() ? (vdp.getEvenOdd() ? RawFrame::FIELD_EVEN
103}
104
105template<std::unsigned_integral Pixel>
107{
108 displayMode = mode;
109 bitmapConverter.setColorMode(colorMode, displayMode);
110}
111
112template<std::unsigned_integral Pixel>
114{
115 colorMode = mode;
116 bitmapConverter.setColorMode(colorMode, displayMode);
117}
118
119template<std::unsigned_integral Pixel>
121 int fromX, int fromY, int limitX, int limitY)
122{
123 Pixel bgColor = vdp.isOverScan()
124 ? 0
125 : palette64[vdp.getBackDropColor() & 63];
126
127 int startY = std::max(fromY - lineRenderTop, 0);
128 int endY = std::min(limitY - lineRenderTop, 240);
129 if (startY >= endY) return;
130
131 if ((fromX == 0) && (limitX == V9990DisplayTiming::UC_TICKS_PER_LINE)) {
132 // optimization
133 for (auto y : xrange(startY, endY)) {
134 workFrame->setBlank(y, bgColor);
135 }
136 return;
137 }
138
139 static int const screenW = SCREEN_WIDTH * 8; // in ticks
140 int startX = std::max(0, V9990::UCtoX(fromX - colZero, displayMode));
141 int endX = V9990::UCtoX(
143 ? screenW : std::min(screenW, limitX - colZero), displayMode);
144 if (startX >= endX) return;
145
146 unsigned lineWidth = vdp.getLineWidth();
148 for (auto y : xrange(startY, endY)) {
149 memset(workFrame->getLineDirect<Pixel>(y).subspan(startX, size_t(endX - startX)),
150 bgColor);
151 workFrame->setLineWidth(y, lineWidth);
152 }
153}
154
155template<std::unsigned_integral Pixel>
157 int fromX, int fromY, int toX, int toY,
158 int displayX, int displayY, int displayYA, int displayYB)
159{
160 static int const screenW = SCREEN_WIDTH * 8;
161 static int const screenH = SCREEN_HEIGHT;
162
163 // from VDP coordinates to screen coordinates
164 fromX -= colZero;
165 toX -= colZero;
166 fromY -= lineRenderTop;
167 toY -= lineRenderTop;
168
169 // Clip to screen
170 if (fromX < 0) {
171 displayX -= fromX;
172 fromX = 0;
173 }
174 if (fromY < 0) {
175 displayY -= fromY;
176 displayYA -= fromY;
177 displayYB -= fromY;
178 fromY = 0;
179 }
180 if (toX > screenW) {
181 toX = screenW;
182 }
183 if (toY > screenH) {
184 toY = screenH;
185 }
186 fromX = V9990::UCtoX(fromX, displayMode);
187 toX = V9990::UCtoX(toX, displayMode);
188
189 if ((toX > fromX) && (toY > fromY)) {
190 bool drawSprites = vdp.spritesEnabled() &&
191 !renderSettings.getDisableSprites();
192
193 displayX = V9990::UCtoX(displayX, displayMode);
194 int displayWidth = toX - fromX;
195 int displayHeight = toY - fromY;
196
197 if (displayMode == P1) {
198 drawP1Mode(fromX, fromY, displayX,
199 displayY, displayYA, displayYB,
200 displayWidth, displayHeight,
201 drawSprites);
202 } else if (displayMode == P2) {
203 drawP2Mode(fromX, fromY, displayX,
204 displayY, displayYA,
205 displayWidth, displayHeight,
206 drawSprites);
207 } else {
208 drawBxMode(fromX, fromY, displayX,
209 displayY, displayYA,
210 displayWidth, displayHeight,
211 drawSprites);
212 }
213 }
214}
215
216template<std::unsigned_integral Pixel>
218 int fromX, int fromY, int displayX,
219 int displayY, int displayYA, int displayYB,
220 int displayWidth, int displayHeight, bool drawSprites)
221{
222 while (displayHeight--) {
223 auto dst = workFrame->getLineDirect<Pixel>(fromY).subspan(fromX, displayWidth);
224 p1Converter.convertLine(dst, displayX, displayY,
225 displayYA, displayYB, drawSprites);
226 workFrame->setLineWidth(fromY, 320);
227 ++fromY;
228 ++displayY;
229 ++displayYA;
230 ++displayYB;
231 }
232}
233
234template<std::unsigned_integral Pixel>
236 int fromX, int fromY, int displayX, int displayY, int displayYA,
237 int displayWidth, int displayHeight, bool drawSprites)
238{
239 while (displayHeight--) {
240 auto dst = workFrame->getLineDirect<Pixel>(fromY).subspan(fromX, displayWidth);
241 p2Converter.convertLine(dst, displayX, displayY, displayYA, drawSprites);
242 workFrame->setLineWidth(fromY, 640);
243 ++fromY;
244 ++displayY;
245 ++displayYA;
246 }
247}
248
249template<std::unsigned_integral Pixel>
251 int fromX, int fromY, int displayX, int displayY, int displayYA,
252 int displayWidth, int displayHeight, bool drawSprites)
253{
254 unsigned scrollX = vdp.getScrollAX();
255 unsigned x = displayX + scrollX;
256
257 int lineStep = 1;
258 if (vdp.isEvenOddEnabled()) {
259 displayYA *= 2;
260 if (vdp.getEvenOdd()) {
261 ++displayY;
262 ++displayYA;
263 }
264 lineStep = 2;
265 }
266
267 unsigned scrollY = vdp.getScrollAY();
268 unsigned rollMask = vdp.getRollMask(0x1FFF);
269 unsigned scrollYBase = scrollY & ~rollMask & 0x1FFF;
270 int cursorY = displayY - vdp.getCursorYOffset();
271 while (displayHeight--) {
272 // Note: convertLine() can draw up to 3 pixels too many. But
273 // that's ok, the buffer is big enough: buffer can hold 1280
274 // pixels, max displayWidth is 1024 pixels. When taking the
275 // position of the borders into account, the display area
276 // plus 3 pixels cannot go beyond the end of the buffer.
277 unsigned y = scrollYBase + ((displayYA + scrollY) & rollMask);
278 auto dst = workFrame->getLineDirect<Pixel>(fromY).subspan(fromX, displayWidth);
279 bitmapConverter.convertLine(dst, x, y, cursorY, drawSprites);
280 workFrame->setLineWidth(fromY, vdp.getLineWidth());
281 ++fromY;
282 displayYA += lineStep;
283 cursorY += lineStep;
284 }
285}
286
287
288template<std::unsigned_integral Pixel>
290{
291 // the 32768 color palette
292 if (renderSettings.isColorMatrixIdentity()) {
293 // Most users use the "normal" monitor type; making this a
294 // special case speeds up palette precalculation a lot.
295 std::array<int, 32> intensity;
296 for (auto [i, r] : enumerate(intensity)) {
297 r = narrow_cast<int>(255.0f * renderSettings.transformComponent(narrow<float>(i) / 31.0f));
298 }
299 for (auto [grb, col] : enumerate(palette32768)) {
300 col = screen.mapKeyedRGB255<Pixel>(gl::ivec3(
301 intensity[(grb >> 5) & 31],
302 intensity[(grb >> 10) & 31],
303 intensity[(grb >> 0) & 31]));
304 }
305 } else {
306 for (auto g : xrange(32)) {
307 for (auto r : xrange(32)) {
308 for (auto b : xrange(32)) {
309 gl::vec3 rgb{narrow<float>(r),
310 narrow<float>(g),
311 narrow<float>(b)};
312 palette32768[(g << 10) + (r << 5) + b] = Pixel(
313 screen.mapRGB(renderSettings.transformRGB(rgb / 31.0f)));
314 }
315 }
316 }
317 }
318
319 // the 256 color palette
320 std::array<int, 8> mapRG = {0, 4, 9, 13, 18, 22, 27, 31};
321 std::array<int, 4> mapB = {0, 11, 21, 31};
322 for (auto g : xrange(8)) {
323 for (auto r : xrange(8)) {
324 for (auto b : xrange(4)) {
325 auto idx256 = (g << 5) | (r << 2) | b;
326 auto idx32768 = (mapRG[g] << 10) | (mapRG[r] << 5) | mapB[b];
327 palette256_32768[idx256] = narrow<int16_t>(idx32768);
328 palette256[idx256] = palette32768[idx32768];
329 }
330 }
331 }
332
333 resetPalette();
334}
335
336template<std::unsigned_integral Pixel>
338 byte r, byte g, byte b, bool ys)
339{
340 auto idx32768 = ((g & 31) << 10) | ((r & 31) << 5) | ((b & 31) << 0);
341 palette64_32768[index & 63] = narrow<int16_t>(idx32768); // TODO what with ys?
342 palette64[index & 63] = ys ? screen.getKeyColor<Pixel>()
343 : palette32768[idx32768];
344}
345
346template<std::unsigned_integral Pixel>
348{
349 // get 64 color palette from VDP
350 for (auto i : xrange(64)) {
351 auto [r, g, b, ys] = vdp.getPalette(i);
352 setPalette(i, r, g, b, ys);
353 }
354 palette256[0] = vdp.isSuperimposing() ? screen.getKeyColor<Pixel>()
355 : palette32768[0];
356 // TODO what with palette256_32768[0]?
357}
358
359template<std::unsigned_integral Pixel>
361{
362 resetPalette();
363}
364
365template<std::unsigned_integral Pixel>
367{
368 return postProcessor->isRecording();
369}
370
371template<std::unsigned_integral Pixel>
373{
374 if (&setting == one_of(&renderSettings.getGammaSetting(),
375 &renderSettings.getBrightnessSetting(),
376 &renderSettings.getContrastSetting(),
377 &renderSettings.getColorMatrixSetting())) {
378 preCalcPalettes();
379 }
380}
381
382// Force template instantiation.
383#if HAVE_16BPP
384template class V9990SDLRasterizer<uint16_t>;
385#endif
386#if HAVE_32BPP || COMPONENT_GL
387template class V9990SDLRasterizer<uint32_t>;
388#endif
389
390} // namespace openmsx
BaseSetting * setting
Definition: Interpreter.cc:28
int g
Definition: one_of.hh:7
Represents the output window/screen of openMSX.
Definition: Display.hh:33
@ FIELD_NONINTERLACED
Interlacing is off for this frame.
Definition: FrameSource.hh:27
@ FIELD_EVEN
Interlacing is on and this is an even frame.
Definition: FrameSource.hh:30
@ FIELD_ODD
Interlacing is on and this is an odd frame.
Definition: FrameSource.hh:33
A frame buffer where pixels can be written to.
Abstract base class for post processors.
A video frame as output by the VDP scanline conversion unit, before any postprocessing filters are ap...
Definition: RawFrame.hh:15
FloatSetting & getContrastSetting()
Contrast video setting.
FloatSetting & getBrightnessSetting()
Brightness video setting.
FloatSetting & getGammaSetting()
The amount of gamma correction.
StringSetting & getColorMatrixSetting()
Color matrix setting.
void attach(Observer< T > &observer)
Definition: Subject.hh:50
static constexpr int UC_TICKS_PER_LINE
The number of clock ticks per line is independent of the crystal used or the display mode (NTSC/PAL)
void drawP1Mode(int fromX, int fromY, int displayX, int displayY, int displayYA, int displayYB, int displayWidth, int displayHeight, bool drawSprites)
void frameStart() override
Indicates the start of a new frame.
void setPalette(int index, byte r, byte g, byte b, bool ys) override
Set RGB values for a palette entry.
void reset() override
Resynchronize with VDP - flush caches etc.
void setSuperimpose(bool enabled) override
Is superimpose enabled?
bool isActive() override
Will the output of this Rasterizer be displayed? There is no point in producing a frame that will not...
bool isRecording() const override
Is video recording active?
void drawBxMode(int fromX, int fromY, int displayX, int displayY, int displayYA, int displayWidth, int displayHeight, bool drawSprites)
void drawBorder(int fromX, int fromY, int limitX, int limitY) override
Render a rectangle of border pixels on the host screen.
void drawDisplay(int fromX, int fromY, int toX, int toY, int displayX, int displayY, int displayYA, int displayYB) override
Render a rectangle of display pixels on the host screen.
void drawP2Mode(int fromX, int fromY, int displayX, int displayY, int displayYA, int displayWidth, int displayHeight, bool drawSprites)
PostProcessor * getPostProcessor() const override
See V9990::getPostProcessor().
void setDisplayMode(V9990DisplayMode displayMode) override
The display mode determines the screens geometry and how V9990 pixels are mapped to pixels on screen.
void update(const Setting &setting) noexcept override
void preCalcPalettes()
Fill the palettes.
void frameEnd(EmuTime::param time) override
Indicates the end of the current frame.
void setColorMode(V9990ColorMode colorMode) override
The color mode determines how the V9990 VRAM data gets converted to pixel colors.
V9990SDLRasterizer(V9990 &vdp, Display &display, OutputSurface &screen, std::unique_ptr< PostProcessor > postProcessor)
Implementation of the Yamaha V9990 VDP as used in the GFX9000 cartridge by Sunrise.
Definition: V9990.hh:35
static int UCtoX(int ticks, V9990DisplayMode mode)
Convert UC ticks to V9990 pixel position on a line.
Definition: V9990.hh:174
constexpr auto enumerate(Iterable &&iterable)
Heavily inspired by Nathan Reed's blog post: Python-Like enumerate() In C++17 http://reedbeta....
Definition: enumerate.hh:28
vecN< 3, int > ivec3
Definition: gl_vec.hh:154
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:267
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:285
This file implemented 3 utility functions:
Definition: Autofire.cc:9
uint32_t Pixel
STL namespace.
constexpr auto subspan(Range &&range, size_t offset, size_t count=std::dynamic_extent)
Definition: ranges.hh:446
A period, either horizontal or vertical, starts with a synchronisation pulse followed by a blank peri...
constexpr auto xrange(T e)
Definition: xrange.hh:132