680 lines
16 KiB
C++
680 lines
16 KiB
C++
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#include "AsyncRenderer.h"
|
|
#include <new>
|
|
#include "../Core/Exception.h"
|
|
#include "../Core/Debug.h"
|
|
#include "../Core/Mutex.h"
|
|
#include "../Core/AutoLocker.h"
|
|
#include "IImage.h"
|
|
#include "IModel.h"
|
|
#include <stdint.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) {
|
|
r->Init();
|
|
}
|
|
};
|
|
class Shutdown: public Command {
|
|
public:
|
|
virtual void Execute(IRenderer *r) {
|
|
r->Shutdown();
|
|
}
|
|
};
|
|
class SetGameMap: public Command {
|
|
public:
|
|
GameMap *map;
|
|
virtual void Execute(IRenderer *r) {
|
|
r->SetGameMap(map);
|
|
}
|
|
};
|
|
class SetFogDistance: public Command {
|
|
public:
|
|
float v;
|
|
virtual void Execute(IRenderer *r){
|
|
r->SetFogDistance(v);
|
|
}
|
|
};
|
|
class SetFogColor: public Command {
|
|
public:
|
|
Vector3 v;
|
|
virtual void Execute(IRenderer *r){
|
|
r->SetFogColor(v);
|
|
}
|
|
};
|
|
class StartScene: public Command {
|
|
public:
|
|
SceneDefinition def;
|
|
virtual void Execute(IRenderer *r){
|
|
r->StartScene(def);
|
|
}
|
|
};
|
|
class AddLight: public Command {
|
|
public:
|
|
IImage *img;
|
|
DynamicLightParam def;
|
|
virtual void Execute(IRenderer *r){
|
|
if(img){
|
|
def.image = img; img = NULL;
|
|
}
|
|
r->AddLight(def);
|
|
if(img){
|
|
img->Release();
|
|
}
|
|
}
|
|
};
|
|
class RenderModel: public Command {
|
|
public:
|
|
IModel *model;
|
|
ModelRenderParam param;
|
|
virtual void Execute(IRenderer *r){
|
|
r->RenderModel(model, param);
|
|
model->Release(); model = NULL;
|
|
}
|
|
};
|
|
class AddDebugLine: public Command {
|
|
public:
|
|
Vector3 a, b;
|
|
Vector4 color;
|
|
virtual void Execute(IRenderer *r){
|
|
r->AddDebugLine(a, b, color);
|
|
}
|
|
};
|
|
class AddSprite: public Command {
|
|
public:
|
|
IImage *img;
|
|
Vector3 center;
|
|
float radius, rotation;
|
|
virtual void Execute(IRenderer *r){
|
|
r->AddSprite(img, center, radius, rotation);
|
|
img->Release(); img = NULL;
|
|
}
|
|
};
|
|
class AddLongSprite: public Command {
|
|
public:
|
|
IImage *img;
|
|
Vector3 p1, p2;
|
|
float radius;
|
|
virtual void Execute(IRenderer *r){
|
|
r->AddLongSprite(img, p1, p2, radius);
|
|
img->Release(); img = NULL;
|
|
}
|
|
};
|
|
class EndScene: public Command {
|
|
public:
|
|
virtual void Execute(IRenderer *r){
|
|
r->EndScene();
|
|
}
|
|
};
|
|
class MultiplyScreenColor: public Command {
|
|
public:
|
|
Vector3 v;
|
|
virtual void Execute(IRenderer *r){
|
|
r->MultiplyScreenColor(v);
|
|
}
|
|
};
|
|
class SetColor: public Command {
|
|
public:
|
|
Vector4 v;
|
|
virtual void Execute(IRenderer *r){
|
|
r->SetColor(v);
|
|
}
|
|
};
|
|
class SetColorAlphaPremultiplied: public Command {
|
|
public:
|
|
Vector4 v;
|
|
virtual void Execute(IRenderer *r){
|
|
r->SetColorAlphaPremultiplied(v);
|
|
}
|
|
};
|
|
class DrawImage: public Command {
|
|
public:
|
|
IImage *img;
|
|
Vector2 outTopLeft;
|
|
Vector2 outTopRight;
|
|
Vector2 outBottomLeft;
|
|
AABB2 inRect;
|
|
virtual void Execute(IRenderer *r){
|
|
r->DrawImage(img, outTopLeft, outTopRight, outBottomLeft,
|
|
inRect);
|
|
img->Release(); img = NULL;
|
|
}
|
|
};
|
|
class DrawFlatGameMap: public Command {
|
|
public:
|
|
AABB2 outRect, inRect;
|
|
virtual void Execute(IRenderer *r){
|
|
r->DrawFlatGameMap(outRect, inRect);
|
|
}
|
|
};
|
|
class FrameDone: public Command {
|
|
public:
|
|
AsyncRenderer *arenderer;
|
|
virtual void Execute(IRenderer *r){
|
|
r->FrameDone();
|
|
|
|
}
|
|
};
|
|
class Flip: public Command {
|
|
public:
|
|
virtual void Execute(IRenderer *r){
|
|
r->Flip();
|
|
}
|
|
};
|
|
};
|
|
|
|
#pragma mark - Command Buffer
|
|
|
|
class AsyncRenderer::CmdBufferGenerator {
|
|
public:
|
|
std::vector<char> buffer;
|
|
|
|
void Clear(){
|
|
buffer.clear();
|
|
}
|
|
|
|
template<typename T>
|
|
T *AllocCommand() {
|
|
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<char> buffer;
|
|
size_t pos;
|
|
public:
|
|
CmdBufferReader(const std::vector<char>& buf):
|
|
buffer(buf), pos(0){
|
|
}
|
|
Command *NextCommand() {
|
|
if(pos >= buffer.size()){
|
|
return NULL;
|
|
}
|
|
Command *cmd = reinterpret_cast<Command *>(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<char> buffer;
|
|
|
|
RenderDispatch(AsyncRenderer *renderer):
|
|
renderer(renderer){
|
|
|
|
}
|
|
virtual void Run() {
|
|
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 = 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<std::string, IImage *>::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;
|
|
}
|
|
return it->second;
|
|
}
|
|
|
|
void AsyncRenderer::Init() {
|
|
SPADES_MARK_FUNCTION();
|
|
generator->AllocCommand<rcmds::Init>();
|
|
}
|
|
|
|
void AsyncRenderer::Shutdown() {
|
|
SPADES_MARK_FUNCTION();
|
|
generator->AllocCommand<rcmds::Shutdown>();
|
|
}
|
|
|
|
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<std::string, IModel *>::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;
|
|
}
|
|
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<rcmds::SetGameMap>();
|
|
cmd->map = gm;
|
|
Sync();
|
|
}
|
|
|
|
void AsyncRenderer::SetFogDistance(float distance) {
|
|
SPADES_MARK_FUNCTION();
|
|
rcmds::SetFogDistance *cmd = generator->AllocCommand<rcmds::SetFogDistance>();
|
|
cmd->v = distance;
|
|
}
|
|
void AsyncRenderer::SetFogColor(Vector3 v) {
|
|
SPADES_MARK_FUNCTION();
|
|
rcmds::SetFogColor *cmd = generator->AllocCommand<rcmds::SetFogColor>();
|
|
cmd->v = v;
|
|
}
|
|
|
|
void AsyncRenderer::StartScene(const SceneDefinition& def) {
|
|
SPADES_MARK_FUNCTION();
|
|
rcmds::StartScene *cmd = generator->AllocCommand<rcmds::StartScene>();
|
|
cmd->def = def;
|
|
}
|
|
|
|
void AsyncRenderer::AddLight(const client::DynamicLightParam& light) {
|
|
SPADES_MARK_FUNCTION();
|
|
rcmds::AddLight *cmd = generator->AllocCommand<rcmds::AddLight>();
|
|
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<rcmds::RenderModel>();
|
|
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<rcmds::AddDebugLine>();
|
|
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<rcmds::AddSprite>();
|
|
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<rcmds::AddLongSprite>();
|
|
cmd->img = img;
|
|
img->AddRef();
|
|
cmd->p1 = p1;
|
|
cmd->p2 = p2;
|
|
cmd->radius = radius;
|
|
}
|
|
|
|
void AsyncRenderer::EndScene() {
|
|
SPADES_MARK_FUNCTION();
|
|
generator->AllocCommand<rcmds::EndScene>();
|
|
}
|
|
|
|
void AsyncRenderer::MultiplyScreenColor(Vector3 v) {
|
|
SPADES_MARK_FUNCTION();
|
|
rcmds::MultiplyScreenColor *cmd = generator->AllocCommand<rcmds::MultiplyScreenColor>();
|
|
cmd->v = v;
|
|
}
|
|
|
|
void AsyncRenderer::SetColor(Vector4 c) {
|
|
SPADES_MARK_FUNCTION();
|
|
rcmds::SetColor *cmd = generator->AllocCommand<rcmds::SetColor>();
|
|
cmd->v = c;
|
|
}
|
|
|
|
void AsyncRenderer::SetColorAlphaPremultiplied(Vector4 c) {
|
|
SPADES_MARK_FUNCTION();
|
|
rcmds::SetColorAlphaPremultiplied *cmd = generator->AllocCommand<rcmds::SetColorAlphaPremultiplied>();
|
|
cmd->v = c;
|
|
}
|
|
|
|
void AsyncRenderer::DrawImage(client::IImage *image,
|
|
const spades::Vector2 &outTopLeft) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
DrawImage(image,
|
|
AABB2(outTopLeft.x, outTopLeft.y,
|
|
image->GetWidth(),
|
|
image->GetHeight()),
|
|
AABB2(0, 0,
|
|
image->GetWidth(),
|
|
image->GetHeight()));
|
|
}
|
|
|
|
void AsyncRenderer::DrawImage(client::IImage *image, const spades::AABB2 &outRect) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
DrawImage(image,
|
|
outRect,
|
|
AABB2(0, 0,
|
|
image->GetWidth(),
|
|
image->GetHeight()));
|
|
}
|
|
|
|
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();
|
|
|
|
if(!image){
|
|
SPInvalidArgument("image");
|
|
}
|
|
|
|
rcmds::DrawImage *cmd = generator->AllocCommand<rcmds::DrawImage>();
|
|
cmd->img = 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<rcmds::DrawFlatGameMap>();
|
|
cmd->outRect = outRect;
|
|
cmd->inRect = inRect;
|
|
}
|
|
|
|
void AsyncRenderer::FrameDone() {
|
|
generator->AllocCommand<rcmds::FrameDone>()->arenderer = this;
|
|
}
|
|
|
|
void AsyncRenderer::Flip() {
|
|
generator->AllocCommand<rcmds::Flip>();
|
|
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();
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|