mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-26 17:03:14 +08:00
355 lines
11 KiB
C++
355 lines
11 KiB
C++
// Released under the MIT License. See LICENSE for details.
|
|
|
|
#ifndef BALLISTICA_BASE_GRAPHICS_COMPONENT_RENDER_COMPONENT_H_
|
|
#define BALLISTICA_BASE_GRAPHICS_COMPONENT_RENDER_COMPONENT_H_
|
|
|
|
#include <vector>
|
|
|
|
#include "ballistica/base/assets/mesh_asset.h"
|
|
#include "ballistica/base/graphics/graphics.h"
|
|
#include "ballistica/base/graphics/renderer/render_pass.h"
|
|
#include "ballistica/base/graphics/support/frame_def.h"
|
|
#include "ballistica/base/graphics/support/render_command_buffer.h"
|
|
#include "ballistica/shared/math/rect.h"
|
|
|
|
namespace ballistica::base {
|
|
|
|
/// RenderComponents are used to assemble command streams to send to the
|
|
/// renderer. These do a lot of extra work in debug builds to make sure
|
|
/// valid commands are being constructed, so it is best to iterate on them
|
|
/// in debug mode when possible.
|
|
///
|
|
/// The general workflow with a RenderComponents is to set all 'config'
|
|
/// options at the beginning and then to issue one or more draw commands
|
|
/// after. Check the source of each call for EnsureConfiguring() or
|
|
/// EnsureDrawing() to see which is which. Flipping from configuring to
|
|
/// drawing can cause shader binding or other work to be done in the
|
|
/// graphics api, so switches back and forth should be minimized.
|
|
///
|
|
/// RenderComponent output goes to a specific draw list in the renderer.
|
|
/// Depending on the type of RenderPass, there may be a single draw-list,
|
|
/// transparent and opaque draw-lists, draw-lists for different shaders,
|
|
/// etc. RenderComponents currently must be sure to only draw to a single
|
|
/// draw list; otherwise things like PushTransform/PopTransforms may affect
|
|
/// different draw lists. Stay tuned for this system to evolve into
|
|
/// something more foolproof.
|
|
class RenderComponent {
|
|
private:
|
|
class ScopedTransformObj_ {
|
|
public:
|
|
explicit ScopedTransformObj_(RenderComponent* c) : c_{c} {
|
|
c_->PushTransform();
|
|
}
|
|
~ScopedTransformObj_() { c_->PopTransform(); }
|
|
|
|
private:
|
|
RenderComponent* c_;
|
|
};
|
|
|
|
class ScopedScissorObj_ {
|
|
public:
|
|
explicit ScopedScissorObj_(RenderComponent* c, const Rect& r) : c_{c} {
|
|
c_->ScissorPush(r);
|
|
}
|
|
~ScopedScissorObj_() { c_->ScissorPop(); }
|
|
|
|
private:
|
|
RenderComponent* c_;
|
|
};
|
|
|
|
public:
|
|
explicit RenderComponent(RenderPass* pass) : pass_(pass) {
|
|
assert(g_base->InLogicThread());
|
|
}
|
|
|
|
~RenderComponent() {
|
|
assert(g_base->InLogicThread());
|
|
|
|
if (state_ != State::kSubmitted) {
|
|
Submit();
|
|
}
|
|
}
|
|
|
|
/// End current drawing by this component. This is implicitly done when a
|
|
/// component goes out of scope, but one may choose to do this explicitly
|
|
/// to allow other components to draw while this one still exists (only
|
|
/// one RenderComponent can be actively drawing in a frame-def at a time).
|
|
void Submit() {
|
|
if (state_ != State::kSubmitted) {
|
|
#if BA_DEBUG_BUILD
|
|
if (state_ == State::kDrawing) {
|
|
// If we were drawing, let the frame-def know we're done.
|
|
assert(pass_->frame_def()->active_render_component() == this);
|
|
pass_->frame_def()->set_active_render_component(nullptr);
|
|
}
|
|
#endif
|
|
state_ = State::kSubmitted;
|
|
}
|
|
}
|
|
|
|
void DrawMeshAsset(MeshAsset* mesh, uint32_t flags = 0) {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kDrawMeshAsset);
|
|
cmd_buffer_->PutInt(flags);
|
|
cmd_buffer_->PutMeshAsset(mesh);
|
|
}
|
|
|
|
void DrawMeshAssetInstanced(MeshAsset* mesh,
|
|
const std::vector<Matrix44f>& matrices,
|
|
int flags = 0) {
|
|
assert(!matrices.empty());
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(
|
|
RenderCommandBuffer::Command::kDrawMeshAssetInstanced);
|
|
cmd_buffer_->PutInt(flags);
|
|
cmd_buffer_->PutMeshAsset(mesh);
|
|
cmd_buffer_->PutMatrices(matrices);
|
|
}
|
|
|
|
void DrawMesh(Mesh* m, int flags = 0) {
|
|
EnsureDrawing();
|
|
if (m->IsValid()) {
|
|
cmd_buffer_->frame_def()->AddMesh(m);
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kDrawMesh);
|
|
cmd_buffer_->PutInt(flags);
|
|
cmd_buffer_->PutMeshData(m->mesh_data_client_handle()->mesh_data);
|
|
}
|
|
}
|
|
|
|
void DrawScreenQuad() {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kDrawScreenQuad);
|
|
}
|
|
|
|
// Draw triangles using old-school gl format.. only for debugging and not
|
|
// supported in all configurations.
|
|
void BeginDebugDrawTriangles() {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(
|
|
RenderCommandBuffer::Command::kBeginDebugDrawTriangles);
|
|
}
|
|
|
|
void BeginDebugDrawLines() {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kBeginDebugDrawLines);
|
|
}
|
|
|
|
void Vertex(float x, float y, float z) {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kDebugDrawVertex3);
|
|
cmd_buffer_->PutFloats(x, y, z);
|
|
}
|
|
|
|
void End() {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kEndDebugDraw);
|
|
}
|
|
|
|
void PushTransform() {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kPushTransform);
|
|
}
|
|
|
|
void PopTransform() {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kPopTransform);
|
|
}
|
|
|
|
/// Add a transform push/pop to the component. Remember to assign the
|
|
/// result to a variable or the pop will be immediate.
|
|
auto ScopedTransform() -> ScopedTransformObj_ {
|
|
return ScopedTransformObj_(this);
|
|
}
|
|
|
|
/// Add a scissor push/pop to the component. Remember to assign the result
|
|
/// to a variable or the pop will be immediate.
|
|
auto ScopedScissor(const Rect& rect) -> ScopedScissorObj_ {
|
|
return ScopedScissorObj_(this, rect);
|
|
}
|
|
|
|
void Translate(float x, float y) {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kTranslate2);
|
|
cmd_buffer_->PutFloats(x, y);
|
|
}
|
|
|
|
void Translate(float x, float y, float z) {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kTranslate3);
|
|
cmd_buffer_->PutFloats(x, y, z);
|
|
}
|
|
|
|
void CursorTranslate() {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kCursorTranslate);
|
|
}
|
|
|
|
void Rotate(float angle, float x, float y, float z) {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kRotate);
|
|
cmd_buffer_->PutFloats(angle, x, y, z);
|
|
}
|
|
|
|
void Scale(float x, float y) {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kScale2);
|
|
cmd_buffer_->PutFloats(x, y);
|
|
}
|
|
|
|
void Scale(float x, float y, float z) {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kScale3);
|
|
cmd_buffer_->PutFloats(x, y, z);
|
|
}
|
|
|
|
void ScaleUniform(float s) {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kScaleUniform);
|
|
cmd_buffer_->PutFloat(s);
|
|
}
|
|
|
|
void MultMatrix(const float* t) {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kMultMatrix);
|
|
cmd_buffer_->PutFloatArray16(t);
|
|
}
|
|
|
|
#if BA_VR_BUILD
|
|
void VRTransformToRightHand() {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(
|
|
RenderCommandBuffer::Command::kTransformToRightHand);
|
|
}
|
|
|
|
void VRTransformToLeftHand() {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kTransformToLeftHand);
|
|
}
|
|
|
|
void VRTransformToHead() {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kTransformToHead);
|
|
}
|
|
#endif // BA_VR_BUILD
|
|
|
|
void TranslateToProjectedPoint(float x, float y, float z) {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(
|
|
RenderCommandBuffer::Command::kTranslateToProjectedPoint);
|
|
cmd_buffer_->PutFloats(x, y, z);
|
|
}
|
|
|
|
void FlipCullFace() {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kFlipCullFace);
|
|
}
|
|
|
|
protected:
|
|
enum class State : uint8_t { kConfiguring, kDrawing, kSubmitted };
|
|
void EnsureConfiguring() {
|
|
if (state_ != State::kConfiguring) {
|
|
#if BA_DEBUG_BUILD
|
|
// FIXME: currently releasing status as active-render-component here
|
|
// but should perhaps hold on to it for consistency.
|
|
if (state_ == State::kDrawing) {
|
|
assert(pass_->frame_def()->active_render_component() == this);
|
|
pass_->frame_def()->set_active_render_component(nullptr);
|
|
}
|
|
#endif
|
|
state_ = State::kConfiguring;
|
|
}
|
|
}
|
|
#if BA_DEBUG_BUILD
|
|
void ConfigForEmptyDebugChecks(bool transparent);
|
|
void ConfigForShadingDebugChecks(ShadingType shading_type);
|
|
#endif
|
|
|
|
// Given a shader type, returns a buffer to write the command stream to.
|
|
void ConfigForEmpty(bool transparent) {
|
|
#if BA_DEBUG_BUILD
|
|
ConfigForEmptyDebugChecks(transparent);
|
|
#endif
|
|
|
|
assert(!pass_->UsesWorldLists());
|
|
if (transparent) {
|
|
cmd_buffer_ = pass_->commands_flat_transparent();
|
|
} else {
|
|
cmd_buffer_ = pass_->commands_flat();
|
|
}
|
|
}
|
|
|
|
// Given a shader type, sets up the config target buffer.
|
|
void ConfigForShading(ShadingType shading_type) {
|
|
// Determine which buffer to write to, etc.
|
|
|
|
#if BA_DEBUG_BUILD
|
|
// Debugging: if we've got transparent-only or opaque-only mode flipped
|
|
// on, make sure only those type of components are being submitted.
|
|
ConfigForShadingDebugChecks(shading_type);
|
|
// Also make sure only transparent stuff is going into the
|
|
// light/shadow/overlay3D passes (we skip rendering the opaque lists
|
|
// there since there shouldn't be anything in them, and we're not using
|
|
// depth for those so it wouldn't be much of an optimization).
|
|
if ((pass_->type() == RenderPass::Type::kLightPass
|
|
|| pass_->type() == RenderPass::Type::kLightShadowPass
|
|
|| pass_->type() == RenderPass::Type::kOverlay3DPass)
|
|
&& !Graphics::IsShaderTransparent(shading_type)) {
|
|
FatalError("Opaque component submitted to light/shadow/overlay3d pass.");
|
|
}
|
|
|
|
// Likewise the blit pass should consist solely of opaque stuff.
|
|
if (pass_->type() == RenderPass::Type::kBlitPass
|
|
&& Graphics::IsShaderTransparent(shading_type)) {
|
|
FatalError("Transparent component submitted to blit pass.");
|
|
}
|
|
#endif // BA_DEBUG_BUILD
|
|
|
|
// Certain passes (overlay, etc) draw objects in the order provided.
|
|
// Other passes group by shader for efficiency.
|
|
if (pass_->UsesWorldLists()) {
|
|
cmd_buffer_ = pass_->GetCommands(shading_type);
|
|
} else {
|
|
if (Graphics::IsShaderTransparent(shading_type)) {
|
|
cmd_buffer_ = pass_->commands_flat_transparent();
|
|
} else {
|
|
cmd_buffer_ = pass_->commands_flat();
|
|
}
|
|
}
|
|
|
|
// Go ahead and throw down the shader command.
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kShader);
|
|
cmd_buffer_->PutInt(static_cast<int>(shading_type));
|
|
}
|
|
|
|
void EnsureDrawing() {
|
|
if (state_ != State::kDrawing) {
|
|
WriteConfig();
|
|
state_ = State::kDrawing;
|
|
#if BA_DEBUG_BUILD
|
|
// Let the frame-def know we're the active component drawing to it now.
|
|
assert(pass_->frame_def()->active_render_component() == nullptr);
|
|
pass_->frame_def()->set_active_render_component(this);
|
|
#endif
|
|
}
|
|
}
|
|
// Subclasses should override this to dump their needed data to the
|
|
// stream.
|
|
virtual void WriteConfig() = 0;
|
|
|
|
State state_{State::kConfiguring};
|
|
RenderCommandBuffer* cmd_buffer_{};
|
|
RenderPass* pass_;
|
|
|
|
public:
|
|
void ScissorPush(const Rect& rect);
|
|
|
|
void ScissorPop() {
|
|
EnsureDrawing();
|
|
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kScissorPop);
|
|
}
|
|
};
|
|
|
|
} // namespace ballistica::base
|
|
|
|
#endif // BALLISTICA_BASE_GRAPHICS_COMPONENT_RENDER_COMPONENT_H_
|