/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "ags/engine/gfx/gfxfilter_scummvm_renderer.h" #include "ags/engine/gfx/ali_3d_scummvm.h" #include "common/std/algorithm.h" #include "ags/engine/ac/sys_events.h" #include "ags/engine/gfx/gfxfilter_scummvm_renderer.h" #include "ags/engine/gfx/gfx_util.h" #include "ags/engine/platform/base/ags_platform_driver.h" #include "ags/engine/platform/base/sys_main.h" #include "ags/engine/ac/timer.h" #include "ags/ags.h" #include "ags/globals.h" namespace AGS3 { namespace AGS { namespace Engine { namespace ALSW { using namespace Shared; static RGB faded_out_palette[256]; // ---------------------------------------------------------------------------- // ScummVMRendererGraphicsDriver // ---------------------------------------------------------------------------- ScummVMRendererGraphicsDriver::ScummVMRendererGraphicsDriver() { _tint_red = 0; _tint_green = 0; _tint_blue = 0; virtualScreen = nullptr; _stageVirtualScreen = nullptr; } ScummVMRendererGraphicsDriver::~ScummVMRendererGraphicsDriver() { delete _screen; ScummVMRendererGraphicsDriver::UnInit(); } bool ScummVMRendererGraphicsDriver::IsModeSupported(const DisplayMode &mode) { if (mode.Width <= 0 || mode.Height <= 0 || mode.ColorDepth <= 0) { warning("Invalid resolution parameters: %d x %d x %d", mode.Width, mode.Height, mode.ColorDepth); return false; } Graphics::PixelFormat format; return ::AGS::g_vm->getPixelFormat(mode.ColorDepth, format); } int ScummVMRendererGraphicsDriver::GetDisplayDepthForNativeDepth(int native_color_depth) const { // TODO: check for device caps to know which depth is supported? if (native_color_depth > 8) return 32; return native_color_depth; } IGfxModeList *ScummVMRendererGraphicsDriver::GetSupportedModeList(int color_depth) { std::vector modes; sys_get_desktop_modes(modes, color_depth); if ((modes.size() == 0) && color_depth == 32) { // Pretend that 24-bit are 32-bit sys_get_desktop_modes(modes, 24); for (auto &m : modes) m.ColorDepth = 32; } return new ScummVMRendererGfxModeList(modes); } PGfxFilter ScummVMRendererGraphicsDriver::GetGraphicsFilter() const { return _filter; } void ScummVMRendererGraphicsDriver::SetGraphicsFilter(PSDLRenderFilter filter) { _filter = filter; OnSetFilter(); // TODO: support separate nearest and linear filters, initialize hint by calls to filter object // e.g like D3D and OGL filters act // SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); // SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); // make the scaled rendering look smoother. } void ScummVMRendererGraphicsDriver::SetTintMethod(TintMethod /*method*/) { // TODO: support new D3D-style tint method } bool ScummVMRendererGraphicsDriver::SetDisplayMode(const DisplayMode &mode) { ReleaseDisplayMode(); set_color_depth(mode.ColorDepth); if (_initGfxCallback != nullptr) _initGfxCallback(nullptr); if (!IsModeSupported(mode)) return false; _capsVsync = true; // reset vsync flag, allow to try setting again const int driver = GFX_SCUMMVM; if (set_gfx_mode(driver, mode.Width, mode.Height, mode.ColorDepth) != 0) return false; if (g_system->hasFeature(OSystem::kFeatureVSync)) { g_system->beginGFXTransaction(); g_system->setFeatureState(OSystem::kFeatureVSync, mode.Vsync); g_system->endGFXTransaction(); } else { _capsVsync = false; Debug::Printf(kDbgMsg_Warn, "WARNING: Vertical sync is not supported. Setting will be kept at driver default."); } OnInit(); OnModeSet(mode); return true; } void ScummVMRendererGraphicsDriver::UpdateDeviceScreen(const Size &screen_sz) { _mode.Width = screen_sz.Width; _mode.Height = screen_sz.Height; #if AGS_PLATFORM_OS_ANDROID SDL_RenderSetLogicalSize(_renderer, _mode.Width, _mode.Height); #endif } void ScummVMRendererGraphicsDriver::CreateVirtualScreen() { if (!IsNativeSizeValid()) return; DestroyVirtualScreen(); // Initialize virtual screen; size is equal to native resolution const int vscreen_w = _srcRect.GetWidth(); const int vscreen_h = _srcRect.GetHeight(); _origVirtualScreen.reset(new Bitmap(vscreen_w, vscreen_h, _srcColorDepth)); virtualScreen = _origVirtualScreen.get(); _stageVirtualScreen = virtualScreen; _lastTexPixels = nullptr; _lastTexPitch = -1; } void ScummVMRendererGraphicsDriver::DestroyVirtualScreen() { delete[] _fakeTexBitmap; // don't use destroy_bitmap(), because it's a fake structure _fakeTexBitmap = nullptr; _origVirtualScreen.reset(); virtualScreen = nullptr; _stageVirtualScreen = nullptr; } void ScummVMRendererGraphicsDriver::ReleaseDisplayMode() { OnModeReleased(); ClearDrawLists(); } bool ScummVMRendererGraphicsDriver::SetNativeResolution(const GraphicResolution &native_res) { OnSetNativeRes(native_res); CreateVirtualScreen(); return !_srcRect.IsEmpty(); } bool ScummVMRendererGraphicsDriver::SetRenderFrame(const Rect &dst_rect) { OnSetRenderFrame(dst_rect); return !_dstRect.IsEmpty(); } void ScummVMRendererGraphicsDriver::ClearRectangle(int /*x1*/, int /*y1*/, int /*x2*/, int /*y2*/, RGB * /*colorToUse*/) { // TODO: but maybe is not necessary, as we use SDL_Renderer with accelerated gfx here? // See SDL_RenderDrawRect } void ScummVMRendererGraphicsDriver::UnInit() { OnUnInit(); ReleaseDisplayMode(); DestroyVirtualScreen(); sys_window_destroy(); } bool ScummVMRendererGraphicsDriver::SupportsGammaControl() { return _hasGamma; } void ScummVMRendererGraphicsDriver::SetGamma(int newGamma) { if (!_hasGamma) { return; } #ifdef TODO uint16 gamma_red[256]; uint16 gamma_green[256]; uint16 gamma_blue[256]; for (int i = 0; i < 256; i++) { gamma_red[i] = MIN(((int)_defaultGammaRed[i] * newGamma) / 100, 0xffff); gamma_green[i] = MIN(((int)_defaultGammaGreen[i] * newGamma) / 100, 0xffff); gamma_blue[i] = MIN(((int)_defaultGammaBlue[i] * newGamma) / 100, 0xffff); } SDL_SetWindowGammaRamp(sys_get_window(), gamma_red, gamma_green, gamma_blue); _gamma = newGamma; #endif } bool ScummVMRendererGraphicsDriver::DoesSupportVsyncToggle() { return g_system->hasFeature(OSystem::kFeatureVSync); } bool ScummVMRendererGraphicsDriver::SetVsyncImpl(bool enabled, bool &vsync_res) { if (g_system->hasFeature(OSystem::kFeatureVSync)) { g_system->beginGFXTransaction(); g_system->setFeatureState(OSystem::kFeatureVSync, enabled); g_system->endGFXTransaction(); vsync_res = g_system->getFeatureState(OSystem::kFeatureVSync); if (!vsync_res) Debug::Printf(kDbgMsg_Warn, "Renderer: SetVsync (%d) failed", enabled); return vsync_res; } return false; } int ScummVMRendererGraphicsDriver::GetCompatibleBitmapFormat(int color_depth) { return color_depth; } IDriverDependantBitmap *ScummVMRendererGraphicsDriver::CreateDDB(int width, int height, int color_depth, bool opaque) { return new ALSoftwareBitmap(width, height, color_depth, opaque); } IDriverDependantBitmap *ScummVMRendererGraphicsDriver::CreateDDBFromBitmap(Bitmap *bitmap, bool has_alpha, bool opaque) { return new ALSoftwareBitmap(bitmap, has_alpha, opaque); } IDriverDependantBitmap *ScummVMRendererGraphicsDriver::CreateRenderTargetDDB(int width, int height, int color_depth, bool opaque) { return new ALSoftwareBitmap(width, height, color_depth, opaque); } void ScummVMRendererGraphicsDriver::UpdateDDBFromBitmap(IDriverDependantBitmap *bitmapToUpdate, Bitmap *bitmap, bool has_alpha) { ALSoftwareBitmap *alSwBmp = (ALSoftwareBitmap *)bitmapToUpdate; alSwBmp->_bmp = bitmap; alSwBmp->_hasAlpha = has_alpha; } void ScummVMRendererGraphicsDriver::DestroyDDB(IDriverDependantBitmap *bitmap) { delete (ALSoftwareBitmap *)bitmap; } void ScummVMRendererGraphicsDriver::InitSpriteBatch(size_t index, const SpriteBatchDesc &desc) { if (_spriteBatches.size() <= index) _spriteBatches.resize(index + 1); ALSpriteBatch &batch = _spriteBatches[index]; batch.ID = index; // Apply parent batch's settings, if preset; Rect viewport = desc.Viewport; SpriteTransform transform = desc.Transform; Bitmap *parent_surf = virtualScreen; if (desc.Parent != UINT32_MAX) { const auto &parent = _spriteBatches[desc.Parent]; if (parent.Surface) parent_surf = parent.Surface.get(); // NOTE: we prioritize parent's surface size as a dest viewport, // because parent may have a scheduled scaled blit. if (viewport.IsEmpty()) viewport = parent_surf ? RectWH(parent_surf->GetSize()) : RectWH(parent.Viewport.GetSize()); } else if (viewport.IsEmpty()) { viewport = _srcRect; } // Calculate expected source surf size, based on dest viewport and scaling const int src_w = viewport.GetWidth() / transform.ScaleX; const int src_h = viewport.GetHeight() / transform.ScaleY; // Initialize batch surface, depending on the batch description. // Surface was prepared externally (common for room cameras) if (desc.Surface != nullptr) { batch.Surface = desc.Surface; batch.Opaque = true; batch.IsParentRegion = false; } // In case something was not initialized else if (desc.Viewport.IsEmpty() || !virtualScreen) { batch.Surface.reset(); batch.Opaque = false; batch.IsParentRegion = false; } // Drawing directly on a viewport without transformation (other than offset): // then make a subbitmap of the parent surface (virtualScreen or else). else if (transform.ScaleX == 1.f && transform.ScaleY == 1.f) { // We need this subbitmap for plugins, which use _stageVirtualScreen and are unaware of possible multiple viewports; // TODO: there could be ways to optimize this further, but best is to update plugin rendering hooks (and upgrade plugins) if (!batch.Surface || !batch.IsParentRegion || (!batch.Surface->IsSameBitmap(parent_surf)) || (batch.Surface->GetSize() != Size(src_w, src_h)) || (batch.Surface->GetSubOffset() != viewport.GetLT())) { batch.Surface.reset(BitmapHelper::CreateSubBitmap(parent_surf, viewport)); } batch.Opaque = true; batch.IsParentRegion = true; // Because we sub-bitmap to viewport, render offsets should account for that transform.X -= viewport.Left; transform.Y -= viewport.Top; } // No surface prepared and has transformation other than offset: // then create exclusive intermediate bitmap. else { if (!batch.Surface || batch.IsParentRegion || (batch.Surface->GetSize() != Size(src_w, src_h))) { batch.Surface.reset(new Bitmap(src_w, src_h, _srcColorDepth)); } batch.Opaque = false; batch.IsParentRegion = false; } batch.Viewport = viewport; batch.Transform = transform; } void ScummVMRendererGraphicsDriver::ResetAllBatches() { // NOTE: we don't release batches themselves here, only sprite lists. // This is because we cache batch surfaces, for performance reasons. _spriteList.clear(); } void ScummVMRendererGraphicsDriver::DrawSprite(int x, int y, IDriverDependantBitmap *bitmap) { assert(_actSpriteBatch != UINT32_MAX); _spriteList.push_back(ALDrawListEntry((ALSoftwareBitmap *)bitmap, _actSpriteBatch, x, y)); } void ScummVMRendererGraphicsDriver::SetScreenFade(int /*red*/, int /*green*/, int /*blue*/) { // TODO: was not necessary atm // TODO: checkme later } void ScummVMRendererGraphicsDriver::SetScreenTint(int red, int green, int blue) { assert(_actSpriteBatch != UINT32_MAX); _tint_red = red; _tint_green = green; _tint_blue = blue; if (((_tint_red > 0) || (_tint_green > 0) || (_tint_blue > 0)) && (_srcColorDepth > 8)) { _spriteList.push_back( ALDrawListEntry(reinterpret_cast(DRAWENTRY_TINT), _actSpriteBatch, 0, 0)); } } void ScummVMRendererGraphicsDriver::SetStageScreen(const Size & /*sz*/, int /*x*/, int /*y*/) { // unsupported, as using _stageVirtualScreen instead } void ScummVMRendererGraphicsDriver::RenderToBackBuffer() { // Close unended batches, and issue a warning assert(_actSpriteBatch == UINT32_MAX); while (_actSpriteBatch != UINT32_MAX) EndSpriteBatch(); if (_spriteBatchDesc.size() == 0) { ClearDrawLists(); return; // no batches - no render } // Render all the sprite batches with necessary transformations // // NOTE: that's not immediately clear whether it would be faster to first draw upon a camera-sized // surface then stretch final result to the viewport on screen, or stretch-blit each individual // sprite right onto screen bitmap. We'd need to do proper profiling to know that. // An important thing is that Allegro does not provide stretching functions for drawing sprites // with blending and translucency; it seems you'd have to first stretch the original sprite onto a // temp buffer and then TransBlendBlt / LitBlendBlt it to the final destination. Of course, doing // that here would slow things down significantly, so if we ever go that way sprite caching will // be required (similarly to how AGS caches flipped/scaled object sprites now for). // const size_t last_batch_to_rend = _spriteBatchDesc.size() - 1; for (size_t cur_bat = 0u, last_bat = 0u, cur_spr = 0u; last_bat <= last_batch_to_rend;) { // Test if we are entering this batch (and not continuing after coming back from nested) if (cur_spr <= _spriteBatchRange[cur_bat].first) { const auto &batch = _spriteBatches[cur_bat]; // Prepare the transparent surface if (batch.Surface && !batch.Opaque) batch.Surface->ClearTransparent(); } // Render immediate batch sprites, if any, update cur_spr iterator if ((cur_spr < _spriteList.size()) && (cur_bat == _spriteList[cur_spr].node)) { const auto &batch = _spriteBatches[cur_bat]; const auto &batch_desc = _spriteBatchDesc[cur_bat]; Bitmap *surface = batch.Surface.get(); Bitmap *parent_surf = ((batch_desc.Parent != UINT32_MAX) && _spriteBatches[batch_desc.Parent].Surface) ? _spriteBatches[batch_desc.Parent].Surface.get() : virtualScreen; const Rect &viewport = batch.Viewport; const SpriteTransform &transform = batch.Transform; _rendSpriteBatch = batch.ID; parent_surf->SetClip(viewport); // CHECKME: this is not exactly correct? if (surface && !batch.IsParentRegion) { _stageVirtualScreen = surface; cur_spr = RenderSpriteBatch(batch, cur_spr, surface, transform.X, transform.Y); } else { _stageVirtualScreen = surface ? surface : parent_surf; cur_spr = RenderSpriteBatch(batch, cur_spr, _stageVirtualScreen, transform.X, transform.Y); } } // Test if we're exiting current batch (and not going into nested ones): // if there's no sprites belonging to this batch (direct, or nested), // and if there's no nested batches (even if empty ones) const uint32_t was_bat = cur_bat; while ((cur_bat != UINT32_MAX) && (cur_spr >= _spriteBatchRange[cur_bat].second) && ((last_bat == last_batch_to_rend) || (_spriteBatchDesc[last_bat + 1].Parent != cur_bat))) { const auto &batch = _spriteBatches[cur_bat]; const auto &batch_desc = _spriteBatchDesc[cur_bat]; Bitmap *surface = batch.Surface.get(); Bitmap *parent_surf = ((batch_desc.Parent != UINT32_MAX) && _spriteBatches[batch_desc.Parent].Surface) ? _spriteBatches[batch_desc.Parent].Surface.get() : virtualScreen; const Rect &viewport = batch.Viewport; // If we're not drawing directly to the subregion of a parent surface, // then blit our own surface to the parent's if (surface && !batch.IsParentRegion) { parent_surf->StretchBlt(surface, viewport, batch.Opaque ? kBitmap_Copy : kBitmap_Transparency); } // Back to the parent batch cur_bat = batch_desc.Parent; } // If we stayed at the same batch, this means that there are still nested batches; // if there's no batches in the stack left, this means we got to move forward anyway. if ((was_bat == cur_bat) || (cur_bat == UINT32_MAX)) { cur_bat = ++last_bat; } } _stageVirtualScreen = virtualScreen; _rendSpriteBatch = UINT32_MAX; ClearDrawLists(); } size_t ScummVMRendererGraphicsDriver::RenderSpriteBatch(const ALSpriteBatch &batch, size_t from, Bitmap *surface, int surf_offx, int surf_offy) { for (; (from < _spriteList.size()) && (_spriteList[from].node == batch.ID); ++from) { const auto &sprite = _spriteList[from]; if (sprite.ddb == nullptr) { if (_spriteEvtCallback) _spriteEvtCallback(sprite.x, sprite.y); else error("Unhandled attempt to draw null sprite"); // Stage surface could have been replaced by plugin surface = _stageVirtualScreen; continue; } else if (sprite.ddb == reinterpret_cast(DRAWENTRY_TINT)) { // draw screen tint fx set_trans_blender(_tint_red, _tint_green, _tint_blue, 0); surface->LitBlendBlt(surface, 0, 0, 128); continue; } ALSoftwareBitmap *bitmap = sprite.ddb; int drawAtX = sprite.x + surf_offx; int drawAtY = sprite.y + surf_offy; if (bitmap->_alpha == 0) { } // fully transparent, do nothing else if ((bitmap->_opaque) && (bitmap->_bmp == surface) && (bitmap->_alpha == 255)) { } else if (bitmap->_opaque) { surface->Blit(bitmap->_bmp, 0, 0, drawAtX, drawAtY, bitmap->_bmp->GetWidth(), bitmap->_bmp->GetHeight()); // TODO: we need to also support non-masked translucent blend, but... // Allegro 4 **does not have such function ready** :( (only masked blends, where it skips magenta pixels); // I am leaving this problem for the future, as coincidentally software mode does not need this atm. } else if (bitmap->_hasAlpha) { if (bitmap->_alpha == 255) // no global transparency, simple alpha blend set_alpha_blender(); else set_blender_mode(kArgbToRgbBlender, 0, 0, 0, bitmap->_alpha); surface->TransBlendBlt(bitmap->_bmp, drawAtX, drawAtY); } else { // here _transparency is used as alpha (between 1 and 254), but 0 means opaque! GfxUtil::DrawSpriteWithTransparency(surface, bitmap->_bmp, drawAtX, drawAtY, bitmap->_alpha); } } return from; } void ScummVMRendererGraphicsDriver::copySurface(const Graphics::Surface &src, bool mode) { assert(src.w == _screen->w && src.h == _screen->h && src.pitch == _screen->pitch); const uint32 *srcP = (const uint32 *)src.getPixels(); uint32 *destP = (uint32 *)_screen->getPixels(); uint32 pixel; int x1 = 9999, y1 = 9999, x2 = -1, y2 = -1; for (int y = 0; y < src.h; ++y) { for (int x = 0; x < src.w; ++x, ++srcP, ++destP) { if (!mode) { pixel = (*srcP & 0xff00ff00) | ((*srcP & 0xff) << 16) | ((*srcP >> 16) & 0xff); } else { pixel = ((*srcP & 0xffffff) << 8) | ((*srcP >> 24) & 0xff); } if (*destP != pixel) { *destP = pixel; x1 = MIN(x1, x); y1 = MIN(y1, y); x2 = MAX(x2, x); y2 = MAX(y2, y); } } } if (x2 != -1) _screen->addDirtyRect(Common::Rect(x1, y1, x2 + 1, y2 + 1)); } void ScummVMRendererGraphicsDriver::Present(int xoff, int yoff, Shared::GraphicFlip flip) { Graphics::Surface *srcTransformed = nullptr; if (xoff != 0 || yoff != 0 || flip != Shared::kFlip_None) { srcTransformed = new Graphics::Surface(); srcTransformed->copyFrom(virtualScreen->GetAllegroBitmap()->getSurface()); switch(flip) { case kFlip_Horizontal: srcTransformed->flipHorizontal(Common::Rect(srcTransformed->w, srcTransformed->h)); break; case kFlip_Vertical: srcTransformed->flipVertical(Common::Rect(srcTransformed->w, srcTransformed->h)); break; case kFlip_Both: srcTransformed->flipHorizontal(Common::Rect(srcTransformed->w, srcTransformed->h)); srcTransformed->flipVertical(Common::Rect(srcTransformed->w, srcTransformed->h)); break; default: break; } srcTransformed->move(xoff, yoff, srcTransformed->h); } const Graphics::Surface &src = srcTransformed ? *srcTransformed : virtualScreen->GetAllegroBitmap()->getSurface(); enum { kRenderInitial, kRenderDirect, kRenderToABGR, kRenderToRGBA, kRenderOther } renderMode; // Check for rendering to use. The virtual screen can change, so I'm // playing it safe and checking the render mode for each frame const Graphics::PixelFormat screenFormat = g_system->getScreenFormat(); if (src.format == screenFormat) { // The virtual surface can be directly blitted to the screen renderMode = kRenderDirect; } else if (src.format != Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24)) { // Not a 32-bit surface, so will have to use an intermediate // surface to correct the virtual screen to the correct format renderMode = kRenderOther; } else if (screenFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)) { renderMode = kRenderToRGBA; } else if (screenFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24)) { renderMode = kRenderToABGR; } else { renderMode = kRenderOther; } if (renderMode != kRenderDirect && !_screen) _screen = new Graphics::Screen(); switch (renderMode) { case kRenderToABGR: // ARGB to ABGR copySurface(src, false); break; case kRenderToRGBA: // ARGB to RGBA copySurface(src, true); break; case kRenderOther: { // Blit the surface to the temporary screen, ignoring the alphas. // This takes care of converting to the screen format Graphics::Surface srcCopy = src; srcCopy.format.aLoss = 8; _screen->blitFrom(srcCopy); break; } case kRenderDirect: // Blit the virtual surface directly to the screen g_system->copyRectToScreen(src.getPixels(), src.pitch, 0, 0, src.w, src.h); g_system->updateScreen(); if (srcTransformed) { srcTransformed->free(); delete srcTransformed; } return; default: break; } if (srcTransformed) { srcTransformed->free(); delete srcTransformed; } if (_screen) _screen->update(); } void ScummVMRendererGraphicsDriver::Render(int xoff, int yoff, GraphicFlip flip) { RenderToBackBuffer(); Present(xoff, yoff, flip); } void ScummVMRendererGraphicsDriver::Render() { Render(0, 0, kFlip_None); } Bitmap *ScummVMRendererGraphicsDriver::GetMemoryBackBuffer() { return virtualScreen; } void ScummVMRendererGraphicsDriver::SetMemoryBackBuffer(Bitmap *backBuffer) { // We need to also test internal AL BITMAP pointer, because we may receive it raw from plugin, // in which case the Bitmap object may be a different wrapper over our own virtual screen. if (backBuffer && (backBuffer->GetAllegroBitmap() != _origVirtualScreen->GetAllegroBitmap())) { virtualScreen = backBuffer; } else { virtualScreen = _origVirtualScreen.get(); } _stageVirtualScreen = virtualScreen; // Reset old virtual screen's subbitmaps; // NOTE: this MUST NOT be called in the midst of the RenderSpriteBatches! assert(_rendSpriteBatch == UINT32_MAX); if (_rendSpriteBatch != UINT32_MAX) return; for (auto &batch : _spriteBatches) { if (batch.IsParentRegion) batch.Surface.reset(); } } Bitmap *ScummVMRendererGraphicsDriver::GetStageBackBuffer(bool /*mark_dirty*/) { return _stageVirtualScreen; } void ScummVMRendererGraphicsDriver::SetStageBackBuffer(Bitmap *backBuffer) { Bitmap *cur_stage = (_rendSpriteBatch == UINT32_MAX) ? virtualScreen : _spriteBatches[_rendSpriteBatch].Surface.get(); // We need to also test internal AL BITMAP pointer, because we may receive it raw from plugin, // in which case the Bitmap object may be a different wrapper over our own virtual screen. if (backBuffer && (backBuffer->GetAllegroBitmap() != cur_stage->GetAllegroBitmap())) _stageVirtualScreen = backBuffer; else _stageVirtualScreen = cur_stage; } bool ScummVMRendererGraphicsDriver::GetCopyOfScreenIntoBitmap(Bitmap *destination, const Rect *src_rect, bool at_native_res, GraphicResolution *want_fmt, uint32_t /*batch_skip_filter*/) { (void)at_native_res; // software driver always renders at native resolution at the moment // software filter is taught to copy to any size, so only check color depth if (destination->GetColorDepth() != _srcColorDepth) { if (want_fmt) *want_fmt = GraphicResolution(destination->GetWidth(), destination->GetHeight(), _srcColorDepth); return false; } Rect copy_from = src_rect ? *src_rect : _srcRect; if (destination->GetSize() == copy_from.GetSize()) { destination->Blit(virtualScreen, copy_from.Left, copy_from.Top, 0, 0, copy_from.GetWidth(), copy_from.GetHeight()); } else { destination->StretchBlt(virtualScreen, copy_from, RectWH(destination->GetSize())); } return true; } /** fade.c - High Color Fading Routines Last Revision: 21 June, 2002 Author: Matthew Leverton **/ void ScummVMRendererGraphicsDriver::highcolor_fade_in(Bitmap *vs, void(*draw_callback)(), int speed, int targetColourRed, int targetColourGreen, int targetColourBlue) { Bitmap *bmp_orig = vs; const int col_depth = bmp_orig->GetColorDepth(); const int clearColor = makecol_depth(col_depth, targetColourRed, targetColourGreen, targetColourBlue); if (speed <= 0) speed = 16; Bitmap *bmp_buff = new Bitmap(bmp_orig->GetWidth(), bmp_orig->GetHeight(), col_depth); SetMemoryBackBuffer(bmp_buff); for (int a = 0; a < 256; a += speed) { bmp_buff->Fill(clearColor); set_trans_blender(0, 0, 0, a); bmp_buff->TransBlendBlt(bmp_orig, 0, 0); if (draw_callback) draw_callback(); RenderToBackBuffer(); Present(); sys_evt_process_pending(); if (_pollingCallback) _pollingCallback(); WaitForNextFrame(); } delete bmp_buff; SetMemoryBackBuffer(vs); if (draw_callback) draw_callback(); RenderToBackBuffer(); Present(); } void ScummVMRendererGraphicsDriver::highcolor_fade_out(Bitmap *vs, void(*draw_callback)(), int speed, int targetColourRed, int targetColourGreen, int targetColourBlue) { Bitmap *bmp_orig = vs; const int col_depth = vs->GetColorDepth(); const int clearColor = makecol_depth(col_depth, targetColourRed, targetColourGreen, targetColourBlue); if (speed <= 0) speed = 16; Bitmap *bmp_buff = new Bitmap(bmp_orig->GetWidth(), bmp_orig->GetHeight(), col_depth); SetMemoryBackBuffer(bmp_buff); for (int a = 255 - speed; a > 0; a -= speed) { bmp_buff->Fill(clearColor); set_trans_blender(0, 0, 0, a); bmp_buff->TransBlendBlt(bmp_orig, 0, 0); if (draw_callback) draw_callback(); RenderToBackBuffer(); Present(); sys_evt_process_pending(); if (_pollingCallback) _pollingCallback(); WaitForNextFrame(); } delete bmp_buff; SetMemoryBackBuffer(vs); vs->Clear(clearColor); if (draw_callback) draw_callback(); RenderToBackBuffer(); Present(); } /** END FADE.C **/ // palette fading routiens // from allegro, modified for mp3 void initialize_fade_256(int r, int g, int b) { int a; for (a = 0; a < 256; a++) { faded_out_palette[a].r = r / 4; faded_out_palette[a].g = g / 4; faded_out_palette[a].b = b / 4; } } void ScummVMRendererGraphicsDriver::__fade_from_range(PALETTE source, PALETTE dest, int speed, int from, int to) { PALETTE temp; int c; for (c = 0; c < PAL_SIZE; c++) temp[c] = source[c]; for (c = 0; c < 64; c += speed) { fade_interpolate(source, dest, temp, c, from, to); set_palette_range(temp, from, to, TRUE); RenderToBackBuffer(); Present(); g_system->delayMillis(5); sys_evt_process_pending(); if (_pollingCallback) _pollingCallback(); } set_palette_range(dest, from, to, TRUE); } void ScummVMRendererGraphicsDriver::__fade_out_range(int speed, int from, int to, int targetColourRed, int targetColourGreen, int targetColourBlue) { PALETTE temp; initialize_fade_256(targetColourRed, targetColourGreen, targetColourBlue); get_palette(temp); __fade_from_range(temp, faded_out_palette, speed, from, to); } void ScummVMRendererGraphicsDriver::FadeOut(int speed, int targetColourRed, int targetColourGreen, int targetColourBlue, uint32_t /*batch_skip_filter*/) { if (_srcColorDepth > 8) { highcolor_fade_out(virtualScreen, _drawPostScreenCallback, speed * 4, targetColourRed, targetColourGreen, targetColourBlue); } else { __fade_out_range(speed, 0, 255, targetColourRed, targetColourGreen, targetColourBlue); } } void ScummVMRendererGraphicsDriver::FadeIn(int speed, PALETTE p, int targetColourRed, int targetColourGreen, int targetColourBlue, uint32_t /*batch_skip_filter*/) { if (_drawScreenCallback) { _drawScreenCallback(); RenderToBackBuffer(); } if (_srcColorDepth > 8) { highcolor_fade_in(virtualScreen, _drawPostScreenCallback, speed * 4, targetColourRed, targetColourGreen, targetColourBlue); } else { initialize_fade_256(targetColourRed, targetColourGreen, targetColourBlue); __fade_from_range(faded_out_palette, p, speed, 0, 255); } } void ScummVMRendererGraphicsDriver::BoxOutEffect(bool blackingOut, int speed, int delay, uint32_t /*batch_skip_filter*/) { if (blackingOut) { int yspeed = _srcRect.GetHeight() / (_srcRect.GetWidth() / speed); int boxwid = speed, boxhit = yspeed; Bitmap *bmp_orig = virtualScreen; Bitmap *bmp_buff = new Bitmap(bmp_orig->GetWidth(), bmp_orig->GetHeight(), bmp_orig->GetColorDepth()); SetMemoryBackBuffer(bmp_buff); while (boxwid < _srcRect.GetWidth()) { boxwid += speed; boxhit += yspeed; int vcentre = _srcRect.GetHeight() / 2; bmp_orig->FillRect(Rect(_srcRect.GetWidth() / 2 - boxwid / 2, vcentre - boxhit / 2, _srcRect.GetWidth() / 2 + boxwid / 2, vcentre + boxhit / 2), 0); bmp_buff->Fill(0); bmp_buff->Blit(bmp_orig); if (_drawPostScreenCallback) _drawPostScreenCallback(); RenderToBackBuffer(); Present(); sys_evt_process_pending(); if (_pollingCallback) _pollingCallback(); _G(platform)->Delay(delay); } delete bmp_buff; SetMemoryBackBuffer(bmp_orig); } else { error("BoxOut fade-in not implemented in sw gfx driver"); } } // end fading routines ScummVMRendererGraphicsFactory *ScummVMRendererGraphicsFactory::_factory = nullptr; ScummVMRendererGraphicsFactory::~ScummVMRendererGraphicsFactory() { _factory = nullptr; } size_t ScummVMRendererGraphicsFactory::GetFilterCount() const { return 1; } const GfxFilterInfo *ScummVMRendererGraphicsFactory::GetFilterInfo(size_t index) const { switch (index) { case 0: return _G(scummvmGfxFilter); default: return nullptr; } } String ScummVMRendererGraphicsFactory::GetDefaultFilterID() const { return _GP(scummvmGfxFilter).Id; } /* static */ ScummVMRendererGraphicsFactory *ScummVMRendererGraphicsFactory::GetFactory() { if (!_factory) _factory = new ScummVMRendererGraphicsFactory(); return _factory; } ScummVMRendererGraphicsDriver *ScummVMRendererGraphicsFactory::EnsureDriverCreated() { if (!_driver) _driver = new ScummVMRendererGraphicsDriver(); return _driver; } ScummVMRendererGfxFilter *ScummVMRendererGraphicsFactory::CreateFilter(const String &id) { if (_GP(scummvmGfxFilter).Id.CompareNoCase(id) == 0) return new ScummVMRendererGfxFilter(); return nullptr; } } // namespace ALSW } // namespace Engine } // namespace AGS } // namespace AGS3