@@ -11,8 +11,21 @@
#define SPRITELOADER_HPP

#include "../core/alloc_type.hpp"
#include "../core/enum_type.hpp"
#include "../gfx_type.h"

struct Sprite;
typedef void *AllocatorProc(size_t size);

/** The different colour components a sprite can have. */
enum SpriteColourComponent {
SCC_RGB = 1 << 0, ///< Sprite has RGB.
SCC_ALPHA = 1 << 1, ///< Sprite has alpha.
SCC_PAL = 1 << 2, ///< Sprite has palette data.
SCC_MASK = SCC_RGB | SCC_ALPHA | SCC_PAL, ///< Mask of valid colour bits.
};
DECLARE_ENUM_AS_BIT_SET(SpriteColourComponent)

/** Interface for the loader of our sprites. */
class SpriteLoader {
public:
@@ -37,6 +50,7 @@ class SpriteLoader {
int16 x_offs; ///< The x-offset of where the sprite will be drawn
int16 y_offs; ///< The y-offset of where the sprite will be drawn
SpriteType type; ///< The sprite type
SpriteColourComponent colours; ///< The colour components of the sprite with useful information.
SpriteLoader::CommonPixel *data; ///< The sprite itself

/**
@@ -64,4 +78,27 @@ class SpriteLoader {
virtual ~SpriteLoader() { }
};

/** Interface for something that can encode a sprite. */
class SpriteEncoder {
public:

/**
* Can the sprite encoder make use of RGBA sprites?
*/
virtual bool Is32BppSupported() = 0;

/**
* Convert a sprite from the loader to our own format.
*/
virtual Sprite *Encode(const SpriteLoader::Sprite *sprite, AllocatorProc *allocator) = 0;

/**
* Get the value which the height and width on a sprite have to be aligned by.
* @return The needed alignment or 0 if any alignment is accepted.
*/
virtual uint GetSpriteAlignment()
{
return 0;
}
};
#endif /* SPRITELOADER_HPP */
@@ -73,6 +73,20 @@ class VideoDriver : public Driver {
return true;
}

/**
* Get whether the mouse cursor is drawn by the video driver.
* @return True if cursor drawing is done by the video driver.
*/
virtual bool UseSystemCursor()
{
return false;
}

/**
* Clear all cached sprites.
*/
virtual void ClearSystemSprites() {}

/**
* Whether the driver has a graphical user interface with the end user.
* Or in other words, whether we should spawn a thread for world generation
@@ -22,12 +22,14 @@
#include "../window_gui.h"
#include "../window_func.h"
#include "../framerate_type.h"
#include "../table/sprites.h"
#include "win32_v.h"
#include <windows.h>
#include <imm.h>
#include <mutex>
#include <condition_variable>
#include <algorithm>
#include <map>

#include "../safeguards.h"

@@ -74,6 +76,10 @@ static std::condition_variable_any *_draw_signal = nullptr;
static volatile bool _draw_continue;
/** Local copy of the palette for use in the drawing thread. */
static Palette _local_palette;
/** Sprite mouse cursors converted to Win32 cursors */
static std::map<SpriteID, HCURSOR> _system_cursor_map;
/** Use a system cursor instead of custom/sprite cursor? */
static bool _use_system_cursor = false;

static void MakePalette()
{
@@ -112,12 +118,239 @@ static void UpdatePalette(HDC dc, uint start, uint count)
SetDIBColorTable(dc, start, count, rgb);
}

class DibSectionSpriteEncoder : public SpriteEncoder {
private:
int cursor_max_x;
int cursor_max_y;
BITMAPINFO bmi;

public:
DibSectionSpriteEncoder()
{
this->cursor_max_x = GetSystemMetrics(SM_CXCURSOR);
this->cursor_max_y = GetSystemMetrics(SM_CYCURSOR);

bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biWidth = this->cursor_max_x;
bmi.bmiHeader.biHeight = -this->cursor_max_y;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = 0;
bmi.bmiHeader.biXPelsPerMeter = 3780; /* 96 dpi / 25.4 mm/inch * 1000 mm/m */
bmi.bmiHeader.biYPelsPerMeter = 3780;
bmi.bmiHeader.biClrUsed = 0;
bmi.bmiHeader.biClrImportant = 0;
}

bool Is32BppSupported() override
{
return true;
}

Sprite *Encode(const SpriteLoader::Sprite *sprite_allzooms, AllocatorProc *allocator) override
{
const SpriteLoader::Sprite &sprite = sprite_allzooms[ZOOM_LVL_OUT_4X];

if (sprite.width > this->cursor_max_x || sprite.height > this->cursor_max_y) return nullptr;
if ((sprite.colours & (SCC_PAL | SCC_RGB)) == 0) return nullptr;

const uint32 mOpaque = 0;
const uint32 mTransparent = 0xFFFFFFFF;

/* Bitmap for colour bits */
uint32 *cbits;
HBITMAP cbmp = CreateDIBSection(GetDC(_wnd.main_wnd), &bmi, DIB_RGB_COLORS, (void **)&cbits, nullptr, 0);
MemSetT<uint32>(cbits, 0, this->cursor_max_x * this->cursor_max_y);

/* Bitmap for mask bits */
uint32 *mbits;
HBITMAP mbmp = CreateDIBSection(GetDC(_wnd.main_wnd), &bmi, DIB_RGB_COLORS, (void **)&mbits, nullptr, 0);
MemSetT<uint32>(mbits, mTransparent, this->cursor_max_x * this->cursor_max_y);

/* Fill pixels */
if ((sprite.colours & (SCC_RGB | SCC_ALPHA)) == (SCC_RGB | SCC_ALPHA)) {
for (uint y = 0; y < sprite.height; ++y) {
for (uint x = 0; x < sprite.width; ++x) {
SpriteLoader::CommonPixel &src = sprite.data[x + y * sprite.width];
uint32 &cdst = cbits[x + y * this->cursor_max_x];
uint32 &mdst = mbits[x + y * this->cursor_max_x];
cdst = src.b | (src.g << 8) | (src.r << 16);
mdst = src.a > 127 ? mTransparent : mOpaque;
}
}
} else if ((sprite.colours & (SCC_RGB | SCC_PAL)) == (SCC_RGB | SCC_PAL)) {
for (uint y = 0; y < sprite.height; ++y) {
for (uint x = 0; x < sprite.width; ++x) {
SpriteLoader::CommonPixel &src = sprite.data[x + y * sprite.width];
uint32 &cdst = cbits[x + y * this->cursor_max_x];
uint32 &mdst = mbits[x + y * this->cursor_max_x];
cdst = src.b | (src.g << 8) | (src.r << 16);
mdst = src.m == 0 ? mTransparent : mOpaque;
}
}
} else if (sprite.colours & SCC_PAL) {
for (uint y = 0; y < sprite.height; ++y) {
for (uint x = 0; x < sprite.width; ++x) {
SpriteLoader::CommonPixel &src = sprite.data[x + y * sprite.width];
uint32 &cdst = cbits[x + y * this->cursor_max_x];
uint32 &mdst = mbits[x + y * this->cursor_max_x];
Colour &c = _cur_palette.palette[src.m];
cdst = c.b | (c.g << 8) | (c.r << 16);
mdst = src.m == 0 ? mTransparent : mOpaque;
}
}
}

/* Create cursor */
ICONINFO iconinfo = {
false,
(DWORD)min(0, -sprite.x_offs), (DWORD)min(0, -sprite.y_offs),
mbmp, cbmp
};
HCURSOR cursor = CreateIconIndirect(&iconinfo);

/*Sprite *dst_sprite = (Sprite *)allocator(sizeof(Sprite) + sizeof(HCURSOR));
dst_sprite->width = bmi.bmiHeader.biWidth;
dst_sprite->height = bmi.bmiHeader.biHeight;
dst_sprite->x_offs = sprite.x_offs;
dst_sprite->y_offs = sprite.y_offs;
(HCURSOR)dst_sprite->data = cursor; */

return (Sprite *)(void *)cursor;
}

HCURSOR MakeCursor(CursorID cursor_sprite)
{
return (HCURSOR)GetRawSprite(cursor_sprite, ST_NORMAL, SimpleSpriteAlloc, this);
}
};

static void RebuildSystemCursorMap()
{
for (std::pair<SpriteID, HCURSOR> mapping : _system_cursor_map) {
ICONINFO iconinfo;
if (GetIconInfo(mapping.second, &iconinfo)) {
DeleteObject(iconinfo.hbmColor);
DeleteObject(iconinfo.hbmMask);
}
DestroyIcon(mapping.second);
}
_system_cursor_map.clear();

_use_system_cursor = true;
if (!_use_system_cursor) return;

static const CursorID all_cursors[] = {
SPR_CURSOR_MOUSE,
SPR_CURSOR_ZZZ,
SPR_CURSOR_BUOY,
SPR_CURSOR_QUERY,
SPR_CURSOR_HQ,
SPR_CURSOR_SHIP_DEPOT,
SPR_CURSOR_SIGN,
SPR_CURSOR_TREE,
SPR_CURSOR_BUY_LAND,
SPR_CURSOR_LEVEL_LAND,
SPR_CURSOR_TOWN,
SPR_CURSOR_INDUSTRY,
SPR_CURSOR_ROCKY_AREA,
SPR_CURSOR_DESERT,
SPR_CURSOR_TRANSMITTER,
SPR_CURSOR_AIRPORT,
SPR_CURSOR_DOCK,
SPR_CURSOR_CANAL,
SPR_CURSOR_LOCK,
SPR_CURSOR_RIVER,
SPR_CURSOR_AQUEDUCT,
SPR_CURSOR_BRIDGE,
SPR_CURSOR_NS_TRACK,
SPR_CURSOR_SWNE_TRACK,
SPR_CURSOR_EW_TRACK,
SPR_CURSOR_NWSE_TRACK,
SPR_CURSOR_NS_MONO,
SPR_CURSOR_SWNE_MONO,
SPR_CURSOR_EW_MONO,
SPR_CURSOR_NWSE_MONO,
SPR_CURSOR_NS_MAGLEV,
SPR_CURSOR_SWNE_MAGLEV,
SPR_CURSOR_EW_MAGLEV,
SPR_CURSOR_NWSE_MAGLEV,
SPR_CURSOR_NS_ELRAIL,
SPR_CURSOR_SWNE_ELRAIL,
SPR_CURSOR_EW_ELRAIL,
SPR_CURSOR_NWSE_ELRAIL,
SPR_CURSOR_RAIL_STATION,
SPR_CURSOR_TUNNEL_RAIL,
SPR_CURSOR_TUNNEL_ELRAIL,
SPR_CURSOR_TUNNEL_MONO,
SPR_CURSOR_TUNNEL_MAGLEV,
SPR_CURSOR_AUTORAIL,
SPR_CURSOR_AUTOELRAIL,
SPR_CURSOR_AUTOMONO,
SPR_CURSOR_AUTOMAGLEV,
SPR_CURSOR_WAYPOINT,
SPR_CURSOR_RAIL_DEPOT,
SPR_CURSOR_ELRAIL_DEPOT,
SPR_CURSOR_MONO_DEPOT,
SPR_CURSOR_MAGLEV_DEPOT,
SPR_CURSOR_CONVERT_RAIL,
SPR_CURSOR_CONVERT_ELRAIL,
SPR_CURSOR_CONVERT_MONO,
SPR_CURSOR_CONVERT_MAGLEV,
SPR_CURSOR_ROAD_NESW,
SPR_CURSOR_ROAD_NWSE,
SPR_CURSOR_AUTOROAD,
SPR_CURSOR_TRAMWAY_NESW,
SPR_CURSOR_TRAMWAY_NWSE,
SPR_CURSOR_AUTOTRAM,
SPR_CURSOR_ROAD_DEPOT,
SPR_CURSOR_BUS_STATION,
SPR_CURSOR_TRUCK_STATION,
SPR_CURSOR_ROAD_TUNNEL,
SPR_CURSOR_CLONE_TRAIN,
SPR_CURSOR_CLONE_ROADVEH,
SPR_CURSOR_CLONE_SHIP,
SPR_CURSOR_CLONE_AIRPLANE,
SPR_CURSOR_DEMOLISH_FIRST,
SPR_CURSOR_DEMOLISH_1,
SPR_CURSOR_DEMOLISH_2,
SPR_CURSOR_DEMOLISH_LAST,
SPR_CURSOR_LOWERLAND_FIRST,
SPR_CURSOR_LOWERLAND_1,
SPR_CURSOR_LOWERLAND_LAST,
SPR_CURSOR_RAISELAND_FIRST,
SPR_CURSOR_RAISELAND_1,
SPR_CURSOR_RAISELAND_LAST,
SPR_CURSOR_PICKSTATION_FIRST,
SPR_CURSOR_PICKSTATION_1,
SPR_CURSOR_PICKSTATION_LAST,
SPR_CURSOR_BUILDSIGNALS_FIRST,
SPR_CURSOR_BUILDSIGNALS_LAST,
};

std::unique_ptr<DibSectionSpriteEncoder> enc(new DibSectionSpriteEncoder);
for (CursorID cursor_sprite : all_cursors) {
_system_cursor_map[cursor_sprite] = enc->MakeCursor(cursor_sprite);
}
}

bool VideoDriver_Win32::ClaimMousePointer()
{
MyShowCursor(false, true);
return true;
}

bool VideoDriver_Win32::UseSystemCursor()
{
return _use_system_cursor && _system_cursor_map.contains(_cursor.sprite_seq[0].sprite);
}

void VideoDriver_Win32::ClearSystemSprites()
{
RebuildSystemCursorMap();
}

struct VkMapping {
byte vk_from;
byte vk_count;
@@ -720,6 +953,15 @@ static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lP
if (!_left_button_down && !_right_button_down) MyShowCursor(true);
return 0;

case WM_SETCURSOR: {
if (!_use_system_cursor) return 0;
if (LOWORD(lParam) != HTCLIENT) return DefWindowProc(hwnd, msg, wParam, lParam);
HCURSOR hc = _system_cursor_map[_cursor.sprite_seq[0].sprite];
if (hc == nullptr) return 0;
SetCursor(hc);
return 1;
}

case WM_MOUSEMOVE: {
int x = (int16)LOWORD(lParam);
int y = (int16)HIWORD(lParam);
@@ -758,7 +1000,7 @@ static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lP
ClientToScreen(hwnd, &pt);
SetCursorPos(pt.x, pt.y);
}
MyShowCursor(false);
MyShowCursor(_use_system_cursor);
HandleMouseEvents();
return 0;
}
@@ -1172,6 +1414,8 @@ void VideoDriver_Win32::MainLoop()
std::thread draw_thread;
std::unique_lock<std::recursive_mutex> draw_lock;

RebuildSystemCursorMap();

if (_draw_threaded) {
/* Initialise the mutex first, because that's the thing we *need*
* directly in the newly created thread. */
@@ -35,6 +35,10 @@ class VideoDriver_Win32 : public VideoDriver {

bool ClaimMousePointer() override;

bool UseSystemCursor() override;

void ClearSystemSprites() override;

void EditBoxLostFocus() override;

const char *GetName() const override { return "win32"; }