mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-06 05:37:48 +00:00
GEEKING
This commit is contained in:
@@ -0,0 +1,369 @@
|
||||
#include "StdAfx.h"
|
||||
#include "ThumbnailGenerator.h"
|
||||
|
||||
#include "v8datamodel/lighting.h"
|
||||
#include "v8datamodel/ContentProvider.h"
|
||||
#include "v8datamodel/TextureContentProvider.h"
|
||||
#include "v8datamodel/MeshContentProvider.h"
|
||||
#include "v8datamodel/SolidModelContentProvider.h"
|
||||
#include "g3d/BinaryOutput.h"
|
||||
#include "g3d/GImage.h"
|
||||
#include "v8DataModel/DataModel.h"
|
||||
#include "v8DataModel/workspace.h"
|
||||
#include "util/base64.hpp"
|
||||
#include "util/StandardOut.h"
|
||||
#include "gui/guidraw.h"
|
||||
#include <sstream>
|
||||
#include "LogManager.h"
|
||||
#include "GfxBase/RenderSettings.h"
|
||||
#include "DummyWindow.h"
|
||||
#include "util/FileSystem.h"
|
||||
#include "reflection/property.h"
|
||||
#include "v8datamodel/Stats.h"
|
||||
#include "v8datamodel/FastLogSettings.h"
|
||||
#include "GfxBase/ViewBase.h"
|
||||
|
||||
using namespace RBX;
|
||||
|
||||
struct ThumbnailRenderSettings : public RBX::CRenderSettings
|
||||
{
|
||||
ThumbnailRenderSettings()
|
||||
{
|
||||
antialiasingMode = CRenderSettings::AntialiasingOn;
|
||||
eagerBulkExecution = true;
|
||||
enableFRM = false;
|
||||
}
|
||||
};
|
||||
|
||||
struct ThumbnailRenderRequest
|
||||
{
|
||||
ThumbnailGenerator* generator;
|
||||
|
||||
std::string fileType;
|
||||
int cx;
|
||||
int cy;
|
||||
bool hideSky;
|
||||
bool crop;
|
||||
|
||||
std::string* strOutput;
|
||||
std::string* errorOutput;
|
||||
|
||||
RBX::CEvent* doneEvent;
|
||||
};
|
||||
|
||||
static rbx::safe_queue<ThumbnailRenderRequest> gThumbRenderQueue;
|
||||
static RBX::CEvent gThumbRenderQueueNotEmpty(/* manualReset= */ false);
|
||||
|
||||
static boost::scoped_ptr<boost::thread> gThumbRenderThread;
|
||||
static boost::once_flag gThumbRenderThreadInit = BOOST_ONCE_INIT;
|
||||
|
||||
static void thumbRenderWorker()
|
||||
{
|
||||
// Create a dummy 1x1 window that holds the GL context and Ogre state alive
|
||||
DummyWindow dummyWindow(1, 1);
|
||||
boost::scoped_ptr<RBX::ViewBase> dummyView;
|
||||
ThumbnailRenderSettings dummySettings;
|
||||
|
||||
OSContext dummyContext;
|
||||
dummyContext.hWnd = dummyWindow.handle;
|
||||
dummyContext.width = 1;
|
||||
dummyContext.height = 1;
|
||||
|
||||
ViewBase::InitPluginModules();
|
||||
|
||||
dummyView.reset(ViewBase::CreateView(CRenderSettings::OpenGL, &dummyContext, &dummySettings));
|
||||
|
||||
while (true)
|
||||
{
|
||||
gThumbRenderQueueNotEmpty.Wait();
|
||||
|
||||
ThumbnailRenderRequest request;
|
||||
while (gThumbRenderQueue.pop_if_present(request))
|
||||
{
|
||||
RBXASSERT(request.generator && request.strOutput && request.errorOutput && request.doneEvent);
|
||||
|
||||
try
|
||||
{
|
||||
// The requesting thread holds a lock for us and waits for doneEvent to be set
|
||||
DataModel::scoped_write_transfer writeTransfer(request.generator);
|
||||
|
||||
// filetype==OBJ
|
||||
if (strcmpi(request.fileType.c_str(), "obj") == 0)
|
||||
{
|
||||
request.generator->exportScene(dummyView.get(), request.strOutput);
|
||||
}
|
||||
else
|
||||
{
|
||||
request.generator->renderThumb(dummyView.get(), dummyWindow.handle, request.fileType, request.cx, request.cy, request.hideSky, request.crop, request.strOutput);
|
||||
}
|
||||
|
||||
*request.errorOutput = std::string();
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
*request.errorOutput = std::string("renderThumb failed: ") + e.what();
|
||||
}
|
||||
|
||||
request.doneEvent->Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void thumbRenderInit()
|
||||
{
|
||||
RBXASSERT(!gThumbRenderThread);
|
||||
|
||||
gThumbRenderThread.reset(new boost::thread(thumbRenderWorker));
|
||||
}
|
||||
|
||||
const char* const sThumbnailGenerator = "ThumbnailGenerator";
|
||||
|
||||
REFLECTION_BEGIN();
|
||||
static RBX::Reflection::BoundFuncDesc<ThumbnailGenerator, shared_ptr<const RBX::Reflection::Tuple>(std::string, int, int, bool, bool)> clickFunction(&ThumbnailGenerator::click, "Click", "fileType", "width", "height", "hideSky", "crop", false, RBX::Security::LocalUser);
|
||||
static RBX::Reflection::BoundFuncDesc<ThumbnailGenerator, shared_ptr<const RBX::Reflection::Tuple>(std::string, std::string, int, int)> clickTextureFunction(&ThumbnailGenerator::clickTexture, "ClickTexture", "textureId", "fileType", "width", "height", RBX::Security::LocalUser);
|
||||
RBX::Reflection::BoundProp<int> prop_HeadColor("GraphicsMode", "Settings", &ThumbnailGenerator::graphicsMode);
|
||||
REFLECTION_END();
|
||||
|
||||
volatile long ThumbnailGenerator::totalCount = 0;
|
||||
|
||||
ThumbnailGenerator::ThumbnailGenerator(void)
|
||||
: graphicsMode(0)
|
||||
{
|
||||
}
|
||||
|
||||
ThumbnailGenerator::~ThumbnailGenerator(void)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void ThumbnailGenerator::configureCaches()
|
||||
{
|
||||
if(RBX::ContentProvider* contentProvider = RBX::ServiceProvider::create<RBX::ContentProvider>(this)){
|
||||
contentProvider->setCacheSize(INT_MAX);
|
||||
}
|
||||
if(RBX::TextureContentProvider* textureContentProvider = RBX::ServiceProvider::create<RBX::TextureContentProvider>(this)){
|
||||
textureContentProvider->setCacheSize(INT_MAX);
|
||||
textureContentProvider->setImmediateMode();
|
||||
}
|
||||
if(RBX::MeshContentProvider* meshContentProvider = RBX::ServiceProvider::create<RBX::MeshContentProvider>(this)){
|
||||
meshContentProvider->setCacheSize(INT_MAX);
|
||||
meshContentProvider->setImmediateMode();
|
||||
}
|
||||
|
||||
if(RBX::SolidModelContentProvider* solidModelContentProvider = RBX::ServiceProvider::create<RBX::SolidModelContentProvider>(this)){
|
||||
solidModelContentProvider->setCacheSize(INT_MAX);
|
||||
solidModelContentProvider->setImmediateMode();
|
||||
}
|
||||
}
|
||||
|
||||
void ReadAccessKey();
|
||||
|
||||
shared_ptr<const RBX::Reflection::Tuple> ThumbnailGenerator::clickTexture(std::string textureId, std::string fileType, int cx, int cy)
|
||||
{
|
||||
::InterlockedIncrement(&totalCount);
|
||||
try
|
||||
{
|
||||
ReadAccessKey();
|
||||
|
||||
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "ThumbnailGenerator::clickTexture(%s, %s, %d, %d)", textureId.c_str(), fileType.c_str(), cx, cy);
|
||||
|
||||
configureCaches();
|
||||
RBX::ContentProvider* contentProvider = RBX::ServiceProvider::create<RBX::ContentProvider>(this);
|
||||
|
||||
G3D::BinaryOutput binaryOutput;
|
||||
binaryOutput.setEndian(G3D::G3D_LITTLE_ENDIAN);
|
||||
|
||||
const G3D::GImage::Format format = G3D::GImage::stringToFormat(fileType);
|
||||
|
||||
G3D::GImage image;
|
||||
|
||||
{
|
||||
boost::shared_ptr<const std::string> content = contentProvider->getContentString(RBX::ContentId(textureId));
|
||||
|
||||
G3D::BinaryInput binaryInput(reinterpret_cast<const unsigned char*>(content->data()), content->size(), G3D::G3D_LITTLE_ENDIAN, false, false);
|
||||
|
||||
image.decode(binaryInput, G3D::GImage::resolveFormat("", binaryInput.getCArray(), binaryInput.size(), G3D::GImage::AUTODETECT));
|
||||
}
|
||||
|
||||
if (format != G3D::GImage::PNG)
|
||||
{
|
||||
// alpha channel not requested, strip it.
|
||||
image = image.stripAlpha();
|
||||
}
|
||||
else
|
||||
{
|
||||
image.setColorAlphaTest(G3D::Color4uint8(255, 255, 255, 0));
|
||||
}
|
||||
|
||||
if (image.width() > 0 && image.height() > 0)
|
||||
{
|
||||
image = image.bilinearStretchBlt(cx, cy);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a dummy 1x1 image that we can save (PNG encode can't handle empty images)
|
||||
image.resize(1, 1, 3);
|
||||
}
|
||||
|
||||
image.encode(format, binaryOutput);
|
||||
|
||||
std::string strOut;
|
||||
base64<char>::encode((const char*)binaryOutput.getCArray(), binaryOutput.length(), strOut, base64<>::noline());
|
||||
|
||||
RBX::StandardOut::singleton()->print(RBX::MESSAGE_INFO, "ThumbnailGenerator::clickTexture() success");
|
||||
|
||||
shared_ptr<RBX::Reflection::Tuple> tuble(new RBX::Reflection::Tuple(2));
|
||||
tuble->values[0] = strOut;
|
||||
tuble->values[1] = contentProvider->getRequestedUrls();
|
||||
|
||||
return tuble;
|
||||
}
|
||||
catch (std::exception& exp)
|
||||
{
|
||||
RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, exp);
|
||||
throw;
|
||||
}
|
||||
catch (const G3D::GImage::Error& e)
|
||||
{
|
||||
RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, e.reason);
|
||||
throw std::runtime_error(e.reason);
|
||||
}
|
||||
};
|
||||
|
||||
shared_ptr<const RBX::Reflection::Tuple> ThumbnailGenerator::click(std::string fileType, int cx, int cy, bool hideSky, bool crop)
|
||||
{
|
||||
::InterlockedIncrement(&totalCount);
|
||||
try
|
||||
{
|
||||
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "ThumbnailGenerator::click(%s, %d, %d, %s)", fileType.c_str(), cx, cy, hideSky ? "true" : "false");
|
||||
|
||||
RBX::Time startTime = RBX::Time::now<Time::Fast>();
|
||||
|
||||
ReadAccessKey();
|
||||
|
||||
std::string strOutput;
|
||||
|
||||
RBX::ContentProvider* contentProvider = RBX::ServiceProvider::create<RBX::ContentProvider>(this);
|
||||
configureCaches();
|
||||
|
||||
{
|
||||
// We need to reuse graphics state between runs; this means reusing Mesa GL context.
|
||||
// There are two reasons why we'd like *all* render requests to come off a single thread:
|
||||
// 1. The window that's created in a thread dies, and DC dies with it - we need to keep the main window alive
|
||||
// 2. Mesa GL context is bound to a thread that created it; if a new thread is created, graphics can't reuse old GL context.
|
||||
std::string errorOutput;
|
||||
RBX::CEvent doneEvent(/* manualReset= */ true);
|
||||
|
||||
ThumbnailRenderRequest request = {this, fileType, cx, cy, hideSky, crop, &strOutput, &errorOutput, &doneEvent};
|
||||
|
||||
boost::call_once(thumbRenderInit, gThumbRenderThreadInit);
|
||||
|
||||
gThumbRenderQueue.push(request);
|
||||
gThumbRenderQueueNotEmpty.Set();
|
||||
|
||||
doneEvent.Wait();
|
||||
|
||||
if (!errorOutput.empty())
|
||||
{
|
||||
// render request threw an exception; "marshal" it here (we lose the stack, but the exception gets converted to Lua error anyway)
|
||||
throw std::runtime_error(errorOutput);
|
||||
}
|
||||
}
|
||||
|
||||
RBX::StandardOut::singleton()->print(RBX::MESSAGE_INFO, "ThumbnailGenerator::click() success");
|
||||
|
||||
// log thumbnail generation time
|
||||
// TODO: log asset/place id
|
||||
shared_ptr<Reflection::ValueTable> entry(new Reflection::ValueTable());
|
||||
(*entry)["Time"] = (RBX::Time::now<Time::Fast>() - startTime).seconds();
|
||||
|
||||
RBX::Stats::StatsService* stats = RBX::ServiceProvider::create<RBX::Stats::StatsService>(this);
|
||||
stats->report("Thumbnail", entry);
|
||||
|
||||
shared_ptr<RBX::Reflection::Tuple> tuble(new RBX::Reflection::Tuple(2));
|
||||
tuble->values[0] = strOutput;
|
||||
tuble->values[1] = contentProvider->getRequestedUrls();
|
||||
|
||||
return tuble;
|
||||
}
|
||||
catch (std::exception& exp)
|
||||
{
|
||||
RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, exp);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void ThumbnailGenerator::exportScene(RBX::ViewBase* view, std::string* outStr)
|
||||
{
|
||||
RBX::Workspace* w = RBX::ServiceProvider::find<RBX::Workspace>(this);
|
||||
// just fill it with a bunch of dummies
|
||||
bool allowDolly = w->setImageServerView(false);
|
||||
RBX::DataModel* dataModel = boost::polymorphic_downcast<RBX::DataModel*>(getParent());
|
||||
view->bindWorkspace(shared_from(dataModel));
|
||||
view->exportSceneThumbJSON(ExporterSaveType_Everything, ExporterFormat_Obj, true, *outStr);
|
||||
view->bindWorkspace(shared_ptr<DataModel>());
|
||||
}
|
||||
|
||||
void ThumbnailGenerator::renderThumb(ViewBase* view, void* windowHandle, std::string fileType, int cx, int cy, bool hideSky, bool crop, std::string* strOutput)
|
||||
{
|
||||
const G3D::GImage::Format format = G3D::GImage::stringToFormat(fileType);
|
||||
|
||||
bool alphaChannel = false;
|
||||
|
||||
// TODO: put this code into an API to Lighting???
|
||||
if (hideSky)
|
||||
{
|
||||
RBX::Lighting* lighting = RBX::ServiceProvider::create<RBX::Lighting>(this);
|
||||
lighting->suppressSky(true);
|
||||
if (format==G3D::GImage::PNG)
|
||||
{
|
||||
alphaChannel = true;
|
||||
lighting->setClearAlpha(0);
|
||||
}
|
||||
else
|
||||
lighting->setClearAlpha(1);
|
||||
}
|
||||
|
||||
// TODO: put this code into an API to Camera
|
||||
RBX::Workspace* w = RBX::ServiceProvider::find<RBX::Workspace>(this);
|
||||
|
||||
// Camera Adjustments:
|
||||
// use camera named "ThumbnailCamera" else:
|
||||
// for a model / bunch of models - look from -Z
|
||||
// for a place: not much.
|
||||
bool allowDolly = w->setImageServerView(!hideSky /* = isAPlace*/);
|
||||
|
||||
// render settings;
|
||||
ThumbnailRenderSettings defaultsettings;
|
||||
|
||||
{
|
||||
RBX::DataModel* dataModel = boost::polymorphic_downcast<RBX::DataModel*>(getParent());
|
||||
G3D::GImage image(cx, cy, 4);
|
||||
|
||||
view->bindWorkspace(shared_from(dataModel));
|
||||
view->renderThumb(image.byte(), cx, cy, crop,allowDolly);
|
||||
view->bindWorkspace(shared_ptr<DataModel>());
|
||||
|
||||
view->garbageCollect();
|
||||
|
||||
if (!alphaChannel)
|
||||
{
|
||||
image.convertToRGB();
|
||||
}
|
||||
|
||||
if (strOutput)
|
||||
{
|
||||
G3D::BinaryOutput binaryOutput;
|
||||
binaryOutput.setEndian(G3D::G3D_LITTLE_ENDIAN);
|
||||
image.encode(format, binaryOutput);
|
||||
base64<char>::encode((const char*)binaryOutput.getCArray(), binaryOutput.length(), *strOutput, base64<>::noline());
|
||||
}
|
||||
|
||||
if (hideSky)
|
||||
{
|
||||
RBX::Workspace* w = RBX::ServiceProvider::find<RBX::Workspace>(this);
|
||||
// Hack to avoid double-toggle
|
||||
w->setImageServerView(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user