1
// -*- Mode: C++; tab-width:2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
// vi:tw=80:et:ts=2:sts=2
4
// -----------------------------------------------------------------------
6
// This file is part of RLVM, a RealLive virtual machine clone.
8
// -----------------------------------------------------------------------
10
// Copyright (C) 2006, 2007 Elliot Glaysher
12
// This program is free software; you can redistribute it and/or modify
13
// it under the terms of the GNU General Public License as published by
14
// the Free Software Foundation; either version 3 of the License, or
15
// (at your option) any later version.
17
// This program is distributed in the hope that it will be useful,
18
// but WITHOUT ANY WARRANTY; without even the implied warranty of
19
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
// GNU General Public License for more details.
22
// You should have received a copy of the GNU General Public License
23
// along with this program; if not, write to the Free Software
24
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
26
// -----------------------------------------------------------------------
30
#include "Systems/SDL/SDLGraphicsSystem.hpp"
33
#include <SDL/SDL_opengl.h>
35
#include <boost/algorithm/string.hpp>
36
#include <boost/bind.hpp>
37
#include <boost/lexical_cast.hpp>
38
#include <boost/scoped_array.hpp>
39
#include <boost/scoped_ptr.hpp>
46
#if defined(__linux__)
47
#include <SDL/SDL_image.h>
50
#include "base/notification_source.h"
51
#include "MachineBase/RLMachine.hpp"
52
#include "Systems/Base/CGMTable.hpp"
53
#include "Systems/Base/Colour.hpp"
54
#include "Systems/Base/EventSystem.hpp"
55
#include "Systems/Base/GraphicsObject.hpp"
56
#include "Systems/Base/MouseCursor.hpp"
57
#include "Systems/Base/Renderable.hpp"
58
#include "Systems/Base/System.hpp"
59
#include "Systems/Base/SystemError.hpp"
60
#include "Systems/Base/TextSystem.hpp"
61
#include "Systems/Base/ToneCurve.hpp"
62
#include "Systems/SDL/SDLColourFilter.hpp"
63
#include "Systems/SDL/SDLEventSystem.hpp"
64
#include "Systems/SDL/SDLRenderToTextureSurface.hpp"
65
#include "Systems/SDL/SDLSurface.hpp"
66
#include "Systems/SDL/SDLUtils.hpp"
67
#include "Systems/SDL/Shaders.hpp"
68
#include "Systems/SDL/Texture.hpp"
69
#include "Utilities/Exception.hpp"
70
#include "Utilities/Graphics.hpp"
71
#include "Utilities/LazyArray.hpp"
72
#include "Utilities/StringUtilities.hpp"
73
#include "libReallive/gameexe.h"
74
#include "xclannad/file.h"
76
using namespace boost;
78
using namespace libReallive;
80
// -----------------------------------------------------------------------
82
// -----------------------------------------------------------------------
84
void SDLGraphicsSystem::setCursor(int cursor) {
85
GraphicsSystem::setCursor(cursor);
87
SDL_ShowCursor(useCustomCursor() ? SDL_DISABLE : SDL_ENABLE);
90
void SDLGraphicsSystem::beginFrame() {
91
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
92
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
95
glDisable(GL_DEPTH_TEST);
96
glDisable(GL_CULL_FACE);
97
glDisable(GL_LIGHTING);
100
glMatrixMode(GL_PROJECTION);
102
glOrtho(0.0, (GLdouble)screenSize().width(), (GLdouble)screenSize().height(),
106
glMatrixMode(GL_MODELVIEW);
110
// Full screen shaking moves where the origin is.
111
Point origin = GetScreenOrigin();
112
glTranslatef(origin.x(), origin.y(), 0);
115
void SDLGraphicsSystem::markScreenAsDirty(GraphicsUpdateType type) {
116
if (isResponsibleForUpdate() &&
117
screenUpdateMode() == SCREENUPDATEMODE_MANUAL &&
118
type == GUT_MOUSE_MOTION)
119
redraw_last_frame_ = true;
121
GraphicsSystem::markScreenAsDirty(type);
124
void SDLGraphicsSystem::endFrame() {
125
FinalRenderers::iterator it = renderer_begin();
126
FinalRenderers::iterator end = renderer_end();
127
for (; it != end; ++it) {
131
if (screenUpdateMode() == SCREENUPDATEMODE_MANUAL) {
132
// Copy the area behind the cursor to the temporary buffer (drivers differ:
133
// the contents of the back buffer is undefined after SDL_GL_SwapBuffers()
134
// and I've just been lucky that the Intel i810 and whatever my Mac machine
135
// has have been doing things that way.)
136
glBindTexture(GL_TEXTURE_2D, screen_contents_texture_);
137
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0,
138
screenSize().width(), screenSize().height());
139
screen_contents_texture_valid_ = true;
141
screen_contents_texture_valid_ = false;
148
SDL_GL_SwapBuffers();
152
void SDLGraphicsSystem::redrawLastFrame() {
153
// We won't redraw the screen between when the DrawManual() command is issued
154
// by the bytecode and the first refresh() is called since we need a valid
155
// copy of the screen to work with and we only snapshot the screen during
156
// DrawManual() mode.
157
if (screen_contents_texture_valid_) {
159
glBindTexture(GL_TEXTURE_2D, screen_contents_texture_);
162
int dx2 = screenSize().width();
164
int dy2 = screenSize().height();
166
float x_cord = dx2 / float(screen_tex_width_);
167
float y_cord = dy2 / float(screen_tex_height_);
169
glColor4ub(255, 255, 255, 255);
170
glTexCoord2f(0, y_cord);
171
glVertex2i(dx1, dy1);
172
glTexCoord2f(x_cord, y_cord);
173
glVertex2i(dx2, dy1);
174
glTexCoord2f(x_cord, 0);
175
glVertex2i(dx2, dy2);
177
glVertex2i(dx1, dy2);
186
SDL_GL_SwapBuffers();
191
void SDLGraphicsSystem::drawCursor() {
192
if (useCustomCursor()) {
193
boost::shared_ptr<MouseCursor> cursor;
194
if (static_cast<SDLEventSystem&>(system().event()).mouseInsideWindow())
195
cursor = currentCursor();
197
Point hotspot = cursorPos();
198
cursor->renderHotspotAt(hotspot);
203
boost::shared_ptr<Surface> SDLGraphicsSystem::endFrameToSurface() {
204
return boost::shared_ptr<Surface>(
205
new SDLRenderToTextureSurface(this, screenSize()));
208
// -----------------------------------------------------------------------
210
// -----------------------------------------------------------------------
212
SDLGraphicsSystem::SDLGraphicsSystem(System& system, Gameexe& gameexe)
213
: GraphicsSystem(system, gameexe), redraw_last_frame_(false),
214
display_data_in_titlebar_(false), time_of_last_titlebar_update_(0),
215
last_seen_number_(0), last_line_number_(0),
216
screen_contents_texture_valid_(false),
217
screen_tex_width_(0),
218
screen_tex_height_(0) {
219
haikei_.reset(new SDLSurface(this));
220
for (int i = 0; i < 16; ++i)
221
display_contexts_[i].reset(new SDLSurface(this));
223
setScreenSize(getScreenSize(gameexe));
224
Texture::SetScreenSize(screenSize());
227
std::string cp932caption = gameexe("CAPTION").to_string();
228
int name_enc = gameexe("NAME_ENC").to_int(0);
229
caption_title_ = cp932toUTF8(cp932caption, name_enc);
233
// Now we allocate the first two display contexts with equal size to
235
display_contexts_[0]->allocate(screenSize(), true);
236
display_contexts_[1]->allocate(screenSize());
240
#if defined(__linux__)
241
// We only set the icon on linux because OSX will use the icns file
242
// automatically and this doesn't look too awesome.
243
SDL_Surface* icon = IMG_Load("/usr/share/icons/hicolor/48x48/apps/rlvm.png");
245
SDL_SetColorKey(icon, SDL_SRCCOLORKEY, SDL_MapRGB(icon->format, 0, 0, 0) );
246
SDL_WM_SetIcon(icon, NULL);
247
SDL_FreeSurface(icon);
251
// When debug is set, display trace data in the titlebar
252
if (gameexe("MEMORY").exists()) {
253
display_data_in_titlebar_ = true;
256
SDL_ShowCursor(useCustomCursor() ? SDL_DISABLE : SDL_ENABLE);
259
NotificationType::FULLSCREEN_STATE_CHANGED,
260
Source<GraphicsSystem>(static_cast<GraphicsSystem*>(this)));
263
void SDLGraphicsSystem::setupVideo() {
264
// Let's get some video information.
265
const SDL_VideoInfo* info = SDL_GetVideoInfo();
266
SDL_WM_SetCaption("rlvm", "rlvm");
270
ss << "Video query failed: " << SDL_GetError();
271
throw SystemError(ss.str());
274
int bpp = info->vfmt->BitsPerPixel;
276
// the flags to pass to SDL_SetVideoMode
278
video_flags = SDL_OPENGL; // Enable OpenGL in SDL
279
video_flags |= SDL_GL_DOUBLEBUFFER; // Enable double buffering
280
video_flags |= SDL_SWSURFACE;
282
if (screenMode() == 0)
283
video_flags |= SDL_FULLSCREEN;
285
// Sets up OpenGL double buffering
286
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
287
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
288
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
289
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
291
// Set the video mode
292
if ((screen_ = SDL_SetVideoMode(
293
screenSize().width(), screenSize().height(), bpp, video_flags)) == 0 ) {
294
// This could happen for a variety of reasons,
295
// including DISPLAY not being set, the specified
296
// resolution not being available, etc.
298
ss << "Video mode set failed: " << SDL_GetError();
299
throw SystemError(ss.str());
303
GLenum err = glewInit();
304
if (GLEW_OK != err) {
306
oss << "Failed to initialize GLEW: " << glewGetErrorString(err);
307
throw SystemError(oss.str());
310
glEnable(GL_TEXTURE_2D);
311
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
313
// Enable Texture Mapping ( NEW )
314
glEnable(GL_TEXTURE_2D);
316
// Enable smooth shading
317
glShadeModel(GL_SMOOTH);
319
// Set the background black
320
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
322
// Depth buffer setup
325
// Enables Depth Testing
326
glEnable(GL_DEPTH_TEST);
330
// The Type Of Depth Test To Do
331
glDepthFunc(GL_LEQUAL);
333
// Really Nice Perspective Calculations
334
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
336
// Full Brightness, 50% Alpha ( NEW )
337
glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
339
// Create a small 32x32 texture for storing what's behind the mouse
341
glGenTextures(1, &screen_contents_texture_);
342
glBindTexture(GL_TEXTURE_2D, screen_contents_texture_);
343
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
344
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
345
screen_tex_width_ = SafeSize(screenSize().width());
346
screen_tex_height_ = SafeSize(screenSize().height());
347
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
348
screen_tex_width_, screen_tex_height_, 0, GL_RGB,
349
GL_UNSIGNED_BYTE, NULL);
354
SDLGraphicsSystem::~SDLGraphicsSystem() {
357
void SDLGraphicsSystem::executeGraphicsSystem(RLMachine& machine) {
358
// For now, nothing, but later, we need to put all code each cycle
360
if (isResponsibleForUpdate() && screenNeedsRefresh()) {
363
redraw_last_frame_ = false;
364
} else if (isResponsibleForUpdate() && redraw_last_frame_) {
366
redraw_last_frame_ = false;
370
int current_time = machine.system().event().getTicks();
371
if ((current_time - time_of_last_titlebar_update_) > 60) {
372
time_of_last_titlebar_update_ = current_time;
374
if (machine.sceneNumber() != last_seen_number_ ||
375
machine.lineNumber() != last_line_number_) {
376
last_seen_number_ = machine.sceneNumber();
377
last_line_number_ = machine.lineNumber();
382
GraphicsSystem::executeGraphicsSystem(machine);
385
void SDLGraphicsSystem::setWindowTitle() {
387
oss << caption_title_;
389
if (displaySubtitle() && subtitle_ != "") {
390
oss << ": " << subtitle_;
393
if (display_data_in_titlebar_) {
394
oss << " - (SEEN" << last_seen_number_ << ")(Line "
395
<< last_line_number_ << ")";
398
// PulseAudio allocates a string each time we set the title. Make sure we
399
// don't do this unnecessarily.
400
string new_caption = oss.str();
401
if (new_caption != currently_set_title_) {
402
SDL_WM_SetCaption(new_caption.c_str(), NULL);
403
currently_set_title_ = new_caption;
407
void SDLGraphicsSystem::Observe(NotificationType type,
408
const NotificationSource& source,
409
const NotificationDetails& details) {
413
void SDLGraphicsSystem::setWindowSubtitle(const std::string& cp932str,
415
// @todo Still not restoring title correctly!
416
subtitle_ = cp932toUTF8(cp932str, text_encoding);
418
GraphicsSystem::setWindowSubtitle(cp932str, text_encoding);
421
void SDLGraphicsSystem::setScreenMode(const int in) {
422
GraphicsSystem::setScreenMode(in);
427
void SDLGraphicsSystem::allocateDC(int dc, Size size) {
430
ss << "Invalid DC number \"" << dc
431
<< "\" in SDLGraphicsSystem::allocate_dc";
432
throw rlvm::Exception(ss.str());
435
// We can't reallocate the screen!
437
throw rlvm::Exception("Attempting to reallocate DC 0!");
439
// DC 1 is a special case and must always be at least the size of
442
SDL_Surface* dc0 = *(display_contexts_[0]);
443
if (size.width() < dc0->w)
444
size.setWidth(dc0->w);
445
if (size.height() < dc0->h)
446
size.setHeight(dc0->h);
449
// Allocate a new obj.
450
display_contexts_[dc]->allocate(size);
453
void SDLGraphicsSystem::setMinimumSizeForDC(int dc, Size size) {
454
if (display_contexts_[dc] == NULL || !display_contexts_[dc]->allocated()) {
455
allocateDC(dc, size);
457
Size current = display_contexts_[dc]->size();
458
if (current.width() < size.width() || current.height() < size.height()) {
459
// Make a new surface of the maximum size.
460
Size maxSize = current.sizeUnion(size);
462
boost::shared_ptr<SDLSurface> newdc(new SDLSurface(this));
463
newdc->allocate(maxSize);
465
display_contexts_[dc]->blitToSurface(
467
display_contexts_[dc]->rect(),
468
display_contexts_[dc]->rect());
470
display_contexts_[dc] = newdc;
475
void SDLGraphicsSystem::freeDC(int dc) {
477
throw rlvm::Exception("Attempt to deallocate DC[0]");
478
} else if (dc == 1) {
479
// DC[1] never gets freed; it only gets blanked
480
getDC(1)->fill(RGBAColour::Black());
482
display_contexts_[dc]->deallocate();
486
void SDLGraphicsSystem::verifySurfaceExists(int dc, const std::string& caller) {
489
ss << "Invalid DC number (" << dc << ") in " << caller;
490
throw rlvm::Exception(ss.str());
493
if (display_contexts_[dc] == NULL) {
495
ss << "Parameter DC[" << dc << "] not allocated in " << caller;
496
throw rlvm::Exception(ss.str());
500
void SDLGraphicsSystem::verifyDCAllocation(int dc, const std::string& caller) {
501
if (display_contexts_[dc] == NULL) {
503
ss << "Couldn't allocate DC[" << dc << "] in " << caller
504
<< ": " << SDL_GetError();
505
throw SystemError(ss.str());
509
// -----------------------------------------------------------------------
511
typedef enum { NO_MASK, ALPHA_MASK, COLOR_MASK} MaskType;
513
// Note to self: These describe the byte order IN THE RAW G00 DATA!
514
// These should NOT be switched to native byte order.
515
#define DefaultRmask 0xff0000
516
#define DefaultGmask 0xff00
517
#define DefaultBmask 0xff
518
#define DefaultAmask 0xff000000
519
#define DefaultBpp 32
521
static SDL_Surface* newSurfaceFromRGBAData(int w, int h, char* data,
522
MaskType with_mask) {
523
int amask = (with_mask == ALPHA_MASK) ? DefaultAmask : 0;
524
SDL_Surface* tmp = SDL_CreateRGBSurfaceFrom(
525
data, w, h, DefaultBpp, w*4, DefaultRmask, DefaultGmask,
526
DefaultBmask, amask);
528
// We now need to convert this surface to a format suitable for use across
529
// the rest of the program. We can't (regretfully) rely on
530
// SDL_DisplayFormat[Alpha] to decide on a format that we can send to OpenGL
531
// (see some Intel macs) so use convert surface to a pixel order our data
532
// correctly while still using the appropriate alpha flags. So use the above
533
// format with only the flags that would have been set by
534
// SDL_DisplayFormat[Alpha].
536
if (with_mask == ALPHA_MASK) {
537
flags = tmp->flags & (SDL_SRCALPHA | SDL_RLEACCELOK);
539
flags = tmp->flags & (SDL_SRCCOLORKEY | SDL_SRCALPHA | SDL_RLEACCELOK);
542
SDL_Surface* surf = SDL_ConvertSurface(tmp, tmp->format, flags);
543
SDL_FreeSurface(tmp);
547
// Helper function for load_surface_from_file; invoked in a stl loop.
548
static SDLSurface::GrpRect xclannadRegionToGrpRect(
549
const GRPCONV::REGION& region) {
550
SDLSurface::GrpRect rect;
551
rect.rect = Rect(Point(region.x1, region.y1),
552
Point(region.x2 + 1, region.y2 + 1));
553
rect.originX = region.origin_x;
554
rect.originY = region.origin_y;
558
boost::shared_ptr<const Surface> SDLGraphicsSystem::loadSurfaceFromFile(
559
const std::string& short_filename) {
560
boost::filesystem::path filename =
561
system().findFile(short_filename, IMAGE_FILETYPES);
562
if (filename.empty()) {
564
oss << "Could not find image file \"" << short_filename << "\".";
565
throw rlvm::Exception(oss.str());
568
// Glue code to allow my stuff to work with Jagarl's loader
569
FILE* file = fopen(filename.string().c_str(), "rb");
572
oss << "Could not open file: " << filename;
573
throw rlvm::Exception(oss.str());
576
fseek(file, 0, SEEK_END);
577
size_t size = ftell(file);
578
scoped_array<char> d(new char[size + 1]);
579
fseek(file, 0, SEEK_SET);
580
fread(d.get(), size, 1, file);
583
scoped_ptr<GRPCONV> conv(GRPCONV::AssignConverter(d.get(), size, "???"));
585
throw SystemError("Failure in GRPCONV.");
587
// do not free until SDL_FreeSurface() is called on the surface using it
588
char* mem = (char*)malloc(conv->Width() * conv->Height() * 4 + 1024);
590
if (conv->Read(mem)) {
591
MaskType is_mask = conv->IsMask() ? ALPHA_MASK : NO_MASK;
592
if (is_mask == ALPHA_MASK) {
593
int len = conv->Width()*conv->Height();
594
unsigned int* d = (unsigned int*)mem;
596
for (i = 0; i < len; i++) {
597
if ( (*d&0xff000000) != 0xff000000) break;
605
s = newSurfaceFromRGBAData(conv->Width(), conv->Height(), mem, is_mask);
609
// Grab the Type-2 information out of the converter or create one
610
// default region if none exist
611
vector<SDLSurface::GrpRect> region_table;
612
if (conv->region_table.size()) {
613
transform(conv->region_table.begin(), conv->region_table.end(),
614
back_inserter(region_table),
615
xclannadRegionToGrpRect);
617
SDLSurface::GrpRect rect;
618
rect.rect = Rect(Point(0, 0), Size(conv->Width(), conv->Height()));
621
region_table.push_back(rect);
624
boost::shared_ptr<Surface> surface_to_ret(
625
new SDLSurface(this, s, region_table));
626
// handle tone curve effect loading
627
if(short_filename.find("?") != short_filename.npos) {
628
string effect_no_str = short_filename.substr(short_filename.find("?") + 1);
629
int effect_no = boost::lexical_cast<int>(effect_no_str);
630
// the effect number is an index that goes from 10 to getEffectCount() * 10, so keep that in mind here
631
if((effect_no / 10) > globals().tone_curves.getEffectCount() || effect_no < 10) {
633
oss << "Tone curve index " << effect_no << " is invalid.";
634
throw rlvm::Exception(oss.str());
636
surface_to_ret.get()->toneCurve(globals().tone_curves.getEffect(effect_no / 10 - 1), Rect(Point(0, 0), Size(conv->Width(), conv->Height())));
639
return surface_to_ret;
642
boost::shared_ptr<Surface> SDLGraphicsSystem::getHaikei() {
643
if (haikei_->rawSurface() == NULL) {
644
haikei_->allocate(screenSize(), true);
650
boost::shared_ptr<Surface> SDLGraphicsSystem::getDC(int dc) {
651
verifySurfaceExists(dc, "SDLGraphicsSystem::get_dc");
653
// If requesting a DC that doesn't exist, allocate it first.
654
if (display_contexts_[dc]->rawSurface() == NULL)
655
allocateDC(dc, display_contexts_[0]->size());
657
return display_contexts_[dc];
660
boost::shared_ptr<Surface> SDLGraphicsSystem::buildSurface(const Size& size) {
661
return boost::shared_ptr<Surface>(new SDLSurface(this, size));
664
ColourFilter* SDLGraphicsSystem::BuildColourFiller() {
665
return new SDLColourFilter();
668
void SDLGraphicsSystem::reset() {
669
last_seen_number_ = 0;
670
last_line_number_ = 0;
672
GraphicsSystem::reset();