godot/servers/rendering/renderer_canvas_render.h

605 lines
16 KiB
C++

/*************************************************************************/
/* renderer_canvas_render.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef RENDERINGSERVERCANVASRENDER_H
#define RENDERINGSERVERCANVASRENDER_H
#include "servers/rendering/renderer_storage.h"
class RendererCanvasRender {
public:
static RendererCanvasRender *singleton;
enum CanvasRectFlags {
CANVAS_RECT_REGION = 1,
CANVAS_RECT_TILE = 2,
CANVAS_RECT_FLIP_H = 4,
CANVAS_RECT_FLIP_V = 8,
CANVAS_RECT_TRANSPOSE = 16,
CANVAS_RECT_CLIP_UV = 32,
CANVAS_RECT_IS_GROUP = 64,
};
struct Light {
bool enabled;
Color color;
Transform2D xform;
float height;
float energy;
float scale;
int z_min;
int z_max;
int layer_min;
int layer_max;
int item_mask;
int item_shadow_mask;
float directional_distance;
RS::CanvasLightMode mode;
RS::CanvasLightBlendMode blend_mode;
RID texture;
Vector2 texture_offset;
RID canvas;
bool use_shadow;
int shadow_buffer_size;
RS::CanvasLightShadowFilter shadow_filter;
Color shadow_color;
float shadow_smooth;
//void *texture_cache; // implementation dependent
Rect2 rect_cache;
Transform2D xform_cache;
float radius_cache; //used for shadow far plane
//CameraMatrix shadow_matrix_cache;
Transform2D light_shader_xform;
//Vector2 light_shader_pos;
Light *shadows_next_ptr;
Light *filter_next_ptr;
Light *next_ptr;
Light *directional_next_ptr;
RID light_internal;
uint64_t version;
int32_t render_index_cache;
Light() {
version = 0;
enabled = true;
color = Color(1, 1, 1);
shadow_color = Color(0, 0, 0, 0);
height = 0;
z_min = -1024;
z_max = 1024;
layer_min = 0;
layer_max = 0;
item_mask = 1;
scale = 1.0;
energy = 1.0;
item_shadow_mask = 1;
mode = RS::CANVAS_LIGHT_MODE_POINT;
blend_mode = RS::CANVAS_LIGHT_BLEND_MODE_ADD;
// texture_cache = nullptr;
next_ptr = nullptr;
directional_next_ptr = nullptr;
filter_next_ptr = nullptr;
use_shadow = false;
shadow_buffer_size = 2048;
shadow_filter = RS::CANVAS_LIGHT_FILTER_NONE;
shadow_smooth = 0.0;
render_index_cache = -1;
directional_distance = 10000.0;
}
};
//easier wrap to avoid mistakes
struct Item;
typedef uint64_t PolygonID;
virtual PolygonID request_polygon(const Vector<int> &p_indices, const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), const Vector<int> &p_bones = Vector<int>(), const Vector<float> &p_weights = Vector<float>()) = 0;
virtual void free_polygon(PolygonID p_polygon) = 0;
//also easier to wrap to avoid mistakes
struct Polygon {
PolygonID polygon_id;
Rect2 rect_cache;
_FORCE_INLINE_ void create(const Vector<int> &p_indices, const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), const Vector<int> &p_bones = Vector<int>(), const Vector<float> &p_weights = Vector<float>()) {
ERR_FAIL_COND(polygon_id != 0);
{
uint32_t pc = p_points.size();
const Vector2 *v2 = p_points.ptr();
rect_cache.position = *v2;
for (uint32_t i = 1; i < pc; i++) {
rect_cache.expand_to(v2[i]);
}
}
polygon_id = singleton->request_polygon(p_indices, p_points, p_colors, p_uvs, p_bones, p_weights);
}
_FORCE_INLINE_ Polygon() { polygon_id = 0; }
_FORCE_INLINE_ ~Polygon() {
if (polygon_id) {
singleton->free_polygon(polygon_id);
}
}
};
//item
struct Item {
//commands are allocated in blocks of 4k to improve performance
//and cache coherence.
//blocks always grow but never shrink.
struct CommandBlock {
enum {
MAX_SIZE = 4096
};
uint32_t usage;
uint8_t *memory;
};
struct Command {
enum Type {
TYPE_RECT,
TYPE_NINEPATCH,
TYPE_POLYGON,
TYPE_PRIMITIVE,
TYPE_MESH,
TYPE_MULTIMESH,
TYPE_PARTICLES,
TYPE_TRANSFORM,
TYPE_CLIP_IGNORE,
};
Command *next;
Type type;
virtual ~Command() {}
};
struct CommandRect : public Command {
Rect2 rect;
Color modulate;
Rect2 source;
uint8_t flags;
RID texture;
CommandRect() {
flags = 0;
type = TYPE_RECT;
}
};
struct CommandNinePatch : public Command {
Rect2 rect;
Rect2 source;
float margin[4];
bool draw_center;
Color color;
RS::NinePatchAxisMode axis_x;
RS::NinePatchAxisMode axis_y;
RID texture;
CommandNinePatch() {
draw_center = true;
type = TYPE_NINEPATCH;
}
};
struct CommandPolygon : public Command {
RS::PrimitiveType primitive;
Polygon polygon;
RID texture;
CommandPolygon() {
type = TYPE_POLYGON;
}
};
struct CommandPrimitive : public Command {
uint32_t point_count;
Vector2 points[4];
Vector2 uvs[4];
Color colors[4];
RID texture;
CommandPrimitive() {
type = TYPE_PRIMITIVE;
}
};
struct CommandMesh : public Command {
RID mesh;
Transform2D transform;
Color modulate;
RID texture;
CommandMesh() { type = TYPE_MESH; }
};
struct CommandMultiMesh : public Command {
RID multimesh;
RID texture;
CommandMultiMesh() { type = TYPE_MULTIMESH; }
};
struct CommandParticles : public Command {
RID particles;
RID texture;
CommandParticles() { type = TYPE_PARTICLES; }
};
struct CommandTransform : public Command {
Transform2D xform;
CommandTransform() { type = TYPE_TRANSFORM; }
};
struct CommandClipIgnore : public Command {
bool ignore;
CommandClipIgnore() {
type = TYPE_CLIP_IGNORE;
ignore = false;
}
};
struct ViewportRender {
RenderingServer *owner;
void *udata;
Rect2 rect;
};
Transform2D xform;
bool clip;
bool visible;
bool behind;
bool update_when_visible;
struct CanvasGroup {
RS::CanvasGroupMode mode;
bool fit_empty;
float fit_margin;
bool blur_mipmaps;
float clear_margin;
};
CanvasGroup *canvas_group = nullptr;
int light_mask;
int z_final;
mutable bool custom_rect;
mutable bool rect_dirty;
mutable Rect2 rect;
RID material;
RID skeleton;
Item *next;
struct CopyBackBuffer {
Rect2 rect;
Rect2 screen_rect;
bool full;
};
CopyBackBuffer *copy_back_buffer;
Color final_modulate;
Transform2D final_transform;
Rect2 final_clip_rect;
Item *final_clip_owner;
Item *material_owner;
Item *canvas_group_owner;
ViewportRender *vp_render;
bool distance_field;
bool light_masked;
Rect2 global_rect_cache;
const Rect2 &get_rect() const {
if (custom_rect || (!rect_dirty && !update_when_visible)) {
return rect;
}
//must update rect
if (commands == nullptr) {
rect = Rect2();
rect_dirty = false;
return rect;
}
Transform2D xf;
bool found_xform = false;
bool first = true;
const Item::Command *c = commands;
while (c) {
Rect2 r;
switch (c->type) {
case Item::Command::TYPE_RECT: {
const Item::CommandRect *crect = static_cast<const Item::CommandRect *>(c);
r = crect->rect;
} break;
case Item::Command::TYPE_NINEPATCH: {
const Item::CommandNinePatch *style = static_cast<const Item::CommandNinePatch *>(c);
r = style->rect;
} break;
case Item::Command::TYPE_POLYGON: {
const Item::CommandPolygon *polygon = static_cast<const Item::CommandPolygon *>(c);
r = polygon->polygon.rect_cache;
} break;
case Item::Command::TYPE_PRIMITIVE: {
const Item::CommandPrimitive *primitive = static_cast<const Item::CommandPrimitive *>(c);
for (uint32_t j = 0; j < primitive->point_count; j++) {
if (j == 0) {
r.position = primitive->points[0];
} else {
r.expand_to(primitive->points[j]);
}
}
} break;
case Item::Command::TYPE_MESH: {
const Item::CommandMesh *mesh = static_cast<const Item::CommandMesh *>(c);
AABB aabb = RendererStorage::base_singleton->mesh_get_aabb(mesh->mesh, RID());
r = Rect2(aabb.position.x, aabb.position.y, aabb.size.x, aabb.size.y);
} break;
case Item::Command::TYPE_MULTIMESH: {
const Item::CommandMultiMesh *multimesh = static_cast<const Item::CommandMultiMesh *>(c);
AABB aabb = RendererStorage::base_singleton->multimesh_get_aabb(multimesh->multimesh);
r = Rect2(aabb.position.x, aabb.position.y, aabb.size.x, aabb.size.y);
} break;
case Item::Command::TYPE_PARTICLES: {
const Item::CommandParticles *particles_cmd = static_cast<const Item::CommandParticles *>(c);
if (particles_cmd->particles.is_valid()) {
AABB aabb = RendererStorage::base_singleton->particles_get_aabb(particles_cmd->particles);
r = Rect2(aabb.position.x, aabb.position.y, aabb.size.x, aabb.size.y);
}
} break;
case Item::Command::TYPE_TRANSFORM: {
const Item::CommandTransform *transform = static_cast<const Item::CommandTransform *>(c);
xf = transform->xform;
found_xform = true;
[[fallthrough]];
}
default: {
c = c->next;
continue;
}
}
if (found_xform) {
r = xf.xform(r);
found_xform = false;
}
if (first) {
rect = r;
first = false;
} else {
rect = rect.merge(r);
}
c = c->next;
}
rect_dirty = false;
return rect;
}
Command *commands;
Command *last_command;
Vector<CommandBlock> blocks;
uint32_t current_block;
template <class T>
T *alloc_command() {
T *command;
if (commands == nullptr) {
// As the most common use case of canvas items is to
// use only one command, the first is done with it's
// own allocation. The rest of them use blocks.
command = memnew(T);
command->next = nullptr;
commands = command;
last_command = command;
} else {
//Subsequent commands go into a block.
while (true) {
if (unlikely(current_block == (uint32_t)blocks.size())) {
// If we need more blocks, we allocate them
// (they won't be freed until this CanvasItem is
// deleted, though).
CommandBlock cb;
cb.memory = (uint8_t *)memalloc(CommandBlock::MAX_SIZE);
cb.usage = 0;
blocks.push_back(cb);
}
CommandBlock *c = &blocks.write[current_block];
size_t space_left = CommandBlock::MAX_SIZE - c->usage;
if (space_left < sizeof(T)) {
current_block++;
continue;
}
//allocate block and add to the linked list
void *memory = c->memory + c->usage;
command = memnew_placement(memory, T);
command->next = nullptr;
last_command->next = command;
last_command = command;
c->usage += sizeof(T);
break;
}
}
rect_dirty = true;
return command;
}
void clear() {
// The first one is always allocated on heap
// the rest go in the blocks
Command *c = commands;
while (c) {
Command *n = c->next;
if (c == commands) {
memdelete(commands);
commands = nullptr;
} else {
c->~Command();
}
c = n;
}
{
uint32_t cbc = MIN((current_block + 1), (uint32_t)blocks.size());
CommandBlock *blockptr = blocks.ptrw();
for (uint32_t i = 0; i < cbc; i++) {
blockptr[i].usage = 0;
}
}
last_command = nullptr;
commands = nullptr;
current_block = 0;
clip = false;
rect_dirty = true;
final_clip_owner = nullptr;
material_owner = nullptr;
light_masked = false;
}
RS::CanvasItemTextureFilter texture_filter;
RS::CanvasItemTextureRepeat texture_repeat;
Item() {
commands = nullptr;
last_command = nullptr;
current_block = 0;
light_mask = 1;
vp_render = nullptr;
next = nullptr;
final_clip_owner = nullptr;
canvas_group_owner = nullptr;
clip = false;
final_modulate = Color(1, 1, 1, 1);
visible = true;
rect_dirty = true;
custom_rect = false;
behind = false;
material_owner = nullptr;
copy_back_buffer = nullptr;
distance_field = false;
light_masked = false;
update_when_visible = false;
z_final = 0;
texture_filter = RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT;
texture_repeat = RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT;
}
virtual ~Item() {
clear();
for (int i = 0; i < blocks.size(); i++) {
memfree(blocks[i].memory);
}
if (copy_back_buffer) {
memdelete(copy_back_buffer);
}
}
};
virtual void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used) = 0;
virtual void canvas_debug_viewport_shadows(Light *p_lights_with_shadow) = 0;
struct LightOccluderInstance {
bool enabled;
RID canvas;
RID polygon;
RID occluder;
Rect2 aabb_cache;
Transform2D xform;
Transform2D xform_cache;
int light_mask;
bool sdf_collision;
RS::CanvasOccluderPolygonCullMode cull_cache;
LightOccluderInstance *next;
LightOccluderInstance() {
enabled = true;
sdf_collision = false;
next = nullptr;
light_mask = 1;
cull_cache = RS::CANVAS_OCCLUDER_POLYGON_CULL_DISABLED;
}
};
virtual RID light_create() = 0;
virtual void light_set_texture(RID p_rid, RID p_texture) = 0;
virtual void light_set_use_shadow(RID p_rid, bool p_enable) = 0;
virtual void light_update_shadow(RID p_rid, int p_shadow_index, const Transform2D &p_light_xform, int p_light_mask, float p_near, float p_far, LightOccluderInstance *p_occluders) = 0;
virtual void light_update_directional_shadow(RID p_rid, int p_shadow_index, const Transform2D &p_light_xform, int p_light_mask, float p_cull_distance, const Rect2 &p_clip_rect, LightOccluderInstance *p_occluders) = 0;
virtual void render_sdf(RID p_render_target, LightOccluderInstance *p_occluders) = 0;
virtual RID occluder_polygon_create() = 0;
virtual void occluder_polygon_set_shape(RID p_occluder, const Vector<Vector2> &p_points, bool p_closed) = 0;
virtual void occluder_polygon_set_cull_mode(RID p_occluder, RS::CanvasOccluderPolygonCullMode p_mode) = 0;
virtual void set_shadow_texture_size(int p_size) = 0;
virtual void draw_window_margins(int *p_margins, RID *p_margin_textures) = 0;
virtual bool free(RID p_rid) = 0;
virtual void update() = 0;
RendererCanvasRender() { singleton = this; }
virtual ~RendererCanvasRender() {}
};
#endif // RENDERINGSERVERCANVASRENDER_H