前面的章节,我们讲述了Sprite加载的原理,最终是放到一个 SpriteCache这样一个数据结构里面。
今天,我们继续看一下在loop里面,程序如何将这些Sprite绘制到界面上。
首先 研究 SpriteCache.cpp ,在《Day05_OpenTTD的Sprite介绍》 我们了解到 游戏启动阶段,LoadNextSprite() 函数加载 SpriteCache的过程。
在 spritecache.h 里面还定义了 GetRawSprite(),这个函数向外提供服务,通过SpriteID获取Sprite数据
GetSprite() 返回的 Sprite数据结构如下():
/** Data structure describing a sprite. */
struct Sprite {
uint16 height; ///< Height of the sprite.
uint16 width; ///< Width of the sprite.
int16 x_offs; ///< Number of pixels to shift the sprite to the right.
int16 y_offs; ///< Number of pixels to shift the sprite downwards.
byte data[]; ///< Sprite data.
};
spritecache.h 通过 GetSprite()封装GetRawSprite()向外提供服务
通过 gfx.cpp DrawSpriteViewport(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub) 调用 GetSprite() 获取 Sprite后,绘制到界面上,调用的顺序如下:
gfx.cpp DrawSpriteViewport()
-> GfxMainBlitterViewport()
-> GfxBlitter<ZOOM_LVL_BASE, false>() <- 构造出 BlitterParams 供下游消费
-> BlitterFactory::GetCurrentBlitter()->Draw() <- 这里有各种类型的blitter对上面的 params进行加工
通过增加DEBUG分析,我们知道默认的加载器是 32bppOptimized.cpp
Blitter_32bppOptimized::Draw(const Blitter::BlitterParams *bp, ZoomLevel zoom)
这个Draw() 主要逻辑是 循环处理 bp->height ,也就是根据像素高一行一行处理,生成 bp->dst
From: base.hpp
/** Parameters related to blitting. */
struct BlitterParams {
const void *sprite; ///< Pointer to the sprite how ever the encoder stored it
const byte *remap; ///< XXX -- Temporary storage for remap array
int skip_left; ///< How much pixels of the source to skip on the left (based on zoom of dst)
int skip_top; ///< How much pixels of the source to skip on the top (based on zoom of dst)
int width; ///< The width in pixels that needs to be drawn to dst
int height; ///< The height in pixels that needs to be drawn to dst
int sprite_width; ///< Real width of the sprite
int sprite_height; ///< Real height of the sprite
int left; ///< The left offset in the 'dst' in pixels to start drawing
int top; ///< The top offset in the 'dst' in pixels to start drawing
void *dst; ///< Destination buffer
int pitch; ///< The pitch of the destination buffer
};
我们根据 dst的关键字,我们定位到 dst的赋值位置 gfx.cpp GfxBlitter()
gfx.cpp Line 1054
static void GfxBlitter(const Sprite * const sprite, int x, int y, BlitterMode mode, const SubSprite * const sub, SpriteID sprite_id, ZoomLevel zoom)
{
const DrawPixelInfo *dpi = _cur_dpi;
...
bp.dst = dpi->dst_ptr;
...
gfx_type.h 定义了 DrawPixelInfo 数据结构如下:
/** Data about how and where to blit pixels. */
struct DrawPixelInfo {
void *dst_ptr;
int left, top, width, height;
int pitch;
ZoomLevel zoom;
};
继续搜索关键字 _cur_dpi,与它相关联的语句大概有下面两种
_cur_dpi = &_screen; <- Draw mouse cursor on screen.
或者
if (!FillDrawPixelInfo(&tmp_dpi, left, y, max_width, ScaleGUITrad(14))) return;
old_dpi = _cur_dpi;
_cur_dpi = &tmp_dpi;
FillDrawPixelInfo() * Set up a clipping area for only drawing into a certain area.
这个函数的作用是只绘制一部分区域,这个函数并没有对 dst_ptr的定义,所以,我们看回 _screen 看看 dst_ptr是怎么来的。
sdl2_v.cpp Line800
bool VideoDriver_SDL_Base::LockVideoBuffer()
{
...
_screen.dst_ptr = this->GetVideoPointer();
...
}
继续 GetVideoPointer();
sdl2_default_v.cpp Line38
...
static SDL_Surface *_sdl_surface;
...
void *VideoDriver_SDL_Default::GetVideoPointer()
{
return _sdl_surface->pixels;
}
到这里就真相大白了,SDL_Surface是 Linux的 SDL 开放出来的数据结构:
/**
* \brief A collection of pixels used in software blitting.
*
* \note This structure should be treated as read-only, except for \c pixels,
* which, if not NULL, contains the raw pixel data for the surface.
*/
typedef struct SDL_Surface
{
Uint32 flags; /**< Read-only */
SDL_PixelFormat *format; /**< Read-only */
int w, h; /**< Read-only */
int pitch; /**< Read-only */
void *pixels; /**< Read-write */
/** Application data associated with the surface */
void *userdata; /**< Read-write */
/** information needed for surfaces requiring locks */
int locked; /**< Read-only */
void *lock_data; /**< Read-only */
/** clipping information */
SDL_Rect clip_rect; /**< Read-only */
/** info for fast blit mapping to other surfaces */
struct SDL_BlitMap *map; /**< Private */
/** Reference count -- used when freeing surface */
int refcount; /**< Read-mostly */
} SDL_Surface;
总结一下绘制的过程,SDL通过 SDL_Surface的pixels 接收 Sprite 信息,通过 Blitter_32bppOptimized的Draw()方法 更新 字段信息,最后 绘制前,通过sdl2_v.cpp LockVideoBuffer() 设置 屏幕指针,然后通过 sdl2_default_v.cpp Paint() 把sprite绘制到界面上。