/*
Copyright (c) 2013 yvt
This file is part of OpenSpades.
OpenSpades 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.
OpenSpades 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 OpenSpades. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include "AsyncRenderer.h"
#include "IImage.h"
#include "IModel.h"
namespace spades {
namespace client {
#pragma mark - Commands
class Command {
public:
uint16_t cmdSize;
virtual void Execute(IRenderer *) = 0;
};
namespace rcmds {
class Init : public Command {
public:
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
r->Init();
}
};
class Shutdown : public Command {
public:
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
r->Shutdown();
}
};
class SetGameMap : public Command {
public:
GameMap *map;
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
r->SetGameMap(map);
}
};
class SetFogDistance : public Command {
public:
float v;
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
r->SetFogDistance(v);
}
};
class SetFogColor : public Command {
public:
Vector3 v;
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
r->SetFogColor(v);
}
};
class StartScene : public Command {
public:
SceneDefinition def;
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
r->StartScene(def);
}
};
class AddLight : public Command {
public:
IImage *img;
DynamicLightParam def;
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
if (img) {
def.image = img;
img = NULL;
}
try {
r->AddLight(def);
} catch (...) {
if (img)
img->Release();
throw;
}
if (img) {
img->Release();
}
}
};
class RenderModel : public Command {
public:
IModel *model;
ModelRenderParam param;
virtual void Execute(IRenderer *r) {
try {
r->RenderModel(model, param);
} catch (...) {
model->Release();
model = NULL;
throw;
}
model->Release();
model = NULL;
}
};
class AddDebugLine : public Command {
public:
Vector3 a, b;
Vector4 color;
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
r->AddDebugLine(a, b, color);
}
};
class AddSprite : public Command {
public:
IImage *img;
Vector3 center;
float radius, rotation;
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
try {
r->AddSprite(img, center, radius, rotation);
} catch (...) {
img->Release();
img = NULL;
throw;
}
img->Release();
img = NULL;
}
};
class AddLongSprite : public Command {
public:
IImage *img;
Vector3 p1, p2;
float radius;
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
try {
r->AddLongSprite(img, p1, p2, radius);
} catch (...) {
img->Release();
img = NULL;
throw;
}
img->Release();
img = NULL;
}
};
class EndScene : public Command {
public:
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
r->EndScene();
}
};
class MultiplyScreenColor : public Command {
public:
Vector3 v;
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
r->MultiplyScreenColor(v);
}
};
class SetColor : public Command {
public:
Vector4 v;
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
r->SetColor(v);
}
};
class SetColorAlphaPremultiplied : public Command {
public:
Vector4 v;
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
r->SetColorAlphaPremultiplied(v);
}
};
class DrawImage : public Command {
public:
IImage *img;
Vector2 outTopLeft;
Vector2 outTopRight;
Vector2 outBottomLeft;
AABB2 inRect;
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
try {
r->DrawImage(img, outTopLeft, outTopRight, outBottomLeft, inRect);
} catch (...) {
if (img)
img->Release();
img = NULL;
throw;
}
if (img)
img->Release();
img = NULL;
}
};
class DrawFlatGameMap : public Command {
public:
AABB2 outRect, inRect;
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
r->DrawFlatGameMap(outRect, inRect);
}
};
class FrameDone : public Command {
public:
AsyncRenderer *arenderer;
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
r->FrameDone();
}
};
class Flip : public Command {
public:
virtual void Execute(IRenderer *r) {
SPADES_MARK_FUNCTION();
r->Flip();
}
};
};
#pragma mark - Command Buffer
class AsyncRenderer::CmdBufferGenerator {
public:
std::vector buffer;
void Clear() { buffer.clear(); }
template T *AllocCommand() {
SPADES_MARK_FUNCTION();
size_t size = sizeof(T);
size_t pos = buffer.size();
buffer.resize(buffer.size() + size);
void *dat = buffer.data() + pos;
T *cmd = new (dat) T;
cmd->cmdSize = sizeof(T);
return cmd;
}
};
class AsyncRenderer::CmdBufferReader {
std::vector buffer;
size_t pos;
public:
CmdBufferReader(const std::vector &buf) : buffer(buf), pos(0) {}
Command *NextCommand() {
SPADES_MARK_FUNCTION();
if (pos >= buffer.size()) {
return NULL;
}
Command *cmd = reinterpret_cast(buffer.data() + pos);
pos += cmd->cmdSize;
if (pos > buffer.size()) {
SPRaise("Truncated render command buffer");
}
return cmd;
}
};
class AsyncRenderer::RenderDispatch : public ConcurrentDispatch {
AsyncRenderer *renderer;
public:
std::vector buffer;
RenderDispatch(AsyncRenderer *renderer) : renderer(renderer) {}
virtual void Run() {
SPADES_MARK_FUNCTION();
CmdBufferReader reader(buffer);
Command *cmd;
while ((cmd = reader.NextCommand()) != NULL) {
cmd->Execute(renderer->base);
}
}
};
AsyncRenderer::AsyncRenderer(IRenderer *base, DispatchQueue *queue)
: base(base), queue(queue) {
generator = new CmdBufferGenerator();
dispatch = new RenderDispatch(this);
}
AsyncRenderer::~AsyncRenderer() {
Sync();
delete dispatch;
delete generator;
}
void AsyncRenderer::FlushCommands() {
if (generator->buffer.empty())
return;
dispatch->Join();
dispatch->buffer = std::move(generator->buffer);
generator->Clear();
dispatch->StartOn(queue);
}
void AsyncRenderer::Sync() {
FlushCommands();
dispatch->Join();
}
#pragma mark - General COmmands
IImage *AsyncRenderer::RegisterImage(const char *filename) {
SPADES_MARK_FUNCTION();
class RegisterImageDispatch : public ConcurrentDispatch {
IRenderer *base;
const char *fn;
IImage *result;
std::string error;
public:
RegisterImageDispatch(IRenderer *base, const char *fn)
: base(base), fn(fn), result(NULL) {}
virtual void Run() {
try {
result = base->RegisterImage(fn);
} catch (const std::exception &ex) {
error = ex.what();
}
}
IImage *GetResult() {
if (!error.empty()) {
SPRaise("Error while RegisterImageDispatch:\n%s", error.c_str());
} else {
return result;
}
}
};
std::map::iterator it = images.find(filename);
if (it == images.end()) {
FlushCommands();
RegisterImageDispatch dispatch(base, filename);
dispatch.StartOn(queue);
dispatch.Join();
IImage *img = dispatch.GetResult();
images[filename] = img;
img->AddRef();
return img;
}
it->second->AddRef();
return it->second;
}
void AsyncRenderer::Init() {
SPADES_MARK_FUNCTION();
generator->AllocCommand();
}
void AsyncRenderer::Shutdown() {
SPADES_MARK_FUNCTION();
generator->AllocCommand();
}
IImage *AsyncRenderer::CreateImage(spades::Bitmap *bmp) {
SPADES_MARK_FUNCTION();
class CreateImageDispatch : public ConcurrentDispatch {
IRenderer *base;
Bitmap *bmp;
IImage *result;
std::string error;
public:
CreateImageDispatch(IRenderer *base, Bitmap *bmp)
: base(base), bmp(bmp), result(NULL) {}
virtual void Run() {
try {
result = base->CreateImage(bmp);
} catch (const std::exception &ex) {
error = ex.what();
}
}
IImage *GetResult() {
if (!error.empty()) {
SPRaise("Error while CreateImageDispatch:\n%s", error.c_str());
} else {
return result;
}
}
};
FlushCommands();
CreateImageDispatch dispatch(base, bmp);
dispatch.StartOn(queue);
dispatch.Join();
IImage *img = dispatch.GetResult();
return img;
}
IModel *AsyncRenderer::RegisterModel(const char *filename) {
SPADES_MARK_FUNCTION();
class RegisterModelDispatch : public ConcurrentDispatch {
IRenderer *base;
const char *fn;
IModel *result;
std::string error;
public:
RegisterModelDispatch(IRenderer *base, const char *fn)
: base(base), fn(fn), result(NULL) {}
virtual void Run() {
try {
result = base->RegisterModel(fn);
} catch (const std::exception &ex) {
error = ex.what();
}
}
IModel *GetResult() {
if (!error.empty()) {
SPRaise("Error while RegisterImageDispatch:\n%s", error.c_str());
} else {
return result;
}
}
};
std::map::iterator it = models.find(filename);
if (it == models.end()) {
FlushCommands();
RegisterModelDispatch dispatch(base, filename);
dispatch.StartOn(queue);
dispatch.Join();
IModel *img = dispatch.GetResult();
models[filename] = img;
img->AddRef();
return img;
}
it->second->AddRef();
return it->second;
}
IModel *AsyncRenderer::CreateModel(spades::VoxelModel *bmp) {
SPADES_MARK_FUNCTION();
class CreateModelDispatch : public ConcurrentDispatch {
IRenderer *base;
VoxelModel *bmp;
IModel *result;
std::string error;
public:
CreateModelDispatch(IRenderer *base, VoxelModel *bmp)
: base(base), bmp(bmp), result(NULL) {}
virtual void Run() {
try {
result = base->CreateModel(bmp);
} catch (const std::exception &ex) {
error = ex.what();
}
}
IModel *GetResult() {
if (!error.empty()) {
SPRaise("Error while CreateImageDispatch:\n%s", error.c_str());
} else {
return result;
}
}
};
FlushCommands();
CreateModelDispatch dispatch(base, bmp);
dispatch.StartOn(queue);
dispatch.Join();
IModel *img = dispatch.GetResult();
return img;
}
void AsyncRenderer::SetGameMap(GameMap *gm) {
SPADES_MARK_FUNCTION();
rcmds::SetGameMap *cmd = generator->AllocCommand();
cmd->map = gm;
Sync();
}
void AsyncRenderer::SetFogDistance(float distance) {
SPADES_MARK_FUNCTION();
rcmds::SetFogDistance *cmd = generator->AllocCommand();
cmd->v = distance;
}
void AsyncRenderer::SetFogColor(Vector3 v) {
SPADES_MARK_FUNCTION();
rcmds::SetFogColor *cmd = generator->AllocCommand();
cmd->v = v;
}
void AsyncRenderer::StartScene(const SceneDefinition &def) {
SPADES_MARK_FUNCTION();
rcmds::StartScene *cmd = generator->AllocCommand();
cmd->def = def;
}
void AsyncRenderer::AddLight(const client::DynamicLightParam &light) {
SPADES_MARK_FUNCTION();
rcmds::AddLight *cmd = generator->AllocCommand();
if (light.image) {
cmd->img = light.image;
cmd->img->AddRef();
} else {
cmd->img = NULL;
}
cmd->def = light;
}
void AsyncRenderer::RenderModel(IModel *m, const ModelRenderParam &p) {
SPADES_MARK_FUNCTION();
rcmds::RenderModel *cmd = generator->AllocCommand();
cmd->model = m;
m->AddRef();
cmd->param = p;
}
void AsyncRenderer::AddDebugLine(Vector3 a, Vector3 b, Vector4 color) {
SPADES_MARK_FUNCTION();
rcmds::AddDebugLine *cmd = generator->AllocCommand();
cmd->a = a;
cmd->b = b;
cmd->color = color;
}
void AsyncRenderer::AddSprite(IImage *img, Vector3 center, float radius, float rotation) {
SPADES_MARK_FUNCTION();
rcmds::AddSprite *cmd = generator->AllocCommand();
cmd->img = img;
img->AddRef();
cmd->center = center;
cmd->radius = radius;
cmd->rotation = rotation;
}
void AsyncRenderer::AddLongSprite(IImage *img, Vector3 p1, Vector3 p2, float radius) {
SPADES_MARK_FUNCTION();
rcmds::AddLongSprite *cmd = generator->AllocCommand();
cmd->img = img;
img->AddRef();
cmd->p1 = p1;
cmd->p2 = p2;
cmd->radius = radius;
}
void AsyncRenderer::EndScene() {
SPADES_MARK_FUNCTION();
generator->AllocCommand();
}
void AsyncRenderer::MultiplyScreenColor(Vector3 v) {
SPADES_MARK_FUNCTION();
rcmds::MultiplyScreenColor *cmd = generator->AllocCommand();
cmd->v = v;
}
void AsyncRenderer::SetColor(Vector4 c) {
SPADES_MARK_FUNCTION();
rcmds::SetColor *cmd = generator->AllocCommand();
cmd->v = c;
}
void AsyncRenderer::SetColorAlphaPremultiplied(Vector4 c) {
SPADES_MARK_FUNCTION();
rcmds::SetColorAlphaPremultiplied *cmd =
generator->AllocCommand();
cmd->v = c;
}
void AsyncRenderer::DrawImage(client::IImage *image, const spades::Vector2 &outTopLeft) {
SPADES_MARK_FUNCTION();
DrawImage(image, AABB2(outTopLeft.x, outTopLeft.y, image ? image->GetWidth() : 0,
image ? image->GetHeight() : 0),
AABB2(0, 0, image ? image->GetWidth() : 0, image ? image->GetHeight() : 0));
}
void AsyncRenderer::DrawImage(client::IImage *image, const spades::AABB2 &outRect) {
SPADES_MARK_FUNCTION();
DrawImage(image, outRect,
AABB2(0, 0, image ? image->GetWidth() : 0, image ? image->GetHeight() : 0));
}
void AsyncRenderer::DrawImage(client::IImage *image, const spades::Vector2 &outTopLeft,
const spades::AABB2 &inRect) {
SPADES_MARK_FUNCTION();
DrawImage(image,
AABB2(outTopLeft.x, outTopLeft.y, inRect.GetWidth(), inRect.GetHeight()),
inRect);
}
void AsyncRenderer::DrawImage(client::IImage *image, const spades::AABB2 &outRect,
const spades::AABB2 &inRect) {
SPADES_MARK_FUNCTION();
DrawImage(image, Vector2::Make(outRect.GetMinX(), outRect.GetMinY()),
Vector2::Make(outRect.GetMaxX(), outRect.GetMinY()),
Vector2::Make(outRect.GetMinX(), outRect.GetMaxY()), inRect);
}
void AsyncRenderer::DrawImage(client::IImage *image, const spades::Vector2 &outTopLeft,
const spades::Vector2 &outTopRight,
const spades::Vector2 &outBottomLeft,
const spades::AABB2 &inRect) {
SPADES_MARK_FUNCTION();
rcmds::DrawImage *cmd = generator->AllocCommand();
cmd->img = image;
if (image)
image->AddRef();
cmd->outTopLeft = outTopLeft;
cmd->outTopRight = outTopRight;
cmd->outBottomLeft = outBottomLeft;
cmd->inRect = inRect;
}
void AsyncRenderer::DrawFlatGameMap(const spades::AABB2 &outRect,
const spades::AABB2 &inRect) {
SPADES_MARK_FUNCTION();
rcmds::DrawFlatGameMap *cmd = generator->AllocCommand();
cmd->outRect = outRect;
cmd->inRect = inRect;
}
void AsyncRenderer::FrameDone() {
generator->AllocCommand()->arenderer = this;
}
void AsyncRenderer::Flip() {
generator->AllocCommand();
FlushCommands();
}
Bitmap *AsyncRenderer::ReadBitmap() {
FlushCommands();
class ReadBitmapDispatch : public ConcurrentDispatch {
IRenderer *base;
Bitmap *result;
std::string error;
public:
ReadBitmapDispatch(IRenderer *base) : base(base), result(NULL) {}
virtual void Run() {
try {
result = base->ReadBitmap();
} catch (const std::exception &ex) {
error = ex.what();
}
}
Bitmap *GetResult() {
if (!error.empty()) {
SPRaise("Error while CreateImageDispatch:\n%s", error.c_str());
} else {
return result;
}
}
};
ReadBitmapDispatch disp(base);
disp.StartOn(queue);
disp.Join();
return disp.GetResult();
}
float AsyncRenderer::ScreenWidth() { return base->ScreenWidth(); }
float AsyncRenderer::ScreenHeight() { return base->ScreenHeight(); }
}
}