Files
2026-02-02 04:50:13 +01:00

486 lines
13 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#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<ITexture> 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<Math::Vector2d> points,
Color color = kDebugRed
) = 0;
virtual void debugPolyline(
Common::Span<Math::Vector2d> 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<uint32>
_spriteOffsets, ///< index offset per sprite and animation frame
_spriteBases; ///< base index per sprite
Common::Array<AnimationFrame> _frames;
Common::Array<Graphics::ManagedSurface *> _images; ///< will contain nullptr for fake images
Common::Array<Common::Point> _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<ITexture> _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<Math::Vector2d> _texMins, _texMaxs;
Common::ScopedPtr<ITexture> _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<Animation> _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<const byte>; ///< byte to convert 128+ characters to image indices
Font &_font;
int _posY, _height, _width;
Color _color;
Common::Span<TextLine> _lines;
Common::Span<int> _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<typename T, typename... Args>
inline T *allocate(Args&&... args) {
return new(allocateRaw(sizeof(T), alignof(T))) T(Common::forward<Args>(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<void *> _pages;
};
class DrawQueue {
public:
DrawQueue(IRenderer *renderer);
template<typename T, typename... Args>
inline void add(Args&&... args) {
addRequest(_allocator.allocate<T>(Common::forward<Args>(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