/* * Copyright 2009, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // This file contains the implementation for the Client class. #include "core/cross/client.h" #include "core/cross/draw_context.h" #include "core/cross/effect.h" #include "core/cross/message_queue.h" #include "core/cross/pack.h" #include "core/cross/shape.h" #include "core/cross/transform.h" #include "core/cross/material.h" #include "core/cross/renderer.h" #include "core/cross/viewport.h" #include "core/cross/clear_buffer.h" #include "core/cross/state_set.h" #include "core/cross/tree_traversal.h" #include "core/cross/draw_pass.h" #include "core/cross/bounding_box.h" #include "core/cross/bitmap.h" #include "core/cross/error.h" #include "core/cross/evaluation_counter.h" #include "core/cross/id_manager.h" #include "core/cross/profiler.h" #include "utils/cross/string_writer.h" #include "utils/cross/json_writer.h" #include "utils/cross/dataurl.h" #ifdef OS_WIN #include "core/cross/core_metrics.h" #endif using std::map; using std::vector; using std::make_pair; namespace o3d { // If Renderer::max_fps has been set, rendering is driven by incoming new // textures. We draw on each new texture as long as not exceeding max_fps. // // If we are in RENDERMODE_ON_DEMAND mode, Client::Render() can still set dirty // specifically. // // If we are in RENDERMODE_CONTINUOUS mode, we do NOT set dirty on each tick any // more (since it is already driven by new textures.). // There is one problem here: what if new texture don't come in for some reason? // If that happened, no rendering callback will be invoked and this can cause // problem sometimes. For example, some UI may depend on the rendering callback // to work correctly. // So, in RENDERMODE_CONTINUOUS mode, if we have set max_fps but haven't // received any new texture for a while, we draw anyway to trigger the rendering // callback. // This value defines the minimum number of draws per seconds in // RENDERMODE_CONTINUOUS mode. static const float kContinuousModeMinDrawPerSecond = 15; // TODO(zhurunz) Tuning this value. // Client constructor. Creates the default root node for the scenegraph Client::Client(ServiceLocator* service_locator) : service_locator_(service_locator), object_manager_(service_locator), error_status_(service_locator), draw_list_manager_(service_locator), counter_manager_(service_locator), transformation_context_(service_locator), semantic_manager_(service_locator), profiler_(service_locator), renderer_(service_locator), evaluation_counter_(service_locator), render_tree_called_(false), render_mode_(RENDERMODE_CONTINUOUS), texture_on_hold_(false), event_manager_(), is_ticking_(false), last_tick_time_(0), root_(NULL), #ifdef OS_WIN calls_(0), #endif rendergraph_root_(NULL), id_(IdManager::CreateId()) { // Create and initialize the message queue to allow external code to // communicate with the Client via RPC calls. message_queue_.reset(new MessageQueue(service_locator_)); if (!message_queue_->Initialize()) { LOG(ERROR) << "Client failed to initialize the message queue"; message_queue_.reset(NULL); } } // Frees up all the resources allocated by the Client factory methods but // does not destroy the "renderer_" object. Client::~Client() { root_.Reset(); rendergraph_root_.Reset(); object_manager_->DestroyAllPacks(); // Unmap the client from the renderer on exit. if (renderer_.IsAvailable()) { renderer_->UninitCommon(); } } // Assigns a Renderer to the Client, and also assigns the Client // to the Renderer and sets up the default render graph void Client::Init() { if (!renderer_.IsAvailable()) return; // Create the root node for the scenegraph. Note that the root lives // outside of a pack object. The root's lifetime is directly bound to that // of the client. root_ = Transform::Ref(new Transform(service_locator_)); root_->set_name(O3D_STRING_CONSTANT("root")); // Creates the root for the render graph. rendergraph_root_ = RenderNode::Ref(new RenderNode(service_locator_)); rendergraph_root_->set_name(O3D_STRING_CONSTANT("root")); // Let the renderer Init a few common things. renderer_->InitCommon(); } void Client::Cleanup() { ClearRenderCallback(); ClearPostRenderCallback(); ClearTickCallback(); event_manager_.ClearAll(); counter_manager_.ClearAllCallbacks(); // Disable continuous rendering because there is nothing to render after // Cleanup is called. This speeds up the the process of unloading the page. // It also preserves the last rendered frame so long as it does not become // invalid. render_mode_ = RENDERMODE_ON_DEMAND; // Destroy the packs here if possible. If there are a lot of objects it takes // a long time and seems to make Chrome timeout if it happens in NPP_Destroy. root_.Reset(); rendergraph_root_.Reset(); object_manager_->DestroyAllPacks(); } Pack* Client::CreatePack() { if (!renderer_.IsAvailable()) { O3D_ERROR(service_locator_) << "No Renderer available, Pack creation not allowed."; return NULL; } return object_manager_->CreatePack(); } // Tick Methods ---------------------------------------------------------------- void Client::SetTickCallback( TickCallback* tick_callback) { tick_callback_manager_.Set(tick_callback); } void Client::ClearTickCallback() { tick_callback_manager_.Clear(); } bool Client::Tick() { is_ticking_ = true; ElapsedTimeTimer timer; float seconds_elapsed = tick_elapsed_time_timer_.GetElapsedTimeAndReset(); tick_event_.set_elapsed_time(seconds_elapsed); profiler_->ProfileStart("Tick callback"); tick_callback_manager_.Run(tick_event_); profiler_->ProfileStop("Tick callback"); evaluation_counter_->InvalidateAllParameters(); counter_manager_.AdvanceCounters(1.0f, seconds_elapsed); // Processes any incoming message found in the message queue. Note that this // call does not block if no new messages are found. bool message_check_ok = true; bool has_new_texture = false; if (message_queue_.get()) { profiler_->ProfileStart("CheckForNewMessages"); message_check_ok = message_queue_->CheckForNewMessages(&has_new_texture); profiler_->ProfileStop("CheckForNewMessages"); } event_manager_.ProcessQueue(); event_manager_.ProcessQueue(); event_manager_.ProcessQueue(); event_manager_.ProcessQueue(); last_tick_time_ = timer.GetElapsedTimeAndReset(); texture_on_hold_ |= has_new_texture; if (texture_on_hold_ && renderer_.IsAvailable()) { int max_fps = renderer_->max_fps(); if (max_fps > 0 && render_mode() == RENDERMODE_ON_DEMAND && render_elapsed_time_timer_.GetElapsedTimeWithoutClearing() > 1.0/max_fps) { renderer_->set_need_to_render(true); texture_on_hold_ = false; } } is_ticking_ = false; return message_check_ok; } // Render Methods -------------------------------------------------------------- void Client::SetLostResourcesCallback(LostResourcesCallback* callback) { if (!renderer_.IsAvailable()) { O3D_ERROR(service_locator_) << "No Renderer"; } else { renderer_->SetLostResourcesCallback(callback); } } void Client::ClearLostResourcesCallback() { if (renderer_.IsAvailable()) { renderer_->ClearLostResourcesCallback(); } } void Client::RenderClientInner(bool present, bool send_callback) { ElapsedTimeTimer timer; render_tree_called_ = false; total_time_to_render_ = 0.0f; if (!renderer_.IsAvailable()) return; if (renderer_->StartRendering()) { counter_manager_.AdvanceRenderFrameCounters(1.0f); profiler_->ProfileStart("Render callback"); if (send_callback) render_callback_manager_.Run(render_event_); // Calling back to JavaScript may have caused the plugin to be // torn down. Guard carefully against this. if (!profiler_.IsAvailable()) { if (renderer_.IsAvailable()) { renderer_->FinishRendering(); } return; } profiler_->ProfileStop("Render callback"); if (!render_tree_called_) { RenderNode* rendergraph_root = render_graph_root(); // If nothing was rendered and there are no render graph nodes then // clear the client area. if (!rendergraph_root || rendergraph_root->children().empty()) { renderer_->Clear(Float4(0.4f, 0.3f, 0.3f, 1.0f), true, 1.0, true, 0, true); } else if (rendergraph_root) { RenderTree(rendergraph_root); } } renderer_->FinishRendering(); if (present) { renderer_->Present(); // This has to be called before the POST render callback because // the post render callback may call Client::Render. renderer_->set_need_to_render(false); } // Call post render callback. profiler_->ProfileStart("Post-render callback"); post_render_callback_manager_.Run(render_event_); profiler_->ProfileStop("Post-render callback"); // Update Render stats. render_event_.set_elapsed_time( render_elapsed_time_timer_.GetElapsedTimeAndReset()); render_event_.set_render_time(total_time_to_render_); render_event_.set_transforms_culled(renderer_->transforms_culled()); render_event_.set_transforms_processed(renderer_->transforms_processed()); render_event_.set_draw_elements_culled(renderer_->draw_elements_culled()); render_event_.set_draw_elements_processed( renderer_->draw_elements_processed()); render_event_.set_draw_elements_rendered( renderer_->draw_elements_rendered()); render_event_.set_primitives_rendered(renderer_->primitives_rendered()); render_event_.set_active_time( timer.GetElapsedTimeAndReset() + last_tick_time_); last_tick_time_ = 0.0f; #ifdef OS_WIN // Update render metrics metric_render_elapsed_time.AddSample( // Convert to ms. static_cast(1000 * render_event_.elapsed_time())); metric_render_time_seconds += static_cast( render_event_.render_time()); metric_render_xforms_culled.AddSample(render_event_.transforms_culled()); metric_render_xforms_processed.AddSample( render_event_.transforms_processed()); metric_render_draw_elts_culled.AddSample( render_event_.draw_elements_culled()); metric_render_draw_elts_processed.AddSample( render_event_.draw_elements_processed()); metric_render_draw_elts_rendered.AddSample( render_event_.draw_elements_rendered()); metric_render_prims_rendered.AddSample(render_event_.primitives_rendered()); #endif // OS_WIN } } void Client::RenderClient(bool send_callback) { if (!renderer_.IsAvailable()) return; bool have_offscreen_surfaces = !(offscreen_render_surface_.IsNull() || offscreen_depth_render_surface_.IsNull()); if (have_offscreen_surfaces) { if (!renderer_->StartRendering()) { return; } renderer_->SetRenderSurfaces(offscreen_render_surface_, offscreen_depth_render_surface_, true); } RenderClientInner(!have_offscreen_surfaces, send_callback); if (have_offscreen_surfaces) { renderer_->SetRenderSurfaces(NULL, NULL, false); renderer_->FinishRendering(); } } bool Client::IsRendering() { return (renderer_.IsAvailable() && renderer_->rendering()); } bool Client::NeedsContinuousRender() { bool needRender = false; // Only may happen in RENDERMODE_CONTINUOUS mode. if (render_mode() == RENDERMODE_CONTINUOUS) { // Always need a draw in normal RENDERMODE_CONTINUOUS mode. needRender = true; if (renderer_.IsAvailable()) { // If max_fps has been set, only need a draw when "long time no draw". int max_fps = renderer_->max_fps(); if (max_fps > 0 && render_elapsed_time_timer_.GetElapsedTimeWithoutClearing() < 1.0/kContinuousModeMinDrawPerSecond) { needRender = false; } } } return needRender; } // Executes draw calls for all visible shapes in a subtree void Client::RenderTree(RenderNode *tree_root) { if (!renderer_.IsAvailable()) return; if (!renderer_->rendering()) { // Render tree can not be called if we are not rendering because all calls // to RenderTree must happen inside renderer->StartRendering() / // renderer->FinishRendering() calls. O3D_ERROR(service_locator_) << "RenderTree must not be called outside of rendering."; return; } render_tree_called_ = true; // Only render the shapes if BeginDraw() succeeds profiler_->ProfileStart("RenderTree"); ElapsedTimeTimer time_to_render_timer; if (renderer_->BeginDraw()) { RenderContext render_context(renderer_.Get()); if (tree_root) { tree_root->RenderTree(&render_context); } draw_list_manager_.Reset(); // Finish up. renderer_->EndDraw(); } total_time_to_render_ += time_to_render_timer.GetElapsedTimeAndReset(); profiler_->ProfileStop("RenderTree"); } void Client::SetRenderCallback(RenderCallback* render_callback) { render_callback_manager_.Set(render_callback); } void Client::ClearRenderCallback() { render_callback_manager_.Clear(); } void Client::SetEventCallback(Event::Type type, EventCallback* event_callback) { event_manager_.SetEventCallback(type, event_callback); } void Client::SetEventCallback(String type_name, EventCallback* event_callback) { Event::Type type = Event::TypeFromString(type_name.c_str()); if (!Event::ValidType(type)) { O3D_ERROR(service_locator_) << "Invalid event type: '" << type_name << "'."; } else { event_manager_.SetEventCallback(type, event_callback); } } void Client::ClearEventCallback(Event::Type type) { event_manager_.ClearEventCallback(type); } void Client::ClearEventCallback(String type_name) { Event::Type type = Event::TypeFromString(type_name.c_str()); if (!Event::ValidType(type)) { O3D_ERROR(service_locator_) << "Invalid event type: '" << type_name << "'."; } else { event_manager_.ClearEventCallback(type); } } void Client::AddEventToQueue(const Event& event) { event_manager_.AddEventToQueue(event); } void Client::SendResizeEvent(int width, int height, bool fullscreen) { Event event(Event::TYPE_RESIZE); event.set_size(width, height, fullscreen); AddEventToQueue(event); } void Client::set_render_mode(RenderMode render_mode) { render_mode_ = render_mode; } void Client::SetPostRenderCallback(RenderCallback* post_render_callback) { post_render_callback_manager_.Set(post_render_callback); } void Client::ClearPostRenderCallback() { post_render_callback_manager_.Clear(); } void Client::Render() { if (render_mode() == RENDERMODE_ON_DEMAND) { if (renderer_.IsAvailable()) { renderer_->set_need_to_render(true); } } } void Client::SetErrorTexture(Texture* texture) { renderer_->SetErrorTexture(texture); } void Client::InvalidateAllParameters() { evaluation_counter_->InvalidateAllParameters(); } String Client::GetScreenshotAsDataURL() { // To take a screenshot we create a render target and render into it // then get a bitmap from that. int pot_width = static_cast(image::ComputePOTSize(renderer_->display_width())); int pot_height = static_cast(image::ComputePOTSize(renderer_->display_height())); if (pot_width == 0 || pot_height == 0) { return dataurl::kEmptyDataURL; } Texture2D::Ref texture = renderer_->CreateTexture2D( pot_width, pot_height, Texture::ARGB8, 1, true); if (texture.IsNull()) { return dataurl::kEmptyDataURL; } RenderSurface::Ref surface(texture->GetRenderSurface(0)); if (surface.IsNull()) { return dataurl::kEmptyDataURL; } RenderDepthStencilSurface::Ref depth(renderer_->CreateDepthStencilSurface( pot_width, pot_height)); if (depth.IsNull()) { return dataurl::kEmptyDataURL; } surface->SetClipSize(renderer_->display_width(), renderer_->display_height()); depth->SetClipSize(renderer_->display_width(), renderer_->display_height()); const RenderSurface* old_render_surface_; const RenderDepthStencilSurface* old_depth_surface_; bool is_back_buffer; renderer_->GetRenderSurfaces(&old_render_surface_, &old_depth_surface_, &is_back_buffer); renderer_->SetRenderSurfaces(surface, depth, true); RenderClientInner(false, true); renderer_->SetRenderSurfaces(old_render_surface_, old_depth_surface_, is_back_buffer); Bitmap::Ref bitmap(surface->GetBitmap()); if (bitmap.IsNull()) { return dataurl::kEmptyDataURL; } else { bitmap->FlipVertically(); return bitmap->ToDataURL(); } } String Client::ToDataURL() { if (!renderer_.IsAvailable()) { O3D_ERROR(service_locator_) << "No Render Device Available"; return dataurl::kEmptyDataURL; } if (renderer_->rendering()) { O3D_ERROR(service_locator_) << "Can not take a screenshot while rendering"; return dataurl::kEmptyDataURL; } if (!renderer_->StartRendering()) { return dataurl::kEmptyDataURL; } String data_url(GetScreenshotAsDataURL()); renderer_->FinishRendering(); return data_url; } String Client::GetMessageQueueAddress() const { if (message_queue_.get()) { return message_queue_->GetSocketAddress(); } else { O3D_ERROR(service_locator_) << "Message queue not initialized"; return String(""); } } void Client::SetOffscreenRenderingSurfaces( RenderSurface::Ref surface, RenderDepthStencilSurface::Ref depth_surface) { offscreen_render_surface_ = surface; offscreen_depth_render_surface_ = depth_surface; } // Error Related methods ------------------------------------------------------- void Client::SetErrorCallback(ErrorCallback* callback) { error_status_.SetErrorCallback(callback); } void Client::ClearErrorCallback() { error_status_.ClearErrorCallback(); } const String& Client::GetLastError() const { return error_status_.GetLastError(); } void Client::ClearLastError() { error_status_.ClearLastError(); } void Client::ProfileStart(const std::string& key) { profiler_->ProfileStart(key); } void Client::ProfileStop(const std::string& key) { profiler_->ProfileStop(key); } void Client::ProfileReset() { profiler_->ProfileReset(); } String Client::ProfileToString() { StringWriter string_writer(StringWriter::LF); JsonWriter json_writer(&string_writer, 2); profiler_->Write(&json_writer); json_writer.Close(); return string_writer.ToString(); } } // namespace o3d