1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This _program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This _program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this _program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
20  * USA.
21  *
22  */
23 
24 #include "backends/platform/3ds/osystem.h"
25 #include "backends/platform/3ds/shader_shbin.h"
26 #include "backends/platform/3ds/options-dialog.h"
27 #include "backends/platform/3ds/config.h"
28 #include "common/rect.h"
29 #include "graphics/conversion.h"
30 #include "graphics/fontman.h"
31 #include "gui/gui-manager.h"
32 
33 // Used to transfer the final rendered display to the framebuffer
34 #define DISPLAY_TRANSFER_FLAGS                                                    \
35 		(GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(0) |                    \
36 		 GX_TRANSFER_RAW_COPY(0) | GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | \
37 		 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) |                           \
38 		 GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO))
39 #define TEXTURE_TRANSFER_FLAGS(fmt)                             \
40 		(GX_TRANSFER_FLIP_VERT(1) | GX_TRANSFER_OUT_TILED(1) |  \
41 		 GX_TRANSFER_RAW_COPY(0) | GX_TRANSFER_IN_FORMAT(fmt) | \
42 		 GX_TRANSFER_OUT_FORMAT(fmt) | GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO))
43 #define DEFAULT_MODE _modeRGBA8
44 
45 namespace N3DS {
46 /* Group the various enums, values, etc. needed for
47  * each graphics mode into instaces of GfxMode3DS */
48 static const GfxMode3DS _modeRGBA8 = { Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0),
49 									   GPU_RGBA8, TEXTURE_TRANSFER_FLAGS(GX_TRANSFER_FMT_RGBA8) };
50 static const GfxMode3DS _modeRGB565 = { Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0),
51 										GPU_RGB565, TEXTURE_TRANSFER_FLAGS(GX_TRANSFER_FMT_RGB565) };
52 static const GfxMode3DS _modeRGB555 = { Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0),
53 										GPU_RGBA5551, TEXTURE_TRANSFER_FLAGS(GX_TRANSFER_FMT_RGB5A1) };
54 static const GfxMode3DS _modeRGB5A1 = { Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0),
55 										GPU_RGBA5551, TEXTURE_TRANSFER_FLAGS(GX_TRANSFER_FMT_RGB5A1) };
56 static const GfxMode3DS _modeCLUT8 = _modeRGBA8;
57 
58 static const GfxMode3DS *gfxModes[] = { &_modeRGBA8, &_modeRGB565, &_modeRGB555, &_modeRGB5A1, &_modeCLUT8 };
59 
60 
init3DSGraphics()61 void OSystem_3DS::init3DSGraphics() {
62 	_gfxState.gfxMode = gfxModes[CLUT8];
63 	_pfGame = Graphics::PixelFormat::createFormatCLUT8();
64 	_pfDefaultTexture = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0);
65 
66 	C3D_Init(C3D_DEFAULT_CMDBUF_SIZE);
67 
68 	// Initialize the render targets
69 
70 	int topScreenWidth = gfxIsWide() ? 800 : 400;
71 
72 	_renderTargetTop =
73 	    C3D_RenderTargetCreate(240, topScreenWidth, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8);
74 	C3D_RenderTargetClear(_renderTargetTop, C3D_CLEAR_ALL, 0x0000000, 0);
75 	C3D_RenderTargetSetOutput(_renderTargetTop, GFX_TOP, GFX_LEFT,
76 	                          DISPLAY_TRANSFER_FLAGS);
77 
78 	_renderTargetBottom =
79 	    C3D_RenderTargetCreate(240, 320, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8);
80 	C3D_RenderTargetClear(_renderTargetBottom, C3D_CLEAR_ALL, 0x00000000, 0);
81 	C3D_RenderTargetSetOutput(_renderTargetBottom, GFX_BOTTOM, GFX_LEFT,
82 	                          DISPLAY_TRANSFER_FLAGS);
83 
84 	// Load and bind simple default shader (shader.v.pica)
85 	_dvlb = DVLB_ParseFile((u32*)shader_shbin, shader_shbin_size);
86 	shaderProgramInit(&_program);
87 	shaderProgramSetVsh(&_program, &_dvlb->DVLE[0]);
88 	C3D_BindProgram(&_program);
89 
90 	_projectionLocation = shaderInstanceGetUniformLocation(_program.vertexShader, "projection");
91 	_modelviewLocation = shaderInstanceGetUniformLocation(_program.vertexShader, "modelView");
92 
93 	C3D_AttrInfo *attrInfo = C3D_GetAttrInfo();
94 	AttrInfo_Init(attrInfo);
95 	AttrInfo_AddLoader(attrInfo, 0, GPU_FLOAT, 3); // v0=position
96 	AttrInfo_AddLoader(attrInfo, 1, GPU_FLOAT, 2); // v1=texcoord
97 
98 	Mtx_OrthoTilt(&_projectionTop, 0.0, 400.0, 240.0, 0.0, 0.0, 1.0, true);
99 	Mtx_OrthoTilt(&_projectionBottom, 0.0, 320.0, 240.0, 0.0, 0.0, 1.0, true);
100 
101 	C3D_TexEnv *env = C3D_GetTexEnv(0);
102 	C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR);
103 	C3D_TexEnvOpRgb(env, GPU_TEVOP_RGB_SRC_COLOR, GPU_TEVOP_RGB_SRC_COLOR, GPU_TEVOP_RGB_SRC_COLOR);
104 	C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE);
105 
106 	C3D_DepthTest(false, GPU_GEQUAL, GPU_WRITE_ALL);
107 	C3D_CullFace(GPU_CULL_NONE);
108 }
109 
destroy3DSGraphics()110 void OSystem_3DS::destroy3DSGraphics() {
111 	_gameScreen.free();
112 	_gameTopTexture.free();
113 	_gameBottomTexture.free();
114 	_cursor.free();
115 	_cursorTexture.free();
116 	_overlay.free();
117 	_activityIcon.free();
118 
119 	shaderProgramFree(&_program);
120 	DVLB_Free(_dvlb);
121 
122 	C3D_RenderTargetDelete(_renderTargetTop);
123 	C3D_RenderTargetDelete(_renderTargetBottom);
124 
125 	C3D_Fini();
126 }
127 
hasFeature(OSystem::Feature f)128 bool OSystem_3DS::hasFeature(OSystem::Feature f) {
129 	return (f == OSystem::kFeatureCursorPalette ||
130 	        f == OSystem::kFeatureFilteringMode ||
131 	        f == OSystem::kFeatureOverlaySupportsAlpha ||
132 	        f == OSystem::kFeatureKbdMouseSpeed ||
133 	        f == OSystem::kFeatureJoystickDeadzone);
134 }
135 
setFeatureState(OSystem::Feature f,bool enable)136 void OSystem_3DS::setFeatureState(OSystem::Feature f, bool enable) {
137 	switch (f) {
138 	case OSystem::kFeatureCursorPalette:
139 		_cursorPaletteEnabled = enable;
140 		flushCursor();
141 		break;
142 	case OSystem::kFeatureFilteringMode:
143 		_filteringEnabled = enable;
144 		break;
145 	default:
146 		break;
147 	}
148 }
149 
getFeatureState(OSystem::Feature f)150 bool OSystem_3DS::getFeatureState(OSystem::Feature f) {
151 	switch (f) {
152 	case OSystem::kFeatureCursorPalette:
153 		return _cursorPaletteEnabled;
154 	case OSystem::kFeatureFilteringMode:
155 		return _filteringEnabled;
156 	default:
157 		return false;
158 	}
159 }
160 
chooseMode(Graphics::PixelFormat * format)161 GraphicsModeID OSystem_3DS::chooseMode(Graphics::PixelFormat *format) {
162 	if (format->bytesPerPixel > 2) {
163 		return RGBA8;
164 	} else if (format->bytesPerPixel > 1) {
165 		if (format->gBits() > 5) {
166 			return RGB565;
167 		} else if (format->aBits() == 0) {
168 			return RGB555;
169 		} else {
170 			return RGB5A1;
171 		}
172 	}
173 	return CLUT8;
174 }
175 
setGraphicsMode(GraphicsModeID modeID)176 bool OSystem_3DS::setGraphicsMode(GraphicsModeID modeID) {
177 	switch (modeID) {
178 	case RGBA8:
179 	case RGB565:
180 	case RGB555:
181 	case RGB5A1:
182 	case CLUT8:
183 		_gfxState.gfxMode = gfxModes[modeID];
184 		return true;
185 	default:
186 		return false;
187 	}
188 }
189 
initSize(uint width,uint height,const Graphics::PixelFormat * format)190 void OSystem_3DS::initSize(uint width, uint height,
191 						   const Graphics::PixelFormat *format) {
192 	debug("3ds initsize w:%d h:%d", width, height);
193 	int oldScreen = config.screen;
194 	loadConfig();
195 	if (config.screen != oldScreen) {
196 		_screenChangeId++;
197 	}
198 
199 	_gameWidth = width;
200 	_gameHeight = height;
201 	_magCenterX = _magWidth / 2;
202 	_magCenterY = _magHeight / 2;
203 
204 	_oldPfGame = _pfGame;
205 	if (!format) {
206 		_pfGame = Graphics::PixelFormat::createFormatCLUT8();
207 	} else {
208 		debug("pixelformat: %d %d %d %d %d", format->bytesPerPixel, format->rBits(), format->gBits(), format->bBits(), format->aBits());
209 		_pfGame = *format;
210 	}
211 
212 	/* If the current graphics mode does not fit with the pixel
213 	 * format being requested, choose one that does and switch to it */
214 	assert(_pfGame.bytesPerPixel > 0);
215 	if (_pfGame != _oldPfGame) {
216 		assert(_transactionState == kTransactionActive);
217 		_gfxState.gfxModeID = chooseMode(&_pfGame);
218 		_transactionDetails.formatChanged = true;
219 	}
220 
221 	_gameTopTexture.create(width, height, _gfxState.gfxMode);
222 	_overlay.create(400, 320, &DEFAULT_MODE);
223 	_gameScreen.create(width, height, _pfGame);
224 
225 	_focusDirty = true;
226 	_focusRect = Common::Rect(_gameWidth, _gameHeight);
227 
228 	updateSize();
229 }
230 
updateSize()231 void OSystem_3DS::updateSize() {
232 	if (config.stretchToFit) {
233 		_gameTopX = _gameTopY = _gameBottomX = _gameBottomY = 0;
234 		_gameTopTexture.setScale(400.f / _gameWidth, 240.f / _gameHeight);
235 		_gameBottomTexture.setScale(320.f / _gameWidth, 240.f / _gameHeight);
236 	} else {
237 		float ratio = static_cast<float>(_gameWidth) / _gameHeight;
238 
239 		if (ratio > 400.f / 240.f) {
240 			float r = 400.f / _gameWidth;
241 			_gameTopTexture.setScale(r, r);
242 			_gameTopX = 0;
243 			_gameTopY = (240.f / r - _gameHeight) / 2.f;
244 		} else {
245 			float r = 240.f / _gameHeight;
246 			_gameTopTexture.setScale(r, r);
247 			_gameTopY = 0;
248 			_gameTopX = (400.f / r - _gameWidth) / 2.f;
249 		}
250 		if (ratio > 320.f / 240.f) {
251 			float r = 320.f / _gameWidth;
252 			_gameBottomTexture.setScale(r, r);
253 			_gameBottomX = 0;
254 			_gameBottomY = (240.f / r - _gameHeight) / 2.f;
255 		} else {
256 			float r = 240.f / _gameHeight;
257 			_gameBottomTexture.setScale(r, r);
258 			_gameBottomY = 0;
259 			_gameBottomX = (320.f / r - _gameWidth) / 2.f;
260 		}
261 	}
262 	_gameTopTexture.setPosition(_gameTopX, _gameTopY);
263 	_gameBottomTexture.setPosition(_gameBottomX, _gameBottomY);
264 	_gameTopTexture.setOffset(0, 0);
265 	_gameBottomTexture.setOffset(0, 0);
266 	if (_overlayVisible) {
267 		_cursorTexture.setScale(1.f, 1.f);
268 	} else if (config.screen == kScreenTop) {
269 		_cursorTexture.setScale(_gameTopTexture.getScaleX(), _gameTopTexture.getScaleY());
270 	} else {
271 		_cursorTexture.setScale(_gameBottomTexture.getScaleX(), _gameBottomTexture.getScaleY());
272 	}
273 }
274 
getSupportedFormats() const275 Common::List<Graphics::PixelFormat> OSystem_3DS::getSupportedFormats() const {
276 	Common::List<Graphics::PixelFormat> list;
277 	list.push_back(Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)); // GPU_RGBA8
278 	list.push_back(Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); // GPU_RGB565
279 	list.push_back(Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)); // RGB555 (needed for FMTOWNS?)
280 	list.push_back(Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0)); // GPU_RGBA5551
281 	list.push_back(Graphics::PixelFormat::createFormatCLUT8());
282 	return list;
283 }
284 
beginGFXTransaction()285 void OSystem_3DS::beginGFXTransaction() {
286 	assert(_transactionState == kTransactionNone);
287 	_transactionState = kTransactionActive;
288 	_transactionDetails.formatChanged = false;
289 	_oldGfxState = _gfxState;
290 }
291 
endGFXTransaction()292 OSystem::TransactionError OSystem_3DS::endGFXTransaction() {
293 	int errors = OSystem::kTransactionSuccess;
294 
295 	assert(_transactionState != kTransactionNone);
296 	if (_transactionState == kTransactionRollback) {
297 		if (_gfxState.gfxModeID != _oldGfxState.gfxModeID) {
298 			errors |= OSystem::kTransactionModeSwitchFailed;
299 			_gfxState = _oldGfxState;
300 		} else if ((_gfxState.gfxMode != _oldGfxState.gfxMode) |
301 		           (_gfxState.gfxMode != gfxModes[_gfxState.gfxModeID])) {
302 			errors |= OSystem::kTransactionFormatNotSupported;
303 			_gfxState = _oldGfxState;
304 		}
305 
306 		_oldGfxState.setup = false;
307 	}
308 	if (_transactionDetails.formatChanged) {
309 		if (!setGraphicsMode(_gfxState.gfxModeID)) {
310 			if (_oldGfxState.setup) {
311 				_transactionState = kTransactionRollback;
312 				errors |= endGFXTransaction();
313 			}
314 		} else if (_gfxState.gfxMode != gfxModes[_gfxState.gfxModeID]) {
315 			if (_oldGfxState.setup) {
316 				_transactionState = kTransactionRollback;
317 				errors |= endGFXTransaction();
318 			}
319 		} else {
320 			initSize(_gameWidth, _gameHeight, &_pfGame);
321 			clearOverlay();
322 			_gfxState.setup = true;
323 			_screenChangeId++;
324 		}
325 	}
326 
327 	_transactionState = kTransactionNone;
328 	return (OSystem::TransactionError)errors;
329 }
330 
getScaleRatio() const331 float OSystem_3DS::getScaleRatio() const {
332 	if (_overlayVisible) {
333 		return 1.0;
334 	} else if (config.screen == kScreenTop) {
335 		return _gameTopTexture.getScaleX();
336 	} else {
337 		return _gameBottomTexture.getScaleX();
338 	}
339 }
340 
setPalette(const byte * colors,uint start,uint num)341 void OSystem_3DS::setPalette(const byte *colors, uint start, uint num) {
342 	assert(start + num <= 256);
343 	memcpy(_palette + 3 * start, colors, 3 * num);
344 	_gameTextureDirty = true;
345 }
346 
grabPalette(byte * colors,uint start,uint num) const347 void OSystem_3DS::grabPalette(byte *colors, uint start, uint num) const {
348 	assert(start + num <= 256);
349 	memcpy(colors, _palette + 3 * start, 3 * num);
350 }
351 
copyRect555To5551(const Graphics::Surface & srcSurface,Graphics::Surface & destSurface,uint16 destX,uint16 destY,const Common::Rect & srcRect)352 static void copyRect555To5551(const Graphics::Surface &srcSurface, Graphics::Surface &destSurface, uint16 destX, uint16 destY, const Common::Rect &srcRect) {
353 	const uint16 *src = (const uint16 *)srcSurface.getBasePtr(srcRect.left, srcRect.top);
354 	uint16 *dst = (uint16 *)destSurface.getBasePtr(destX, destY);
355 	for (int i = 0; i < srcRect.height(); i++) {
356 		for (int j = 0; j < srcRect.width(); j++) {
357 			*dst++ = (*src++ << 1) | 1;
358 		}
359 		src += srcSurface.pitch / 2 - srcRect.width();
360 		dst += destSurface.pitch / 2 - srcRect.width();
361 	}
362 }
363 
copyRectToScreen(const void * buf,int pitch,int x,int y,int w,int h)364 void OSystem_3DS::copyRectToScreen(const void *buf, int pitch, int x,
365 								   int y, int w, int h) {
366 	Common::Rect rect(x, y, x+w, y+h);
367 	_gameScreen.copyRectToSurface(buf, pitch, x, y, w, h);
368 	Graphics::Surface subSurface = _gameScreen.getSubArea(rect);
369 
370 	if (_pfGame == _gameTopTexture.format) {
371 		_gameTopTexture.copyRectToSurface(subSurface, x, y, Common::Rect(w, h));
372 	} else if (_gfxState.gfxMode == &_modeRGB555) {
373 		copyRect555To5551(subSurface, _gameTopTexture, x, y, Common::Rect(w, h));
374 	} else {
375 		Graphics::Surface *convertedSubSurface = subSurface.convertTo(_gameTopTexture.format, _palette);
376 		_gameTopTexture.copyRectToSurface(*convertedSubSurface, x, y, Common::Rect(w, h));
377 		convertedSubSurface->free();
378 		delete convertedSubSurface;
379 	}
380 
381 	_gameTopTexture.markDirty();
382 }
383 
flushGameScreen()384 void OSystem_3DS::flushGameScreen() {
385 	if (_pfGame == _gameTopTexture.format) {
386 		_gameTopTexture.copyRectToSurface(_gameScreen, 0, 0, Common::Rect(_gameScreen.w, _gameScreen.h));
387 	} else if (_gfxState.gfxMode == &_modeRGB555) {
388 		copyRect555To5551(_gameScreen, _gameTopTexture, 0, 0, Common::Rect(_gameScreen.w, _gameScreen.h));
389 	} else {
390 		Graphics::Surface *converted = _gameScreen.convertTo(_gameTopTexture.format, _palette);
391 		_gameTopTexture.copyRectToSurface(*converted, 0, 0, Common::Rect(converted->w, converted->h));
392 		converted->free();
393 		delete converted;
394 	}
395 
396 	_gameTopTexture.markDirty();
397 }
398 
lockScreen()399 Graphics::Surface *OSystem_3DS::lockScreen() {
400 	return &_gameScreen;
401 }
unlockScreen()402 void OSystem_3DS::unlockScreen() {
403 	_gameTextureDirty = true;
404 }
405 
updateScreen()406 void OSystem_3DS::updateScreen() {
407 	if (sleeping || exiting) {
408 		return;
409 	}
410 
411 	if (_gameTextureDirty) {
412 		flushGameScreen();
413 		_gameTextureDirty = false;
414 	}
415 
416 // 	updateFocus();
417 	updateMagnify();
418 
419 	if (_osdMessage.getPixels() && _osdMessageEndTime <= getMillis(true)) {
420 		_osdMessage.free();
421 	}
422 
423 	C3D_FrameBegin(0);
424 		_gameTopTexture.transfer();
425 		if (_overlayVisible) {
426 			_overlay.transfer();
427 		}
428 		if (_cursorVisible && config.showCursor) {
429 			_cursorTexture.transfer();
430 		}
431 		_osdMessage.transfer();
432 		_activityIcon.transfer();
433 	C3D_FrameEnd(0);
434 
435 	C3D_FrameBegin(0);
436 		// Render top screen
437 		C3D_RenderTargetClear(_renderTargetTop, C3D_CLEAR_ALL, 0x00000000, 0);
438 		C3D_FrameDrawOn(_renderTargetTop);
439 		if (config.screen == kScreenTop || config.screen == kScreenBoth) {
440 			C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _projectionLocation, &_projectionTop);
441 			C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _gameTopTexture.getMatrix());
442 			_gameTopTexture.setFilteringMode(_magnifyMode != MODE_MAGON && _filteringEnabled);
443 			_gameTopTexture.render();
444 			if (_overlayVisible && config.screen == kScreenTop) {
445 				C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _overlay.getMatrix());
446 				_overlay.render();
447 			}
448 			if (_activityIcon.getPixels() && config.screen == kScreenTop) {
449 				_activityIcon.setPosition(400 - _activityIcon.actualWidth, 0);
450 				C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _activityIcon.getMatrix());
451 				_activityIcon.render();
452 			}
453 			if (_osdMessage.getPixels() && config.screen == kScreenTop) {
454 				_osdMessage.setPosition((400 - _osdMessage.actualWidth) / 2, (240 - _osdMessage.actualHeight) / 2);
455 				C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _osdMessage.getMatrix());
456 				_osdMessage.render();
457 			}
458 			if (_cursorVisible && config.showCursor && config.screen == kScreenTop) {
459 				C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _cursorTexture.getMatrix());
460 				_cursorTexture.setFilteringMode(!_overlayVisible && _filteringEnabled);
461 				_cursorTexture.render();
462 			}
463 		}
464 
465 		// Render bottom screen
466 		C3D_RenderTargetClear(_renderTargetBottom, C3D_CLEAR_ALL, 0x00000000, 0);
467 		C3D_FrameDrawOn(_renderTargetBottom);
468 		if (config.screen == kScreenBottom || config.screen == kScreenBoth) {
469 			C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _projectionLocation, &_projectionBottom);
470 			C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _gameBottomTexture.getMatrix());
471 			_gameTopTexture.setFilteringMode(_filteringEnabled);
472 			_gameTopTexture.render();
473 			if (_overlayVisible) {
474 				C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _overlay.getMatrix());
475 				_overlay.render();
476 			}
477 			if (_activityIcon.getPixels()) {
478 				_activityIcon.setPosition(320 - _activityIcon.actualWidth, 0);
479 				C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _activityIcon.getMatrix());
480 				_activityIcon.render();
481 			}
482 			if (_osdMessage.getPixels()) {
483 				_osdMessage.setPosition((320 - _osdMessage.actualWidth) / 2, (240 - _osdMessage.actualHeight) / 2);
484 				C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _osdMessage.getMatrix());
485 				_osdMessage.render();
486 			}
487 			if (_cursorVisible && config.showCursor) {
488 				C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, _modelviewLocation, _cursorTexture.getMatrix());
489 				_cursorTexture.setFilteringMode(!_overlayVisible && _filteringEnabled);
490 				_cursorTexture.render();
491 			}
492 		}
493 	C3D_FrameEnd(0);
494 }
495 
setShakePos(int shakeXOffset,int shakeYOffset)496 void OSystem_3DS::setShakePos(int shakeXOffset, int shakeYOffset) {
497 	// TODO: implement this in overlay, top screen, and mouse too
498 	_screenShakeXOffset = shakeXOffset;
499 	_screenShakeYOffset = shakeYOffset;
500 	int topX = _gameTopX + (_gameTopTexture.getScaleX() * shakeXOffset);
501 	int topY = _gameTopY + (_gameTopTexture.getScaleY() * shakeYOffset);
502 	_gameTopTexture.setPosition(topX, topY);
503 	int bottomX = _gameBottomX + (_gameBottomTexture.getScaleX() * shakeXOffset);
504 	int bottomY = _gameBottomY + (_gameBottomTexture.getScaleY() * shakeYOffset);
505 	_gameBottomTexture.setPosition(bottomX, bottomY);
506 }
507 
setFocusRectangle(const Common::Rect & rect)508 void OSystem_3DS::setFocusRectangle(const Common::Rect &rect) {
509 	debug("setfocus: %d %d %d %d", rect.left, rect.top, rect.width(), rect.height());
510 	_focusRect = rect;
511 	_focusDirty = true;
512 	_focusClearTime = 0;
513 }
514 
clearFocusRectangle()515 void OSystem_3DS::clearFocusRectangle() {
516 	_focusClearTime = getMillis();
517 }
518 
updateFocus()519 void OSystem_3DS::updateFocus() {
520 
521 	if (_focusClearTime && getMillis() - _focusClearTime > 5000) {
522 		_focusClearTime = 0;
523 		_focusDirty = true;
524 		_focusRect = Common::Rect(_gameWidth, _gameHeight);
525 	}
526 
527 	if (_focusDirty) {
528 		float duration = 1.f / 20.f; // Focus animation in frame duration
529 		float w = 400.f;
530 		float h = 240.f;
531 		float ratio = _focusRect.width() / _focusRect.height();
532 		if (ratio > w/h) {
533 			_focusTargetScaleX = w / _focusRect.width();
534 			float newHeight = (float)_focusRect.width() / w/h;
535 			_focusTargetScaleY = h / newHeight;
536 			_focusTargetPosX = _focusTargetScaleX * _focusRect.left;
537 			_focusTargetPosY = _focusTargetScaleY * ((float)_focusRect.top - (newHeight - _focusRect.height())/2.f);
538 		} else {
539 			_focusTargetScaleY = h / _focusRect.height();
540 			float newWidth = (float)_focusRect.height() * w/h;
541 			_focusTargetScaleX = w / newWidth;
542 			_focusTargetPosY = _focusTargetScaleY * _focusRect.top;
543 			_focusTargetPosX = _focusTargetScaleX * ((float)_focusRect.left - (newWidth - _focusRect.width())/2.f);
544 		}
545 		if (_focusTargetPosX < 0 && _focusTargetScaleY != 240.f / _gameHeight) {
546 			_focusTargetPosX = 0;
547 		}
548 		if (_focusTargetPosY < 0 && _focusTargetScaleX != 400.f / _gameWidth) {
549 			_focusTargetPosY = 0;
550 		}
551 		_focusStepPosX = duration * (_focusTargetPosX - _focusPosX);
552 		_focusStepPosY = duration * (_focusTargetPosY - _focusPosY);
553 		_focusStepScaleX = duration * (_focusTargetScaleX - _focusScaleX);
554 		_focusStepScaleY = duration * (_focusTargetScaleY - _focusScaleY);
555 	}
556 
557 	if (_focusDirty || _focusPosX != _focusTargetPosX || _focusPosY != _focusTargetPosY ||
558 	    _focusScaleX != _focusTargetScaleX || _focusScaleY != _focusTargetScaleY) {
559 		_focusDirty = false;
560 
561 		if ((_focusStepPosX > 0 && _focusPosX > _focusTargetPosX) || (_focusStepPosX < 0 && _focusPosX < _focusTargetPosX)) {
562 			_focusPosX = _focusTargetPosX;
563 		} else if (_focusPosX != _focusTargetPosX) {
564 			_focusPosX += _focusStepPosX;
565 		}
566 
567 		if ((_focusStepPosY > 0 && _focusPosY > _focusTargetPosY) || (_focusStepPosY < 0 && _focusPosY < _focusTargetPosY)) {
568 			_focusPosY = _focusTargetPosY;
569 		} else if (_focusPosY != _focusTargetPosY) {
570 			_focusPosY += _focusStepPosY;
571 		}
572 
573 		if ((_focusStepScaleX > 0 && _focusScaleX > _focusTargetScaleX) || (_focusStepScaleX < 0 && _focusScaleX < _focusTargetScaleX)) {
574 			_focusScaleX = _focusTargetScaleX;
575 		} else if (_focusScaleX != _focusTargetScaleX) {
576 			_focusScaleX += _focusStepScaleX;
577 		}
578 
579 		if ((_focusStepScaleY > 0 && _focusScaleY > _focusTargetScaleY) || (_focusStepScaleY < 0 && _focusScaleY < _focusTargetScaleY)) {
580 			_focusScaleY = _focusTargetScaleY;
581 		} else if (_focusScaleY != _focusTargetScaleY) {
582 			_focusScaleY += _focusStepScaleY;
583 		}
584 
585 		Mtx_Identity(&_focusMatrix);
586 		Mtx_Translate(&_focusMatrix, -_focusPosX, -_focusPosY, 0, true);
587 		Mtx_Scale(&_focusMatrix, _focusScaleX, _focusScaleY, 1.f);
588 	}
589 }
590 
updateMagnify()591 void OSystem_3DS::updateMagnify() {
592 	if (_magnifyMode == MODE_MAGON && config.screen != kScreenBoth) {
593 		// Only allow to magnify when both screens are enabled
594 		_magnifyMode = MODE_MAGOFF;
595 	}
596 
597 	if (_magnifyMode == MODE_MAGON) {
598 		if (!_overlayVisible) {
599 			_magX = (_cursorScreenX < _magCenterX) ?
600 			         0 : ((_cursorScreenX < (_gameWidth - _magCenterX)) ?
601 			         _cursorScreenX - _magCenterX : _gameWidth - _magWidth);
602 			_magY = (_cursorScreenY < _magCenterY) ?
603 			         0 : ((_cursorScreenY < _gameHeight - _magCenterY) ?
604 			         _cursorScreenY - _magCenterY : _gameHeight - _magHeight);
605 		}
606 		_gameTopTexture.setScale(1.f,1.f);
607 		_gameTopTexture.setPosition(0,0);
608 		_gameTopTexture.setOffset(_magX, _magY);
609 	}
610 }
611 
showOverlay()612 void OSystem_3DS::showOverlay() {
613 	_overlayVisible = true;
614 	updateSize();
615 }
616 
hideOverlay()617 void OSystem_3DS::hideOverlay() {
618 	_overlayVisible = false;
619 	updateSize();
620 }
621 
getOverlayFormat() const622 Graphics::PixelFormat OSystem_3DS::getOverlayFormat() const {
623 	return _overlay.format;
624 }
625 
clearOverlay()626 void OSystem_3DS::clearOverlay() {
627 	_overlay.clear();
628 }
629 
grabOverlay(Graphics::Surface & surface)630 void OSystem_3DS::grabOverlay(Graphics::Surface &surface) {
631 	assert(surface.w >= getOverlayWidth());
632 	assert(surface.h >= getOverlayHeight());
633 	assert(surface.format.bytesPerPixel == _overlay.format.bytesPerPixel);
634 
635 	byte *src = (byte *)_overlay.getPixels();
636 	byte *dst = (byte *)surface.getPixels();
637 	Graphics::copyBlit(dst, src, surface.pitch, _overlay.pitch,
638 		getOverlayWidth(), getOverlayHeight(), _overlay.format.bytesPerPixel);
639 }
640 
copyRectToOverlay(const void * buf,int pitch,int x,int y,int w,int h)641 void OSystem_3DS::copyRectToOverlay(const void *buf, int pitch, int x,
642 									int y, int w, int h) {
643 	_overlay.copyRectToSurface(buf, pitch, x, y, w, h);
644 	_overlay.markDirty();
645 }
646 
displayMessageOnOSD(const Common::U32String & msg)647 void OSystem_3DS::displayMessageOnOSD(const Common::U32String &msg) {
648 	// The font we are going to use:
649 	const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kLocalizedFont);
650 	if (!font) {
651 		warning("No available font to render OSD messages");
652 		return;
653 	}
654 
655 	// Split the message into separate lines.
656 	Common::Array<Common::U32String> lines;
657 	Common::U32String::const_iterator strLineItrBegin = msg.begin();
658 
659 	for (Common::U32String::const_iterator itr = msg.begin(); itr != msg.end(); itr++) {
660 		if (*itr == '\n') {
661 			lines.push_back(Common::U32String(strLineItrBegin, itr));
662 			strLineItrBegin = itr + 1;
663 		}
664 	}
665 	if (strLineItrBegin != msg.end())
666 		lines.push_back(Common::U32String(strLineItrBegin, msg.end()));
667 
668 	// Determine a rect which would contain the message string (clipped to the
669 	// screen dimensions).
670 	const int vOffset = 6;
671 	const int lineSpacing = 1;
672 	const int lineHeight = font->getFontHeight() + 2 * lineSpacing;
673 	int width = 0;
674 	int height = lineHeight * lines.size() + 2 * vOffset;
675 	uint i;
676 	for (i = 0; i < lines.size(); i++) {
677 		width = MAX(width, font->getStringWidth(lines[i]) + 14);
678 	}
679 
680 	// Clip the rect
681 	if (width > getOverlayWidth()) {
682 		width = getOverlayWidth();
683 	}
684 	if (height > getOverlayHeight()) {
685 		height = getOverlayHeight();
686 	}
687 
688 	_osdMessage.create(width, height, &DEFAULT_MODE);
689 	_osdMessage.fillRect(Common::Rect(width, height), _pfDefaultTexture.ARGBToColor(200, 0, 0, 0));
690 
691 	// Render the message, centered, and in white
692 	for (i = 0; i < lines.size(); i++) {
693 		font->drawString(&_osdMessage, lines[i],
694 		                 0, 0 + i * lineHeight + vOffset + lineSpacing, width,
695 		                 _pfDefaultTexture.RGBToColor(255, 255, 255),
696 		                 Graphics::kTextAlignCenter, 0, true);
697 	}
698 
699 	_osdMessageEndTime = getMillis(true) + kOSDMessageDuration;
700 }
701 
displayActivityIconOnOSD(const Graphics::Surface * icon)702 void OSystem_3DS::displayActivityIconOnOSD(const Graphics::Surface *icon) {
703 	if (!icon) {
704 		_activityIcon.free();
705 	} else {
706 		if (!_activityIcon.getPixels() || icon->w != _activityIcon.w || icon->h != _activityIcon.h) {
707 			_activityIcon.create(icon->w, icon->h, &DEFAULT_MODE);
708 		}
709 
710 		if (icon->format == _activityIcon.format) {
711 			_activityIcon.copyRectToSurface(*icon, 0, 0, Common::Rect(icon->w, icon->h));
712 		} else {
713 			Graphics::Surface *converted = icon->convertTo(_activityIcon.format);
714 			_activityIcon.copyRectToSurface(*converted, 0, 0, Common::Rect(converted->w, converted->h));
715 			converted->free();
716 			delete converted;
717 		}
718 
719 		_activityIcon.markDirty();
720 	}
721 }
722 
getOverlayHeight()723 int16 OSystem_3DS::getOverlayHeight() {
724 	return 240;
725 }
726 
getOverlayWidth()727 int16 OSystem_3DS::getOverlayWidth() {
728 	return config.screen == kScreenTop ? 400 : 320;
729 }
730 
showMouse(bool visible)731 bool OSystem_3DS::showMouse(bool visible) {
732 	_cursorVisible = visible;
733 	flushCursor();
734 	return !visible;
735 }
736 
warpMouse(int x,int y)737 void OSystem_3DS::warpMouse(int x, int y) {
738 	if (!_overlayVisible) {
739 		_cursorScreenX = x;
740 		_cursorScreenY = y;
741 	} else {
742 		_cursorOverlayX = x;
743 		_cursorOverlayY = y;
744 	}
745 
746 	// TODO: adjust for _cursorScalable ?
747 	x -= _cursorHotspotX;
748 	y -= _cursorHotspotY;
749 
750 	int offsetx = 0;
751 	int offsety = 0;
752 	if (!_overlayVisible) {
753 		offsetx = config.screen == kScreenTop ? _gameTopTexture.getPosX() : _gameBottomTexture.getPosX();
754 		offsety = config.screen == kScreenTop ? _gameTopTexture.getPosY() : _gameBottomTexture.getPosY();
755 	}
756 
757 	_cursorTexture.setPosition(x + offsetx, y + offsety);
758 }
759 
setCursorDelta(float deltaX,float deltaY)760 void OSystem_3DS::setCursorDelta(float deltaX, float deltaY) {
761 	_cursorDeltaX = deltaX;
762 	_cursorDeltaY = deltaY;
763 }
764 
setMouseCursor(const void * buf,uint w,uint h,int hotspotX,int hotspotY,uint32 keycolor,bool dontScale,const Graphics::PixelFormat * format)765 void OSystem_3DS::setMouseCursor(const void *buf, uint w, uint h,
766 								 int hotspotX, int hotspotY,
767 								 uint32 keycolor, bool dontScale,
768 								 const Graphics::PixelFormat *format) {
769 	_cursorScalable = !dontScale;
770 	_cursorHotspotX = hotspotX;
771 	_cursorHotspotY = hotspotY;
772 	_cursorKeyColor = keycolor;
773 	_pfCursor = !format ? Graphics::PixelFormat::createFormatCLUT8() : *format;
774 
775 	if (w != (uint)_cursor.w || h != (uint)_cursor.h || _cursor.format != _pfCursor) {
776 		_cursor.create(w, h, _pfCursor);
777 		_cursorTexture.create(w, h, &DEFAULT_MODE);
778 	}
779 
780 	if ( w != 0 && h != 0 ) {
781 		_cursor.copyRectToSurface(buf, w * _pfCursor.bytesPerPixel, 0, 0, w, h);
782 	}
783 
784 	flushCursor();
785 
786 	if (!_overlayVisible) {
787 		warpMouse(_cursorScreenX, _cursorScreenY);
788 	} else {
789 		warpMouse(_cursorOverlayX, _cursorOverlayY);
790 	}
791 }
792 
setCursorPalette(const byte * colors,uint start,uint num)793 void OSystem_3DS::setCursorPalette(const byte *colors, uint start, uint num) {
794 	assert(start + num <= 256);
795 	memcpy(_cursorPalette + 3 * start, colors, 3 * num);
796 	_cursorPaletteEnabled = true;
797 	flushCursor();
798 }
799 
800 namespace {
801 template<typename SrcColor>
applyKeyColor(Graphics::Surface * src,Graphics::Surface * dst,const SrcColor keyColor)802 void applyKeyColor(Graphics::Surface *src, Graphics::Surface *dst, const SrcColor keyColor) {
803 	assert(dst->format.bytesPerPixel == 4);
804 	assert((dst->w >= src->w) && (dst->h >= src->h));
805 
806 	for (uint y = 0; y < (uint)src->h; ++y) {
807 		SrcColor *srcPtr = (SrcColor *)src->getBasePtr(0, y);
808 		uint32 *dstPtr = (uint32 *)dst->getBasePtr(0, y);
809 
810 		for (uint x = 0; x < (uint)src->w; ++x) {
811 			const SrcColor color = *srcPtr++;
812 
813 			if (color == keyColor) {
814 				*dstPtr = 0;
815 			}
816 
817 			dstPtr++;
818 		}
819 	}
820 }
821 } // End of anonymous namespace
822 
flushCursor()823 void OSystem_3DS::flushCursor() {
824 	if (_cursor.getPixels()) {
825 		Graphics::Surface *converted = _cursor.convertTo(_cursorTexture.format, _cursorPaletteEnabled ? _cursorPalette : _palette);
826 		_cursorTexture.copyRectToSurface(*converted, 0, 0, Common::Rect(converted->w, converted->h));
827 		_cursorTexture.markDirty();
828 		converted->free();
829 		delete converted;
830 
831 		if (_pfCursor.bytesPerPixel == 1) {
832 			applyKeyColor<byte>(&_cursor, &_cursorTexture, _cursorKeyColor);
833 		} else if (_pfCursor.bytesPerPixel == 2) {
834 			applyKeyColor<uint16>(&_cursor, &_cursorTexture, _cursorKeyColor);
835 		} else if (_pfCursor.bytesPerPixel == 4) {
836 			applyKeyColor<uint32>(&_cursor, &_cursorTexture, _cursorKeyColor);
837 		}
838 	}
839 }
840 
841 } // namespace N3DS
842