/* 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(); } } }