/* 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 . * */ #ifndef ALCACHOFA_GRAPHICS_H #define ALCACHOFA_GRAPHICS_H #include "common/ptr.h" #include "common/stream.h" #include "common/serializer.h" #include "common/rect.h" #include "common/span.h" #include "math/vector2d.h" #include "graphics/managed_surface.h" #include "alcachofa/camera.h" #include "alcachofa/common.h" namespace Alcachofa { /** * Because this gets confusing fast, here in tabular form * * | BlendMode | SrcColor | SrcAlpha | SrcBlend | DstBlend | * |:-------------:|:---------------------------------|:----------|:---------|:-------------| * | AdditiveAlpha | (1 - TintAlpha) * TexColor | TexAlpha | One | 1 - SrcAlpha | * | Additive | (1 - TintAlpha) * TexColor | TexAlpha | One | One | * | Multiply | (1 - TintAlpha) * TexColor | TexAlpha | DstColor | One | * | Alpha | TexColor | TintAlpha | SrcAlpha | 1 - SrcAlpha | * | Tinted | TintColor * TintAlpha * TexColor | TexAlpha | One | 1 - SrcAlpha | * */ enum class BlendMode { AdditiveAlpha, // Normal objects Additive, // "Effect" objects, fades Multiply, // Unused in Movie Adventure Alpha, // Unused in Movie Adventure (used for debugging) Tinted // Used for fonts }; class Shape; class ITexture { public: ITexture(Common::Point size); virtual ~ITexture() {} virtual void update(const Graphics::Surface &surface) = 0; inline void update(const Graphics::ManagedSurface &surface) { update(surface.rawSurface()); } inline Common::Point size() const { return _size; } private: Common::Point _size; }; class IRenderer { public: virtual ~IRenderer() {} virtual Common::ScopedPtr createTexture(int32 w, int32 h, bool withMipmaps = true) = 0; virtual Graphics::PixelFormat getPixelFormat() const = 0; virtual bool requiresPoTTextures() const = 0; virtual void begin() = 0; virtual void setTexture(ITexture *texture) = 0; virtual void setBlendMode(BlendMode blendMode) = 0; virtual void setLodBias(float lodBias) = 0; virtual void setOutput(Graphics::Surface &surface) = 0; virtual bool hasOutput() const = 0; virtual void quad( Math::Vector2d topLeft, Math::Vector2d size, Color color = kWhite, Math::Angle rotation = Math::Angle(), Math::Vector2d texMin = Math::Vector2d(0, 0), Math::Vector2d texMax = Math::Vector2d(1, 1)) = 0; virtual void end() = 0; static IRenderer *createOpenGLRenderer(Common::Point resolution); static IRenderer *createOpenGLRendererClassic(Common::Point resolution); static IRenderer *createOpenGLRendererShaders(Common::Point resolution); static IRenderer *createTinyGLRenderer(Common::Point resolution); }; class IDebugRenderer : public virtual IRenderer { public: virtual void debugPolygon( Common::Span points, Color color = kDebugRed ) = 0; virtual void debugPolyline( Common::Span points, Color color = kDebugRed ) = 0; virtual void debugShape( const Shape &shape, Color color = kDebugRed ); inline void debugPolyline(Common::Point a, Common::Point b, Color color = kDebugRed) { Math::Vector2d points[] = { { (float)a.x, (float)a.y }, { (float)b.x, (float)b.y } }; debugPolygon({ points, 2 }, color); } }; enum class AnimationFolder { Animations, Masks, Backgrounds }; struct AnimationFrame { Common::Point _center, ///< the center is used for more than just drawing the animation frame _offset; ///< the offset is only used for drawing the animation frame uint32 _duration; }; /** * An animation contains one or more sprites which change their position and image during playback. * * Internally there is a single list of images. Every sprite ID is mapped to an index * (via _spriteIndexMapping) which points to: * 1. The fixed image base for that sprite * 2. The image offset for that sprite for the current frame * Image indices are unfortunately one-based * * As fonts are handled very differently they are split into a second class */ class AnimationBase { protected: AnimationBase(Common::String fileName, AnimationFolder folder = AnimationFolder::Animations); ~AnimationBase(); void load(); void loadMissingAnimation(); void freeImages(); Graphics::ManagedSurface *readImage(Common::SeekableReadStream &stream) const; Common::Point imageSize(int32 imageI) const; inline bool isLoaded() const { return _isLoaded; } static void fullBlend( const Graphics::ManagedSurface &source, Graphics::ManagedSurface &destination, int offsetX, int offsetY); static constexpr const uint kMaxSpriteIDs = 256; Common::String _fileName; AnimationFolder _folder; bool _isLoaded = false; uint32 _totalDuration = 0; int32 _spriteIndexMapping[kMaxSpriteIDs] = { 0 }; Common::Array _spriteOffsets, ///< index offset per sprite and animation frame _spriteBases; ///< base index per sprite Common::Array _frames; Common::Array _images; ///< will contain nullptr for fake images Common::Array _imageOffsets; }; /** * Animations prerenders its sprites into a single texture for a set frame. * This prerendering can be customized with a alpha to be premultiplied */ class Animation : private AnimationBase { public: Animation(Common::String fileName, AnimationFolder folder = AnimationFolder::Animations); void load(); void freeImages(); using AnimationBase::isLoaded; inline uint spriteCount() const { return _spriteBases.size(); } inline uint frameCount() const { return _frames.size(); } inline uint32 frameDuration(int32 frameI) const { return _frames[frameI]._duration; } inline Common::Point frameCenter(int32 frameI) const { return _frames[frameI]._center; } inline uint32 totalDuration() const { return _totalDuration; } inline uint8 &premultiplyAlpha() { return _premultiplyAlpha; } Common::Rect frameBounds(int32 frameI) const; Common::Point totalFrameOffset(int32 frameI) const; int32 frameAtTime(uint32 time) const; int32 imageIndex(int32 frameI, int32 spriteI) const; using AnimationBase::imageSize; void outputRect2D(int32 frameI, float scale, Math::Vector2d &topLeft, Math::Vector2d &size) const; void outputRect3D(int32 frameI, float scale, Math::Vector3d &topLeft, Math::Vector2d &size) const; void overrideTexture(const Graphics::ManagedSurface &surface); void draw2D( int32 frameI, Math::Vector2d topLeft, float scale, BlendMode blendMode, Color color); void draw3D( int32 frameI, Math::Vector3d topLeft, float scale, BlendMode blendMode, Color color); void drawEffect( int32 frameI, Math::Vector3d topLeft, Math::Vector2d tiling, Math::Vector2d texOffset, BlendMode blendMode); private: Common::Rect spriteBounds(int32 frameI, int32 spriteI) const; Common::Rect maxFrameBounds() const; void prerenderFrame(int32 frameI); int32_t _renderedFrameI = -1; uint8 _premultiplyAlpha = 100, ///< in percent [0-100] not [0-255] _renderedPremultiplyAlpha = 255; Graphics::ManagedSurface _renderedSurface; Common::ScopedPtr _renderedTexture; }; class Font : private AnimationBase { public: Font(Common::String fileName); void load(); void freeImages(); void drawCharacter(int32 imageI, Common::Point center, Color color); using AnimationBase::isLoaded; using AnimationBase::imageSize; inline uint imageCount() const { return _images.size(); } private: Common::Array _texMins, _texMaxs; Common::ScopedPtr _texture; }; class Graphic { public: Graphic(); Graphic(Common::ReadStream &stream); Graphic(const Graphic &other); // animation reference is taken, so keep other alive Graphic &operator= (const Graphic &other); inline Common::Point &topLeft() { return _topLeft; } inline int8 &order() { return _order; } inline int16 &scale() { return _scale; } inline float &depthScale() { return _depthScale; } inline Color &color() { return _color; } inline int32 &frameI() { return _frameI; } inline uint32 &lastTime() { return _lastTime; } inline bool isPaused() const { return _isPaused; } inline bool hasAnimation() const { return _animation != nullptr; } inline Animation &animation() { assert(_animation != nullptr && _animation->isLoaded()); return *_animation; } inline uint8 &premultiplyAlpha() { assert(_animation != nullptr); return _animation->premultiplyAlpha(); } void loadResources(); void freeResources(); void update(); void start(bool looping); void pause(); void reset(); void setAnimation(const Common::String &fileName, AnimationFolder folder); void setAnimation(Animation *animation); ///< no memory ownership is given, but for prerendering it has to be mutable void syncGame(Common::Serializer &serializer); private: friend class AnimationDrawRequest; friend class SpecialEffectDrawRequest; Common::ScopedPtr _ownedAnimation; Animation *_animation = nullptr; Common::Point _topLeft; int16 _scale = kBaseScale; int8 _order = 0; Color _color = kWhite; bool _isPaused = true, _isLooping = true; uint32 _lastTime = 0; ///< either start time or played duration at pause int32 _frameI = -1; float _depthScale = 1.0f; }; class IDrawRequest { public: IDrawRequest(int8 order); virtual ~IDrawRequest() {} inline int8 order() const { return _order; } virtual void draw() = 0; private: const int8 _order; }; class AnimationDrawRequest : public IDrawRequest { public: AnimationDrawRequest( Graphic &graphic, bool is3D, BlendMode blendMode, float lodBias = 0.0f); AnimationDrawRequest( Animation *animation, int32 frameI, Math::Vector2d center, int8 order ); void draw() override; private: bool _is3D; Animation *_animation; int32 _frameI; Math::Vector3d _topLeft; float _scale; Color _color; BlendMode _blendMode; float _lodBias; }; class SpecialEffectDrawRequest : public IDrawRequest { public: SpecialEffectDrawRequest( Graphic &graphic, Common::Point topLeft, Common::Point bottomRight, Math::Vector2d texOffset, BlendMode blendMode); void draw() override; private: Animation *_animation; int32 _frameI; Math::Vector3d _topLeft; Math::Vector2d _size, _texOffset; BlendMode _blendMode; }; class TextDrawRequest : public IDrawRequest { public: TextDrawRequest( Font &font, const char *text, Common::Point pos, int maxWidth, bool centered, Color color, int8 order); inline Common::Point size() const { return { (int16)_width, (int16)_height }; } void draw() override; private: static constexpr uint kMaxLines = 12; using TextLine = Common::Span; ///< byte to convert 128+ characters to image indices Font &_font; int _posY, _height, _width; Color _color; Common::Span _lines; Common::Span _posX; TextLine _allLines[kMaxLines]; int _allPosX[kMaxLines]; }; enum class FadeType { ToBlack, ToWhite // Originally there was a CrossFade, but it is unused for now and thus not implemented }; enum class PermanentFadeAction { Nothing, SetFaded, UnsetFaded }; class FadeDrawRequest : public IDrawRequest { public: FadeDrawRequest(FadeType type, float value, int8 order); void draw() override; private: FadeType _type; float _value; }; Task *fade(Process &process, FadeType fadeType, float from, float to, int32 duration, EasingType easingType, int8 order, PermanentFadeAction permanentFadeAction = PermanentFadeAction::Nothing); class BorderDrawRequest : public IDrawRequest { public: BorderDrawRequest(Common::Rect rect, Color color); void draw() override; private: Common::Rect _rect; Color _color; }; class BumpAllocator { public: BumpAllocator(size_t pageSize); ~BumpAllocator(); template inline T *allocate(Args&&... args) { return new(allocateRaw(sizeof(T), alignof(T))) T(Common::forward(args)...); } void *allocateRaw(size_t size, size_t align); void deallocateAll(); private: void allocatePage(); const size_t _pageSize; size_t _pageI = 0, _used = 0; Common::Array _pages; }; class DrawQueue { public: DrawQueue(IRenderer *renderer); template inline void add(Args&&... args) { addRequest(_allocator.allocate(Common::forward(args)...)); } inline BumpAllocator &allocator() { return _allocator; } void clear(); void setLodBias(int8 orderFrom, int8 orderTo, float newLodBias); void draw(); private: void addRequest(IDrawRequest *drawRequest); static constexpr const uint kMaxDrawRequestsPerOrder = 50; IRenderer *const _renderer; BumpAllocator _allocator; IDrawRequest *_requestsPerOrder[kOrderCount][kMaxDrawRequestsPerOrder] = { { 0 } }; uint8 _requestsPerOrderCount[kOrderCount] = { 0 }; float _lodBiasPerOrder[kOrderCount] = { 0 }; }; } #endif // ALCACHOFA_GRAPHICS_H