Merge pull request #68960 from lawnjelly/multirect
Batching - Add MultiRect command
This commit is contained in:
commit
4c5a934408
18 changed files with 1134 additions and 56 deletions
136
core/fixed_array.h
Normal file
136
core/fixed_array.h
Normal file
|
@ -0,0 +1,136 @@
|
|||
/**************************************************************************/
|
||||
/* fixed_array.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 FIXED_ARRAY_H
|
||||
#define FIXED_ARRAY_H
|
||||
|
||||
#include "core/local_vector.h"
|
||||
|
||||
// High performance fixed size array, single threaded.
|
||||
// Especially useful if you need to create an array on the stack, to
|
||||
// prevent dynamic allocations (especially in bottleneck code).
|
||||
|
||||
template <class T, uint32_t CAPACITY = 8, bool force_trivial = false, uint32_t ALIGN = 1>
|
||||
class FixedArray {
|
||||
static_assert(ALIGN > 0, "ALIGN must be at least 1.");
|
||||
const static uint32_t UNIT_SIZE = ((sizeof(T) + ALIGN - 1) / ALIGN * ALIGN);
|
||||
const static bool CONSTRUCT = !std::is_trivially_constructible<T>::value && !force_trivial;
|
||||
const static bool DESTRUCT = !std::is_trivially_destructible<T>::value && !force_trivial;
|
||||
|
||||
uint32_t _size = 0;
|
||||
uint8_t _data[CAPACITY * UNIT_SIZE];
|
||||
|
||||
const T &get(uint32_t p_index) const {
|
||||
return *(const T *)&_data[p_index * UNIT_SIZE];
|
||||
}
|
||||
T &get(uint32_t p_index) {
|
||||
return *(T *)&_data[p_index * UNIT_SIZE];
|
||||
}
|
||||
|
||||
public:
|
||||
uint32_t size() const { return _size; }
|
||||
bool is_empty() const { return !_size; }
|
||||
bool is_full() const { return _size == CAPACITY; }
|
||||
uint32_t capacity() const { return CAPACITY; }
|
||||
|
||||
T *request(bool p_construct = true) {
|
||||
if (size() < CAPACITY) {
|
||||
T *ele = &get(_size++);
|
||||
if (CONSTRUCT && p_construct) {
|
||||
memnew_placement(ele, T);
|
||||
}
|
||||
return ele;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
void push_back(const T &p_val) {
|
||||
T *mem = request(false);
|
||||
ERR_FAIL_NULL(mem);
|
||||
*mem = p_val;
|
||||
}
|
||||
void clear() {
|
||||
resize(0);
|
||||
}
|
||||
void remove_unordered(uint32_t p_index) {
|
||||
ERR_FAIL_UNSIGNED_INDEX(p_index, _size);
|
||||
|
||||
_size--;
|
||||
if (_size > p_index) {
|
||||
get(p_index) = get(_size);
|
||||
}
|
||||
|
||||
if (DESTRUCT) {
|
||||
get(_size).~T();
|
||||
}
|
||||
}
|
||||
void resize(uint32_t p_size) {
|
||||
ERR_FAIL_COND(p_size > CAPACITY);
|
||||
|
||||
if (DESTRUCT && (p_size < _size)) {
|
||||
for (uint32_t i = p_size; i < _size; i++) {
|
||||
get(i).~T();
|
||||
}
|
||||
}
|
||||
|
||||
if (CONSTRUCT && (p_size > _size)) {
|
||||
for (uint32_t i = _size; i < p_size; i++) {
|
||||
memnew_placement(&get(i), T);
|
||||
}
|
||||
}
|
||||
|
||||
_size = p_size;
|
||||
}
|
||||
const T &operator[](uint32_t p_index) const {
|
||||
DEV_ASSERT(p_index < size());
|
||||
return get(p_index);
|
||||
}
|
||||
T &operator[](uint32_t p_index) {
|
||||
DEV_ASSERT(p_index < size());
|
||||
return get(p_index);
|
||||
}
|
||||
|
||||
operator Vector<T>() const {
|
||||
Vector<T> ret;
|
||||
if (size()) {
|
||||
ret.resize(size());
|
||||
T *dest = ret.ptrw();
|
||||
if (ALIGN <= 1) {
|
||||
memcpy(dest, _data, sizeof(T) * _size);
|
||||
} else {
|
||||
for (uint32_t n = 0; n < _size; n++) {
|
||||
dest[n] = get(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // FIXED_ARRAY_H
|
|
@ -1477,6 +1477,10 @@
|
|||
<member name="rendering/batching/options/use_batching_in_editor" type="bool" setter="" getter="" default="true">
|
||||
Switches on 2D batching within the editor.
|
||||
</member>
|
||||
<member name="rendering/batching/options/use_multirect" type="bool" setter="" getter="" default="true">
|
||||
Allows use of the [code]MultiRect[/code] command in the Rasterizer.
|
||||
This can provide some acceleration for large groups of rects, especially text and tilemaps.
|
||||
</member>
|
||||
<member name="rendering/batching/parameters/batch_buffer_size" type="int" setter="" getter="" default="16384">
|
||||
Size of buffer reserved for batched vertices. Larger size enables larger batches, but there are diminishing returns for the memory used. This should only have a minor effect on performance.
|
||||
</member>
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include "rasterizer_asserts.h"
|
||||
#include "rasterizer_storage_common.h"
|
||||
#include "servers/visual/rasterizer.h"
|
||||
#include "servers/visual/visual_server_canvas_helper.h"
|
||||
|
||||
// We are using the curiously recurring template pattern
|
||||
// https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
|
||||
|
@ -296,7 +297,6 @@ public:
|
|||
settings_use_batching = false;
|
||||
settings_max_join_item_commands = 0;
|
||||
settings_colored_vertex_format_threshold = 0.0f;
|
||||
settings_batch_buffer_num_verts = 0;
|
||||
scissor_threshold_area = 0.0f;
|
||||
joined_item_batch_flags = 0;
|
||||
diagnose_frame = false;
|
||||
|
@ -432,7 +432,6 @@ public:
|
|||
bool settings_diagnose_frame; // print out batches to help optimize / regression test
|
||||
int settings_max_join_item_commands;
|
||||
float settings_colored_vertex_format_threshold;
|
||||
int settings_batch_buffer_num_verts;
|
||||
bool settings_scissor_lights;
|
||||
float settings_scissor_threshold; // 0.0 to 1.0
|
||||
int settings_item_reordering_lookahead;
|
||||
|
@ -609,6 +608,8 @@ private:
|
|||
bool _prefill_polygon(RasterizerCanvas::Item::CommandPolygon *p_poly, FillState &r_fill_state, int &r_command_start, int command_num, int command_count, RasterizerCanvas::Item *p_item, bool multiply_final_modulate);
|
||||
template <bool SEND_LIGHT_ANGLES>
|
||||
bool _prefill_rect(RasterizerCanvas::Item::CommandRect *rect, FillState &r_fill_state, int &r_command_start, int command_num, int command_count, RasterizerCanvas::Item::Command *const *commands, RasterizerCanvas::Item *p_item, bool multiply_final_modulate);
|
||||
template <bool SEND_LIGHT_ANGLES>
|
||||
bool _prefill_multirect(RasterizerCanvas::Item::CommandMultiRect *mrect, FillState &r_fill_state, int &r_command_start, int command_num, bool multiply_final_modulate);
|
||||
|
||||
// dealing with textures
|
||||
int _batch_find_or_create_tex(const RID &p_texture, const RID &p_normal, bool p_tile, int p_previous_match);
|
||||
|
@ -1078,7 +1079,7 @@ PREAMBLE(void)::batch_initialize() {
|
|||
// The sweet spot on my desktop for cache is actually smaller than the max, and this
|
||||
// is the default. This saves memory too so we will use it for now, needs testing to see whether this varies according
|
||||
// to device / platform.
|
||||
bdata.settings_batch_buffer_num_verts = GLOBAL_GET("rendering/batching/parameters/batch_buffer_size");
|
||||
int batch_buffer_num_verts_requested = GLOBAL_GET("rendering/batching/parameters/batch_buffer_size");
|
||||
|
||||
// override the use_batching setting in the editor
|
||||
// (note that if the editor can't start, you can't change the use_batching project setting!)
|
||||
|
@ -1113,7 +1114,6 @@ PREAMBLE(void)::batch_initialize() {
|
|||
// frame diagnosis. print out the batches every nth frame
|
||||
bdata.settings_diagnose_frame = false;
|
||||
if (!Engine::get_singleton()->is_editor_hint() && bdata.settings_use_batching) {
|
||||
// {
|
||||
bdata.settings_diagnose_frame = GLOBAL_GET("rendering/batching/debug/diagnose_frame");
|
||||
}
|
||||
|
||||
|
@ -1123,10 +1123,15 @@ PREAMBLE(void)::batch_initialize() {
|
|||
// Note this determines the memory use by the vertex buffer vector. max quads (65536/4)-1
|
||||
// but can be reduced to save memory if really required (will result in more batches though)
|
||||
const int max_possible_quads = (65536 / 4) - 1;
|
||||
const int min_possible_quads = 8; // some reasonable small value
|
||||
|
||||
// We must have enough quads to fit in a MultiRect
|
||||
const int min_possible_quads = MAX(8, MultiRect::MAX_RECTS); // some reasonable small value
|
||||
|
||||
// value from project settings
|
||||
int max_quads = bdata.settings_batch_buffer_num_verts / 4;
|
||||
int max_quads = batch_buffer_num_verts_requested / 4;
|
||||
|
||||
bool use_multirect = GLOBAL_GET("rendering/batching/options/use_multirect");
|
||||
VisualServerCanvasHelper::_multirect_enabled = (bdata.settings_use_batching && use_multirect);
|
||||
|
||||
// sanity checks
|
||||
max_quads = CLAMP(max_quads, min_possible_quads, max_possible_quads);
|
||||
|
@ -1136,22 +1141,6 @@ PREAMBLE(void)::batch_initialize() {
|
|||
bdata.settings_light_max_join_items = CLAMP(bdata.settings_light_max_join_items, 0, 65535);
|
||||
bdata.settings_item_reordering_lookahead = CLAMP(bdata.settings_item_reordering_lookahead, 0, 65535);
|
||||
|
||||
// For debug purposes, output a string with the batching options.
|
||||
if (bdata.settings_use_batching) {
|
||||
String batching_options_string = "OpenGL ES 2D Batching: ON\n";
|
||||
batching_options_string += "Batching Options:\n";
|
||||
batching_options_string += "\tmax_join_item_commands " + itos(bdata.settings_max_join_item_commands) + "\n";
|
||||
batching_options_string += "\tcolored_vertex_format_threshold " + String(Variant(bdata.settings_colored_vertex_format_threshold)) + "\n";
|
||||
batching_options_string += "\tbatch_buffer_size " + itos(bdata.settings_batch_buffer_num_verts) + "\n";
|
||||
batching_options_string += "\tlight_scissor_area_threshold " + String(Variant(bdata.settings_scissor_threshold)) + "\n";
|
||||
batching_options_string += "\titem_reordering_lookahead " + itos(bdata.settings_item_reordering_lookahead) + "\n";
|
||||
batching_options_string += "\tlight_max_join_items " + itos(bdata.settings_light_max_join_items) + "\n";
|
||||
batching_options_string += "\tsingle_rect_fallback " + String(Variant(bdata.settings_use_single_rect_fallback)) + "\n";
|
||||
batching_options_string += "\tdebug_flash " + String(Variant(bdata.settings_flash_batching)) + "\n";
|
||||
batching_options_string += "\tdiagnose_frame " + String(Variant(bdata.settings_diagnose_frame));
|
||||
print_verbose(batching_options_string);
|
||||
}
|
||||
|
||||
// special case, for colored vertex format threshold.
|
||||
// as the comparison is >=, we want to be able to totally turn on or off
|
||||
// conversion to colored vertex format at the extremes, so we will force
|
||||
|
@ -1181,6 +1170,22 @@ PREAMBLE(void)::batch_initialize() {
|
|||
bdata.vertex_buffer_size_bytes = max_verts * sizeof_batch_vert;
|
||||
bdata.index_buffer_size_bytes = bdata.index_buffer_size_units * 2; // 16 bit inds
|
||||
|
||||
// For debug purposes, output a string with the batching options.
|
||||
if (bdata.settings_use_batching) {
|
||||
String batching_options_string = "OpenGL ES 2D Batching: ON\n";
|
||||
batching_options_string += "Batching Options:\n";
|
||||
batching_options_string += "\tmax_join_item_commands " + itos(bdata.settings_max_join_item_commands) + "\n";
|
||||
batching_options_string += "\tcolored_vertex_format_threshold " + String(Variant(bdata.settings_colored_vertex_format_threshold)) + "\n";
|
||||
batching_options_string += "\tbatch_buffer_effective_size " + itos(bdata.vertex_buffer_size_units) + "\n";
|
||||
batching_options_string += "\tlight_scissor_area_threshold " + String(Variant(bdata.settings_scissor_threshold)) + "\n";
|
||||
batching_options_string += "\titem_reordering_lookahead " + itos(bdata.settings_item_reordering_lookahead) + "\n";
|
||||
batching_options_string += "\tlight_max_join_items " + itos(bdata.settings_light_max_join_items) + "\n";
|
||||
batching_options_string += "\tsingle_rect_fallback " + String(Variant(bdata.settings_use_single_rect_fallback)) + "\n";
|
||||
batching_options_string += "\tdebug_flash " + String(Variant(bdata.settings_flash_batching)) + "\n";
|
||||
batching_options_string += "\tdiagnose_frame " + String(Variant(bdata.settings_diagnose_frame));
|
||||
print_verbose(batching_options_string);
|
||||
}
|
||||
|
||||
// create equal number of normal and (max) unit sized verts (as the normal may need to be translated to a larger FVF)
|
||||
bdata.vertices.create(max_verts); // 512k
|
||||
bdata.unit_vertices.create(max_verts, sizeof(BatchVertexLarge));
|
||||
|
@ -2247,6 +2252,425 @@ bool C_PREAMBLE::_prefill_rect(RasterizerCanvas::Item::CommandRect *rect, FillSt
|
|||
return false;
|
||||
}
|
||||
|
||||
T_PREAMBLE
|
||||
template <bool SEND_LIGHT_ANGLES>
|
||||
bool C_PREAMBLE::_prefill_multirect(RasterizerCanvas::Item::CommandMultiRect *mrect, FillState &r_fill_state, int &r_command_start, int command_num, bool multiply_final_modulate) {
|
||||
bool change_batch = false;
|
||||
|
||||
// conditions for creating a new batch
|
||||
if (r_fill_state.curr_batch->type != RasterizerStorageCommon::BT_RECT) {
|
||||
// don't allow joining to a different sequence type
|
||||
if (r_fill_state.sequence_batch_type_flags & (~RasterizerStorageCommon::BTF_RECT)) {
|
||||
// don't allow joining to a different sequence type
|
||||
r_command_start = command_num;
|
||||
return true;
|
||||
}
|
||||
r_fill_state.sequence_batch_type_flags |= RasterizerStorageCommon::BTF_RECT;
|
||||
|
||||
change_batch = true;
|
||||
}
|
||||
|
||||
// try to create vertices BEFORE creating a batch,
|
||||
// because if the vertex buffer is full, we need to finish this
|
||||
// function, draw what we have so far, and then start a new set of batches
|
||||
|
||||
// request ALL vertices at a time, this is more efficient
|
||||
uint32_t total_verts = 4 * mrect->rects.size();
|
||||
|
||||
BatchVertex *bvs = bdata.vertices.request(total_verts);
|
||||
if (!bvs) {
|
||||
// run out of space in the vertex buffer .. finish this function and draw what we have so far
|
||||
// return where we got to
|
||||
r_command_start = command_num;
|
||||
|
||||
// Check for an error condition - if we have been creating MultiRects that require more than
|
||||
// the maximum number of verts in the buffer, this could cause an infinite loop.
|
||||
ERR_FAIL_COND_V(total_verts > bdata.vertex_buffer_size_units, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
// are we using large FVF?
|
||||
const bool use_large_verts = bdata.use_large_verts;
|
||||
const bool use_modulate = bdata.use_modulate;
|
||||
|
||||
Color col = mrect->modulate;
|
||||
|
||||
// use_modulate and use_large_verts should have been checked in the calling prefill_item function.
|
||||
// we don't want to apply the modulate on the CPU if it is stored in the vertex format, it will
|
||||
// be applied in the shader
|
||||
if (multiply_final_modulate) {
|
||||
col *= r_fill_state.final_modulate;
|
||||
}
|
||||
|
||||
// instead of doing all the texture preparation for EVERY rect,
|
||||
// we build a list of texture combinations and do this once off.
|
||||
// This means we have a potentially rather slow step to identify which texture combo
|
||||
// using the RIDs.
|
||||
int old_batch_tex_id = r_fill_state.batch_tex_id;
|
||||
r_fill_state.batch_tex_id = _batch_find_or_create_tex(mrect->texture, mrect->normal_map, mrect->flags & RasterizerCanvas::CANVAS_RECT_TILE, old_batch_tex_id);
|
||||
|
||||
//r_fill_state.use_light_angles = send_light_angles;
|
||||
if (SEND_LIGHT_ANGLES) {
|
||||
bdata.use_light_angles = true;
|
||||
}
|
||||
|
||||
// conditions for creating a new batch
|
||||
if (old_batch_tex_id != r_fill_state.batch_tex_id) {
|
||||
change_batch = true;
|
||||
}
|
||||
|
||||
// we need to treat color change separately because we need to count these
|
||||
// to decide whether to switch on the fly to colored vertices.
|
||||
if (!change_batch && !r_fill_state.curr_batch->color.equals(col)) {
|
||||
change_batch = true;
|
||||
bdata.total_color_changes++;
|
||||
}
|
||||
|
||||
uint32_t num_rects = mrect->rects.size();
|
||||
|
||||
if (change_batch) {
|
||||
// put the tex pixel size in a local (less verbose and can be a register)
|
||||
const BatchTex &batchtex = bdata.batch_textures[r_fill_state.batch_tex_id];
|
||||
batchtex.tex_pixel_size.to(r_fill_state.texpixel_size);
|
||||
|
||||
if (bdata.settings_uv_contract) {
|
||||
r_fill_state.contract_uvs = (batchtex.flags & VS::TEXTURE_FLAG_FILTER) == 0;
|
||||
}
|
||||
|
||||
// need to preserve texpixel_size between items
|
||||
//r_fill_state.texpixel_size = r_fill_state.texpixel_size;
|
||||
|
||||
// open new batch (this should never fail, it dynamically grows)
|
||||
r_fill_state.curr_batch = _batch_request_new(false);
|
||||
|
||||
r_fill_state.curr_batch->type = RasterizerStorageCommon::BT_RECT;
|
||||
r_fill_state.curr_batch->color.set(col);
|
||||
r_fill_state.curr_batch->batch_texture_id = r_fill_state.batch_tex_id;
|
||||
r_fill_state.curr_batch->first_command = command_num;
|
||||
r_fill_state.curr_batch->num_commands = num_rects;
|
||||
//r_fill_state.curr_batch->first_quad = bdata.total_quads;
|
||||
r_fill_state.curr_batch->first_vert = bdata.total_verts;
|
||||
} else {
|
||||
// we could alternatively do the count when closing a batch .. perhaps more efficient
|
||||
r_fill_state.curr_batch->num_commands += num_rects;
|
||||
}
|
||||
|
||||
// test for simplified pipeline
|
||||
const uint8_t disallow_flags = RasterizerCanvas::CANVAS_RECT_TRANSPOSE | RasterizerCanvas::CANVAS_RECT_FLIP_H | RasterizerCanvas::CANVAS_RECT_FLIP_V;
|
||||
if ((mrect->flags & RasterizerCanvas::CANVAS_RECT_REGION) && ((mrect->flags & disallow_flags) == 0)) {
|
||||
// simplified pipeline
|
||||
for (uint32_t n = 0; n < num_rects; n++) {
|
||||
const Rect2 &rect = mrect->rects[n];
|
||||
const Rect2 &source = mrect->sources[n];
|
||||
|
||||
// fill the quad geometry
|
||||
Vector2 mins = rect.position;
|
||||
|
||||
// just aliases
|
||||
BatchVertex *bA = &bvs[0];
|
||||
BatchVertex *bB = &bvs[1];
|
||||
BatchVertex *bC = &bvs[2];
|
||||
BatchVertex *bD = &bvs[3];
|
||||
|
||||
// possibility of applying flips here for normal mapping .. but they don't seem to be used
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (rect.size.x < 0) {
|
||||
ERR_PRINT_ONCE("MultiRect with negative size detected. Ensure rects are non-negative.");
|
||||
}
|
||||
if (rect.size.y < 0) {
|
||||
ERR_PRINT_ONCE("MultiRect with negative size detected. Ensure rects are non-negative.");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (r_fill_state.transform_mode == TM_TRANSLATE) {
|
||||
if (!use_large_verts) {
|
||||
_software_transform_vertex(mins, r_fill_state.transform_combined);
|
||||
}
|
||||
}
|
||||
Vector2 maxs = mins + rect.size;
|
||||
|
||||
bA->pos.x = mins.x;
|
||||
bA->pos.y = mins.y;
|
||||
|
||||
bB->pos.x = maxs.x;
|
||||
bB->pos.y = mins.y;
|
||||
|
||||
bC->pos.x = maxs.x;
|
||||
bC->pos.y = maxs.y;
|
||||
|
||||
bD->pos.x = mins.x;
|
||||
bD->pos.y = maxs.y;
|
||||
|
||||
if (r_fill_state.transform_mode == TM_ALL) {
|
||||
if (!use_large_verts) {
|
||||
_software_transform_vertex(bA->pos, r_fill_state.transform_combined);
|
||||
_software_transform_vertex(bB->pos, r_fill_state.transform_combined);
|
||||
_software_transform_vertex(bC->pos, r_fill_state.transform_combined);
|
||||
_software_transform_vertex(bD->pos, r_fill_state.transform_combined);
|
||||
}
|
||||
}
|
||||
|
||||
// uvs
|
||||
Vector2 src_min;
|
||||
Vector2 src_max;
|
||||
src_min = source.position;
|
||||
src_max = src_min + source.size;
|
||||
|
||||
src_min *= r_fill_state.texpixel_size;
|
||||
src_max *= r_fill_state.texpixel_size;
|
||||
|
||||
const float uv_epsilon = bdata.settings_uv_contract_amount;
|
||||
|
||||
// nudge offset for the maximum to prevent precision error on GPU reading into line outside the source rect
|
||||
// this is very difficult to get right.
|
||||
if (r_fill_state.contract_uvs) {
|
||||
src_min.x += uv_epsilon;
|
||||
src_min.y += uv_epsilon;
|
||||
src_max.x -= uv_epsilon;
|
||||
src_max.y -= uv_epsilon;
|
||||
}
|
||||
|
||||
// 10% faster calculating the max first
|
||||
Vector2 uvs[4] = {
|
||||
src_min,
|
||||
Vector2(src_max.x, src_min.y),
|
||||
src_max,
|
||||
Vector2(src_min.x, src_max.y),
|
||||
};
|
||||
|
||||
bA->uv.set(uvs[0]);
|
||||
bB->uv.set(uvs[1]);
|
||||
bC->uv.set(uvs[2]);
|
||||
bD->uv.set(uvs[3]);
|
||||
|
||||
bvs += 4; // move the destination verts on by 4 each rect
|
||||
} // for n through rects
|
||||
|
||||
} else {
|
||||
// full pipeline
|
||||
for (uint32_t n = 0; n < num_rects; n++) {
|
||||
const Rect2 &rect = mrect->rects[n];
|
||||
const Rect2 &source = mrect->sources[n];
|
||||
|
||||
// fill the quad geometry
|
||||
Vector2 mins = rect.position;
|
||||
|
||||
if (r_fill_state.transform_mode == TM_TRANSLATE) {
|
||||
if (!use_large_verts) {
|
||||
_software_transform_vertex(mins, r_fill_state.transform_combined);
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 maxs = mins + rect.size;
|
||||
|
||||
// just aliases
|
||||
BatchVertex *bA = &bvs[0];
|
||||
BatchVertex *bB = &bvs[1];
|
||||
BatchVertex *bC = &bvs[2];
|
||||
BatchVertex *bD = &bvs[3];
|
||||
|
||||
bA->pos.x = mins.x;
|
||||
bA->pos.y = mins.y;
|
||||
|
||||
bB->pos.x = maxs.x;
|
||||
bB->pos.y = mins.y;
|
||||
|
||||
bC->pos.x = maxs.x;
|
||||
bC->pos.y = maxs.y;
|
||||
|
||||
bD->pos.x = mins.x;
|
||||
bD->pos.y = maxs.y;
|
||||
|
||||
// possibility of applying flips here for normal mapping .. but they don't seem to be used
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (rect.size.x < 0) {
|
||||
//SWAP(bA->pos, bB->pos);
|
||||
//SWAP(bC->pos, bD->pos);
|
||||
ERR_PRINT_ONCE("MultiRect with negative size detected. Ensure rects are non-negative.");
|
||||
}
|
||||
if (rect.size.y < 0) {
|
||||
//SWAP(bA->pos, bD->pos);
|
||||
//SWAP(bB->pos, bC->pos);
|
||||
ERR_PRINT_ONCE("MultiRect with negative size detected. Ensure rects are non-negative.");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (r_fill_state.transform_mode == TM_ALL) {
|
||||
if (!use_large_verts) {
|
||||
_software_transform_vertex(bA->pos, r_fill_state.transform_combined);
|
||||
_software_transform_vertex(bB->pos, r_fill_state.transform_combined);
|
||||
_software_transform_vertex(bC->pos, r_fill_state.transform_combined);
|
||||
_software_transform_vertex(bD->pos, r_fill_state.transform_combined);
|
||||
}
|
||||
}
|
||||
|
||||
// uvs
|
||||
Vector2 src_min;
|
||||
Vector2 src_max;
|
||||
if (mrect->flags & RasterizerCanvas::CANVAS_RECT_REGION) {
|
||||
src_min = source.position;
|
||||
src_max = src_min + source.size;
|
||||
|
||||
src_min *= r_fill_state.texpixel_size;
|
||||
src_max *= r_fill_state.texpixel_size;
|
||||
|
||||
const float uv_epsilon = bdata.settings_uv_contract_amount;
|
||||
|
||||
// nudge offset for the maximum to prevent precision error on GPU reading into line outside the source rect
|
||||
// this is very difficult to get right.
|
||||
if (r_fill_state.contract_uvs) {
|
||||
src_min.x += uv_epsilon;
|
||||
src_min.y += uv_epsilon;
|
||||
src_max.x -= uv_epsilon;
|
||||
src_max.y -= uv_epsilon;
|
||||
}
|
||||
} else {
|
||||
src_min = Vector2(0, 0);
|
||||
src_max = Vector2(1, 1);
|
||||
}
|
||||
|
||||
// 10% faster calculating the max first
|
||||
Vector2 uvs[4] = {
|
||||
src_min,
|
||||
Vector2(src_max.x, src_min.y),
|
||||
src_max,
|
||||
Vector2(src_min.x, src_max.y),
|
||||
};
|
||||
|
||||
if (mrect->flags & RasterizerCanvas::CANVAS_RECT_TRANSPOSE) {
|
||||
SWAP(uvs[1], uvs[3]);
|
||||
}
|
||||
|
||||
if (mrect->flags & RasterizerCanvas::CANVAS_RECT_FLIP_H) {
|
||||
SWAP(uvs[0], uvs[1]);
|
||||
SWAP(uvs[2], uvs[3]);
|
||||
}
|
||||
if (mrect->flags & RasterizerCanvas::CANVAS_RECT_FLIP_V) {
|
||||
SWAP(uvs[0], uvs[3]);
|
||||
SWAP(uvs[1], uvs[2]);
|
||||
}
|
||||
|
||||
bA->uv.set(uvs[0]);
|
||||
bB->uv.set(uvs[1]);
|
||||
bC->uv.set(uvs[2]);
|
||||
bD->uv.set(uvs[3]);
|
||||
|
||||
bvs += 4; // move the destination verts on by 4 each rect
|
||||
} // for n through rects
|
||||
} // full pipeline
|
||||
|
||||
// modulate
|
||||
if (use_modulate) {
|
||||
// store the final modulate separately from the rect modulate
|
||||
BatchColor *pBC = bdata.vertex_modulates.request(total_verts);
|
||||
RAST_DEBUG_ASSERT(pBC);
|
||||
pBC[0].set(r_fill_state.final_modulate);
|
||||
for (uint32_t n = 1; n < total_verts; n++) {
|
||||
pBC[n] = pBC[0];
|
||||
}
|
||||
}
|
||||
|
||||
// they will all have the same vertex transforms
|
||||
if (use_large_verts) {
|
||||
// store the transform separately
|
||||
BatchTransform *pBT = bdata.vertex_transforms.request(total_verts);
|
||||
RAST_DEBUG_ASSERT(pBT);
|
||||
BatchTransform *pBT_first = pBT;
|
||||
|
||||
const Transform2D &tr = r_fill_state.transform_combined;
|
||||
|
||||
pBT[0].translate.set(tr.elements[2]);
|
||||
|
||||
pBT[0].basis[0].set(tr.elements[0][0], tr.elements[0][1]);
|
||||
pBT[0].basis[1].set(tr.elements[1][0], tr.elements[1][1]);
|
||||
|
||||
for (uint32_t n = 1; n < num_rects * 4; n++) {
|
||||
pBT++;
|
||||
*pBT = *pBT_first;
|
||||
}
|
||||
}
|
||||
|
||||
if (SEND_LIGHT_ANGLES) {
|
||||
// SAME FOR ALL
|
||||
// for encoding in light angle
|
||||
bool flip_h = false;
|
||||
bool flip_v = false;
|
||||
|
||||
if (mrect->flags & RasterizerCanvas::CANVAS_RECT_FLIP_H) {
|
||||
flip_h = !flip_h;
|
||||
flip_v = !flip_v;
|
||||
}
|
||||
if (mrect->flags & RasterizerCanvas::CANVAS_RECT_FLIP_V) {
|
||||
flip_v = !flip_v;
|
||||
}
|
||||
|
||||
// we can either keep the light angles in sync with the verts when writing,
|
||||
// or sync them up during translation. We are syncing in translation.
|
||||
// N.B. There may be batches that don't require light_angles between batches that do.
|
||||
float *angles = bdata.light_angles.request(total_verts);
|
||||
RAST_DEBUG_ASSERT(angles);
|
||||
|
||||
float angle = 0.0f;
|
||||
const float TWO_PI = Math_PI * 2;
|
||||
|
||||
if (r_fill_state.transform_mode != TM_NONE) {
|
||||
const Transform2D &tr = r_fill_state.transform_combined;
|
||||
|
||||
// apply to an x axis
|
||||
// the x axis and y axis can be taken directly from the transform (no need to xform identity vectors)
|
||||
Vector2 x_axis(tr.elements[0][0], tr.elements[0][1]);
|
||||
|
||||
// have to do a y axis to check for scaling flips
|
||||
// this is hassle and extra slowness. We could only allow flips via the flags.
|
||||
Vector2 y_axis(tr.elements[1][0], tr.elements[1][1]);
|
||||
|
||||
// has the x / y axis flipped due to scaling?
|
||||
float cross = x_axis.cross(y_axis);
|
||||
if (cross < 0.0f) {
|
||||
flip_v = !flip_v;
|
||||
}
|
||||
|
||||
// passing an angle is smaller than a vector, it can be reconstructed in the shader
|
||||
angle = x_axis.angle();
|
||||
|
||||
// we don't want negative angles, as negative is used to encode flips.
|
||||
// This moves range from -PI to PI to 0 to TWO_PI
|
||||
if (angle < 0.0f) {
|
||||
angle += TWO_PI;
|
||||
}
|
||||
|
||||
} // if transform needed
|
||||
|
||||
// if horizontal flip, angle is shifted by 180 degrees
|
||||
if (flip_h) {
|
||||
angle += Math_PI;
|
||||
|
||||
// mod to get back to 0 to TWO_PI range
|
||||
angle = fmodf(angle, TWO_PI);
|
||||
}
|
||||
|
||||
// add 1 (to take care of zero floating point error with sign)
|
||||
angle += 1.0f;
|
||||
|
||||
// flip if necessary to indicate a vertical flip in the shader
|
||||
if (flip_v) {
|
||||
angle *= -1.0f;
|
||||
}
|
||||
|
||||
// light angle must be sent for each vert, instead as a single uniform in the uniform draw method
|
||||
// this has the benefit of enabling batching with light angles.
|
||||
for (uint32_t n = 0; n < total_verts; n++) {
|
||||
angles[n] = angle;
|
||||
}
|
||||
}
|
||||
|
||||
// increment quad count
|
||||
bdata.total_quads += num_rects;
|
||||
bdata.total_verts += total_verts;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// This function may be called MULTIPLE TIMES for each item, so needs to record how far it has got
|
||||
PREAMBLE(bool)::prefill_joined_item(FillState &r_fill_state, int &r_command_start, RasterizerCanvas::Item *p_item, RasterizerCanvas::Item *p_current_clip, bool &r_reclip, typename T_STORAGE::Material *p_material) {
|
||||
// we will prefill batches and vertices ready for sending in one go to the vertex buffer
|
||||
|
@ -2338,6 +2762,29 @@ PREAMBLE(bool)::prefill_joined_item(FillState &r_fill_state, int &r_command_star
|
|||
return true;
|
||||
}
|
||||
|
||||
} break;
|
||||
case RasterizerCanvas::Item::Command::TYPE_MULTIRECT: {
|
||||
RasterizerCanvas::Item::CommandMultiRect *mrect = static_cast<RasterizerCanvas::Item::CommandMultiRect *>(command);
|
||||
|
||||
// MultRects with no rects should ideally not be created
|
||||
ERR_CONTINUE(!mrect->rects.size());
|
||||
|
||||
bool send_light_angles = mrect->normal_map != RID();
|
||||
|
||||
bool buffer_full = false;
|
||||
|
||||
// the template params must be explicit for compilation,
|
||||
// this forces building the multiple versions of the function.
|
||||
if (send_light_angles) {
|
||||
buffer_full = _prefill_multirect<true>(mrect, r_fill_state, r_command_start, command_num, multiply_final_modulate);
|
||||
} else {
|
||||
buffer_full = _prefill_multirect<false>(mrect, r_fill_state, r_command_start, command_num, multiply_final_modulate);
|
||||
}
|
||||
|
||||
if (buffer_full) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} break;
|
||||
case RasterizerCanvas::Item::Command::TYPE_NINEPATCH: {
|
||||
RasterizerCanvas::Item::CommandNinePatch *np = static_cast<RasterizerCanvas::Item::CommandNinePatch *>(command);
|
||||
|
@ -2767,7 +3214,7 @@ PREAMBLE(bool)::_sort_items_match(const BSortItem &p_a, const BSortItem &p_b) co
|
|||
// return false;
|
||||
|
||||
const RasterizerCanvas::Item::Command &cb = *b->commands[0];
|
||||
if (cb.type != RasterizerCanvas::Item::Command::TYPE_RECT) {
|
||||
if ((cb.type != RasterizerCanvas::Item::Command::TYPE_RECT) && (cb.type != RasterizerCanvas::Item::Command::TYPE_MULTIRECT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -2830,7 +3277,7 @@ PREAMBLE(bool)::sort_items_from(int p_start) {
|
|||
return false;
|
||||
}
|
||||
const RasterizerCanvas::Item::Command &command_start = *start.item->commands[0];
|
||||
if (command_start.type != RasterizerCanvas::Item::Command::TYPE_RECT) {
|
||||
if ((command_start.type != RasterizerCanvas::Item::Command::TYPE_RECT) && (command_start.type != RasterizerCanvas::Item::Command::TYPE_MULTIRECT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -3215,6 +3662,11 @@ PREAMBLE(bool)::_detect_item_batch_break(RenderItemState &r_ris, RasterizerCanva
|
|||
return true;
|
||||
}
|
||||
} break;
|
||||
case RasterizerCanvas::Item::Command::TYPE_MULTIRECT: {
|
||||
if (_disallow_item_join_if_batch_types_too_different(r_ris, RasterizerStorageCommon::BTF_RECT)) {
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
case RasterizerCanvas::Item::Command::TYPE_NINEPATCH: {
|
||||
// do not handle tiled ninepatches, these can't be batched and need to use legacy method
|
||||
RasterizerCanvas::Item::CommandNinePatch *np = static_cast<RasterizerCanvas::Item::CommandNinePatch *>(command);
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include "scene/2d/area_2d.h"
|
||||
#include "servers/navigation_2d_server.h"
|
||||
#include "servers/physics_2d_server.h"
|
||||
#include "servers/visual/visual_server_canvas_helper.h"
|
||||
|
||||
void TileMap::Quadrant::clear_navpoly() {
|
||||
for (Map<PosKey, Quadrant::NavPoly>::Element *E = navpoly_ids.front(); E; E = E->next()) {
|
||||
|
@ -406,6 +407,8 @@ void TileMap::update_dirty_quadrants() {
|
|||
RID prev_canvas_item;
|
||||
RID prev_debug_canvas_item;
|
||||
|
||||
bool multirect_started = false;
|
||||
|
||||
for (int i = 0; i < q.cells.size(); i++) {
|
||||
Map<PosKey, Cell>::Element *E = tile_map.find(q.cells[i]);
|
||||
Cell &c = E->get();
|
||||
|
@ -568,7 +571,11 @@ void TileMap::update_dirty_quadrants() {
|
|||
if (r == Rect2()) {
|
||||
tex->draw_rect(canvas_item, rect, false, modulate, c.transpose, normal_map);
|
||||
} else {
|
||||
tex->draw_rect_region(canvas_item, rect, r, modulate, c.transpose, normal_map, clip_uv);
|
||||
if (!multirect_started) {
|
||||
multirect_started = true;
|
||||
VisualServerCanvasHelper::tilemap_begin();
|
||||
}
|
||||
VisualServerCanvasHelper::tilemap_add_rect(canvas_item, rect, tex->get_rid(), r, modulate, c.transpose, normal_map.is_valid() ? normal_map->get_rid() : RID(), clip_uv);
|
||||
}
|
||||
|
||||
Vector<TileSet::ShapeData> shapes = tile_set->tile_get_shapes(c.id);
|
||||
|
@ -719,6 +726,10 @@ void TileMap::update_dirty_quadrants() {
|
|||
}
|
||||
}
|
||||
|
||||
if (multirect_started) {
|
||||
VisualServerCanvasHelper::tilemap_end();
|
||||
}
|
||||
|
||||
dirty_quadrant_list.remove(dirty_quadrant_list.first());
|
||||
quadrant_order_dirty = true;
|
||||
}
|
||||
|
|
|
@ -404,6 +404,8 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
|
|||
bool just_breaked_in_middle = false;
|
||||
rchar = 0;
|
||||
FontDrawer drawer(font, Color(1, 1, 1));
|
||||
MultiRect &multirect = drawer.get_multirect();
|
||||
|
||||
while (*c) {
|
||||
int end = 0;
|
||||
float w = 0.0f;
|
||||
|
@ -613,12 +615,12 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
|
|||
|
||||
const Point2 base_pos = p_ofs + Point2(align_ofs + pofs + spacing_char, y + lh - line_descent);
|
||||
if (shadow_color.a > 0) {
|
||||
font->draw_char(ci, base_pos + shadow_ofs + fx_offset, fx_char, c[i + 1], shadow_color);
|
||||
font->draw_char_ex(ci, base_pos + shadow_ofs + fx_offset, fx_char, c[i + 1], shadow_color, false, &multirect);
|
||||
|
||||
if (p_shadow_as_outline) {
|
||||
font->draw_char(ci, base_pos + Vector2(-shadow_ofs.x, shadow_ofs.y) + fx_offset, fx_char, c[i + 1], shadow_color);
|
||||
font->draw_char(ci, base_pos + Vector2(shadow_ofs.x, -shadow_ofs.y) + fx_offset, fx_char, c[i + 1], shadow_color);
|
||||
font->draw_char(ci, base_pos + Vector2(-shadow_ofs.x, -shadow_ofs.y) + fx_offset, fx_char, c[i + 1], shadow_color);
|
||||
font->draw_char_ex(ci, base_pos + Vector2(-shadow_ofs.x, shadow_ofs.y) + fx_offset, fx_char, c[i + 1], shadow_color, false, &multirect);
|
||||
font->draw_char_ex(ci, base_pos + Vector2(shadow_ofs.x, -shadow_ofs.y) + fx_offset, fx_char, c[i + 1], shadow_color, false, &multirect);
|
||||
font->draw_char_ex(ci, base_pos + Vector2(-shadow_ofs.x, -shadow_ofs.y) + fx_offset, fx_char, c[i + 1], shadow_color, false, &multirect);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
|
||||
#include "core/os/file_access.h"
|
||||
#include "core/os/os.h"
|
||||
#include "servers/visual/visual_server_canvas_helper.h"
|
||||
|
||||
#include FT_STROKER_H
|
||||
|
||||
|
@ -498,7 +499,7 @@ Rect2 DynamicFontAtSize::get_char_tx_uv_rect(CharType p_char, CharType p_next, c
|
|||
return Rect2();
|
||||
}
|
||||
|
||||
float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize>> &p_fallbacks, bool p_advance_only, bool p_outline) const {
|
||||
float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize>> &p_fallbacks, bool p_advance_only, bool p_outline, MultiRect *p_multirect) const {
|
||||
if (!valid) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -561,7 +562,11 @@ float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharT
|
|||
modulate.r = modulate.g = modulate.b = 1.0;
|
||||
}
|
||||
RID texture = font->textures[ch->texture_idx].texture->get_rid();
|
||||
VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, ch->rect.size), texture, ch->rect_uv, modulate, false, RID(), false);
|
||||
if (p_multirect) {
|
||||
p_multirect->add_rect(p_canvas_item, Rect2(cpos, ch->rect.size), texture, ch->rect_uv, modulate, false, RID(), false);
|
||||
} else {
|
||||
VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, ch->rect.size), texture, ch->rect_uv, modulate, false, RID(), false);
|
||||
}
|
||||
}
|
||||
|
||||
advance = ch->advance;
|
||||
|
@ -1191,7 +1196,7 @@ Rect2 DynamicFont::get_char_tx_uv_rect(CharType p_char, CharType p_next, bool p_
|
|||
}
|
||||
}
|
||||
|
||||
float DynamicFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline) const {
|
||||
float DynamicFont::draw_char_ex(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline, MultiRect *p_multirect) const {
|
||||
if (!data_at_size.is_valid()) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -1203,11 +1208,11 @@ float DynamicFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_
|
|||
|
||||
if (p_outline) {
|
||||
if (outline_data_at_size.is_valid() && outline_cache_id.outline_size > 0) {
|
||||
outline_data_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate * outline_color, fallback_outline_data_at_size, false, true); // Draw glyph outline.
|
||||
outline_data_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate * outline_color, fallback_outline_data_at_size, false, true, p_multirect); // Draw glyph outline.
|
||||
}
|
||||
return data_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate, fallback_data_at_size, true, false) + spacing; // Return advance of the base glyph.
|
||||
return data_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate, fallback_data_at_size, true, false, p_multirect) + spacing; // Return advance of the base glyph.
|
||||
} else {
|
||||
return data_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate, fallback_data_at_size, false, false) + spacing; // Draw base glyph and return advance.
|
||||
return data_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate, fallback_data_at_size, false, false, p_multirect) + spacing; // Draw base glyph and return advance.
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -259,7 +259,7 @@ public:
|
|||
Size2 get_char_size(CharType p_char, CharType p_next, const Vector<Ref<DynamicFontAtSize>> &p_fallbacks) const;
|
||||
String get_available_chars() const;
|
||||
|
||||
float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize>> &p_fallbacks, bool p_advance_only = false, bool p_outline = false) const;
|
||||
float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize>> &p_fallbacks, bool p_advance_only = false, bool p_outline = false, MultiRect *p_multirect = nullptr) const;
|
||||
|
||||
RID get_char_texture(CharType p_char, CharType p_next, const Vector<Ref<DynamicFontAtSize>> &p_fallbacks) const;
|
||||
Size2 get_char_texture_size(CharType p_char, CharType p_next, const Vector<Ref<DynamicFontAtSize>> &p_fallbacks) const;
|
||||
|
@ -361,7 +361,7 @@ public:
|
|||
|
||||
virtual bool has_outline() const;
|
||||
|
||||
virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const;
|
||||
virtual float draw_char_ex(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false, MultiRect *p_multirect = nullptr) const;
|
||||
|
||||
RID get_char_texture(CharType p_char, CharType p_next, bool p_outline) const;
|
||||
Size2 get_char_texture_size(CharType p_char, CharType p_next, bool p_outline) const;
|
||||
|
|
|
@ -64,6 +64,9 @@ void Font::draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, co
|
|||
|
||||
int chars_drawn = 0;
|
||||
bool with_outline = has_outline();
|
||||
|
||||
MultiRect multirect;
|
||||
|
||||
for (int i = 0; i < p_text.length(); i++) {
|
||||
int width = get_char_size(p_text[i]).width;
|
||||
|
||||
|
@ -71,14 +74,14 @@ void Font::draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, co
|
|||
break; //clip
|
||||
}
|
||||
|
||||
ofs.x += draw_char(p_canvas_item, p_pos + ofs, p_text[i], p_text[i + 1], with_outline ? p_outline_modulate : p_modulate, with_outline);
|
||||
ofs.x += draw_char_ex(p_canvas_item, p_pos + ofs, p_text[i], p_text[i + 1], with_outline ? p_outline_modulate : p_modulate, with_outline, &multirect);
|
||||
++chars_drawn;
|
||||
}
|
||||
|
||||
if (has_outline()) {
|
||||
ofs = Vector2(0, 0);
|
||||
for (int i = 0; i < chars_drawn; i++) {
|
||||
ofs.x += draw_char(p_canvas_item, p_pos + ofs, p_text[i], p_text[i + 1], p_modulate, false);
|
||||
ofs.x += draw_char_ex(p_canvas_item, p_pos + ofs, p_text[i], p_text[i + 1], p_modulate, false, &multirect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -535,6 +538,21 @@ Size2 Font::total_size_of_lines(Vector<String> p_lines) {
|
|||
return size;
|
||||
}
|
||||
|
||||
FontDrawer::FontDrawer(const Ref<Font> &p_font, const Color &p_outline_color) :
|
||||
font(p_font),
|
||||
outline_color(p_outline_color) {
|
||||
has_outline = p_font->has_outline();
|
||||
multirect.begin();
|
||||
}
|
||||
|
||||
FontDrawer::~FontDrawer() {
|
||||
for (int i = 0; i < pending_draws.size(); ++i) {
|
||||
const PendingDraw &draw = pending_draws[i];
|
||||
font->draw_char_ex(draw.canvas_item, draw.pos, draw.chr, draw.next, draw.modulate, false, &multirect);
|
||||
}
|
||||
multirect.end();
|
||||
}
|
||||
|
||||
void BitmapFont::set_fallback(const Ref<BitmapFont> &p_fallback) {
|
||||
for (Ref<BitmapFont> fallback_child = p_fallback; fallback_child != nullptr; fallback_child = fallback_child->get_fallback()) {
|
||||
ERR_FAIL_COND_MSG(fallback_child == this, "Can't set as fallback one of its parents to prevent crashes due to recursive loop.");
|
||||
|
@ -681,7 +699,7 @@ Rect2 BitmapFont::get_char_tx_uv_rect(CharType p_char, CharType p_next, bool p_o
|
|||
}
|
||||
}
|
||||
|
||||
float BitmapFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline) const {
|
||||
float BitmapFont::draw_char_ex(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline, MultiRect *p_multirect) const {
|
||||
int32_t ch = p_char;
|
||||
if (((p_char & 0xfffffc00) == 0xd800) && (p_next & 0xfffffc00) == 0xdc00) { // decode surrogate pair.
|
||||
ch = (p_char << 10UL) + p_next - ((0xd800 << 10UL) + 0xdc00 - 0x10000);
|
||||
|
@ -705,7 +723,12 @@ float BitmapFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_c
|
|||
cpos.x += c->h_align;
|
||||
cpos.y -= ascent;
|
||||
cpos.y += c->v_align;
|
||||
VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, c->rect.size), textures[c->texture_idx]->get_rid(), c->rect, p_modulate, false, RID(), false);
|
||||
|
||||
if (p_multirect) {
|
||||
p_multirect->add_rect(p_canvas_item, Rect2(cpos, c->rect.size), textures[c->texture_idx]->get_rid(), c->rect, p_modulate, false, RID(), false);
|
||||
} else {
|
||||
VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, c->rect.size), textures[c->texture_idx]->get_rid(), c->rect, p_modulate, false, RID(), false);
|
||||
}
|
||||
}
|
||||
|
||||
return get_char_size(p_char, p_next).width;
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "core/map.h"
|
||||
#include "core/resource.h"
|
||||
#include "scene/resources/texture.h"
|
||||
#include "servers/visual/visual_server_canvas_helper.h"
|
||||
|
||||
class Font : public Resource {
|
||||
GDCLASS(Font, Resource);
|
||||
|
@ -65,7 +66,8 @@ public:
|
|||
void draw_halign(RID p_canvas_item, const Point2 &p_pos, HAlign p_align, float p_width, const String &p_text, const Color &p_modulate = Color(1, 1, 1), const Color &p_outline_modulate = Color(1, 1, 1)) const;
|
||||
|
||||
virtual bool has_outline() const { return false; }
|
||||
virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const = 0;
|
||||
float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const { return draw_char_ex(p_canvas_item, p_pos, p_char, p_next, p_modulate, p_outline); }
|
||||
virtual float draw_char_ex(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false, MultiRect *p_multirect = nullptr) const = 0;
|
||||
|
||||
virtual RID get_char_texture(CharType p_char, CharType p_next, bool p_outline) const = 0;
|
||||
virtual Size2 get_char_texture_size(CharType p_char, CharType p_next, bool p_outline) const = 0;
|
||||
|
@ -85,6 +87,7 @@ class FontDrawer {
|
|||
const Ref<Font> &font;
|
||||
Color outline_color;
|
||||
bool has_outline;
|
||||
MultiRect multirect;
|
||||
|
||||
struct PendingDraw {
|
||||
RID canvas_item;
|
||||
|
@ -97,26 +100,18 @@ class FontDrawer {
|
|||
Vector<PendingDraw> pending_draws;
|
||||
|
||||
public:
|
||||
FontDrawer(const Ref<Font> &p_font, const Color &p_outline_color) :
|
||||
font(p_font),
|
||||
outline_color(p_outline_color) {
|
||||
has_outline = p_font->has_outline();
|
||||
}
|
||||
FontDrawer(const Ref<Font> &p_font, const Color &p_outline_color);
|
||||
|
||||
float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) {
|
||||
if (has_outline) {
|
||||
PendingDraw draw = { p_canvas_item, p_pos, p_char, p_next, p_modulate };
|
||||
pending_draws.push_back(draw);
|
||||
}
|
||||
return font->draw_char(p_canvas_item, p_pos, p_char, p_next, has_outline ? outline_color : p_modulate, has_outline);
|
||||
return font->draw_char_ex(p_canvas_item, p_pos, p_char, p_next, has_outline ? outline_color : p_modulate, has_outline, &multirect);
|
||||
}
|
||||
MultiRect &get_multirect() { return multirect; }
|
||||
|
||||
~FontDrawer() {
|
||||
for (int i = 0; i < pending_draws.size(); ++i) {
|
||||
const PendingDraw &draw = pending_draws[i];
|
||||
font->draw_char(draw.canvas_item, draw.pos, draw.chr, draw.next, draw.modulate, false);
|
||||
}
|
||||
}
|
||||
~FontDrawer();
|
||||
};
|
||||
|
||||
class BitmapFont : public Font {
|
||||
|
@ -208,7 +203,7 @@ public:
|
|||
void set_distance_field_hint(bool p_distance_field);
|
||||
bool is_distance_field_hint() const;
|
||||
|
||||
float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const;
|
||||
float draw_char_ex(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false, MultiRect *p_multirect = nullptr) const;
|
||||
|
||||
RID get_char_texture(CharType p_char, CharType p_next, bool p_outline) const;
|
||||
Size2 get_char_texture_size(CharType p_char, CharType p_next, bool p_outline) const;
|
||||
|
|
|
@ -808,6 +808,7 @@ public:
|
|||
TYPE_CIRCLE,
|
||||
TYPE_TRANSFORM,
|
||||
TYPE_CLIP_IGNORE,
|
||||
TYPE_MULTIRECT,
|
||||
};
|
||||
|
||||
Type type;
|
||||
|
@ -849,6 +850,20 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
struct CommandMultiRect : public Command {
|
||||
RID texture;
|
||||
RID normal_map;
|
||||
Color modulate;
|
||||
Vector<Rect2> rects;
|
||||
Vector<Rect2> sources;
|
||||
uint8_t flags;
|
||||
|
||||
CommandMultiRect() {
|
||||
flags = 0;
|
||||
type = TYPE_MULTIRECT;
|
||||
}
|
||||
};
|
||||
|
||||
struct CommandNinePatch : public Command {
|
||||
Rect2 rect;
|
||||
Rect2 source;
|
||||
|
@ -1055,6 +1070,16 @@ public:
|
|||
r = crect->rect;
|
||||
|
||||
} break;
|
||||
case Item::Command::TYPE_MULTIRECT: {
|
||||
const Item::CommandMultiRect *mrect = static_cast<const Item::CommandMultiRect *>(c);
|
||||
int num_rects = mrect->rects.size();
|
||||
if (num_rects) {
|
||||
r = mrect->rects[0];
|
||||
for (int n = 1; n < num_rects; n++) {
|
||||
r = mrect->rects[n].merge(r);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case Item::Command::TYPE_NINEPATCH: {
|
||||
const Item::CommandNinePatch *style = static_cast<const Item::CommandNinePatch *>(c);
|
||||
r = style->rect;
|
||||
|
|
|
@ -670,6 +670,32 @@ void VisualServerCanvas::canvas_item_add_texture_rect(RID p_item, const Rect2 &p
|
|||
canvas_item->commands.push_back(rect);
|
||||
}
|
||||
|
||||
void VisualServerCanvas::canvas_item_add_texture_multirect_region(RID p_item, const Vector<Rect2> &p_rects, RID p_texture, const Vector<Rect2> &p_src_rects, const Color &p_modulate, uint32_t p_canvas_rect_flags, RID p_normal_map) {
|
||||
Item *canvas_item = canvas_item_owner.getornull(p_item);
|
||||
ERR_FAIL_COND(!canvas_item);
|
||||
ERR_FAIL_COND(p_rects.size() != p_src_rects.size());
|
||||
ERR_FAIL_COND(!p_rects.size());
|
||||
|
||||
Item::CommandMultiRect *rect = memnew(Item::CommandMultiRect);
|
||||
ERR_FAIL_COND(!rect);
|
||||
rect->modulate = p_modulate;
|
||||
rect->texture = p_texture;
|
||||
rect->normal_map = p_normal_map;
|
||||
|
||||
// Rects should have flips and transposes pre-applied, and the relevant
|
||||
// flags added to p_canvas_rect_flags.
|
||||
// A single Multirect should contain rects ALL of the same flag type.
|
||||
// The idea is to simplify the renderer as much as possible, and push the complexity
|
||||
// to the one off creation code.
|
||||
rect->flags = p_canvas_rect_flags | RasterizerCanvas::CANVAS_RECT_REGION;
|
||||
|
||||
rect->rects = p_rects;
|
||||
rect->sources = p_src_rects;
|
||||
|
||||
canvas_item->rect_dirty = true;
|
||||
canvas_item->commands.push_back(rect);
|
||||
}
|
||||
|
||||
void VisualServerCanvas::canvas_item_add_texture_rect_region(RID p_item, const Rect2 &p_rect, RID p_texture, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, RID p_normal_map, bool p_clip_uv) {
|
||||
Item *canvas_item = canvas_item_owner.getornull(p_item);
|
||||
ERR_FAIL_COND(!canvas_item);
|
||||
|
|
|
@ -194,6 +194,7 @@ public:
|
|||
void canvas_item_add_circle(RID p_item, const Point2 &p_pos, float p_radius, const Color &p_color);
|
||||
void canvas_item_add_texture_rect(RID p_item, const Rect2 &p_rect, RID p_texture, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, RID p_normal_map = RID());
|
||||
void canvas_item_add_texture_rect_region(RID p_item, const Rect2 &p_rect, RID p_texture, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, RID p_normal_map = RID(), bool p_clip_uv = false);
|
||||
void canvas_item_add_texture_multirect_region(RID p_item, const Vector<Rect2> &p_rects, RID p_texture, const Vector<Rect2> &p_src_rects, const Color &p_modulate = Color(1, 1, 1), uint32_t p_canvas_rect_flags = 0, RID p_normal_map = RID());
|
||||
void canvas_item_add_nine_patch(RID p_item, const Rect2 &p_rect, const Rect2 &p_source, RID p_texture, const Vector2 &p_topleft, const Vector2 &p_bottomright, VS::NinePatchAxisMode p_x_axis_mode = VS::NINE_PATCH_STRETCH, VS::NinePatchAxisMode p_y_axis_mode = VS::NINE_PATCH_STRETCH, bool p_draw_center = true, const Color &p_modulate = Color(1, 1, 1), RID p_normal_map = RID());
|
||||
void canvas_item_add_primitive(RID p_item, const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs, RID p_texture, float p_width = 1.0, RID p_normal_map = RID());
|
||||
void canvas_item_add_polygon(RID p_item, const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), RID p_texture = RID(), RID p_normal_map = RID(), bool p_antialiased = false);
|
||||
|
|
280
servers/visual/visual_server_canvas_helper.cpp
Normal file
280
servers/visual/visual_server_canvas_helper.cpp
Normal file
|
@ -0,0 +1,280 @@
|
|||
/**************************************************************************/
|
||||
/* visual_server_canvas_helper.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "visual_server_canvas_helper.h"
|
||||
|
||||
#include "servers/visual/rasterizer.h"
|
||||
#include "servers/visual_server.h"
|
||||
|
||||
LocalVector<MultiRect> VisualServerCanvasHelper::_tilemap_multirects;
|
||||
Mutex VisualServerCanvasHelper::_tilemap_mutex;
|
||||
|
||||
bool VisualServerCanvasHelper::_multirect_enabled = true;
|
||||
|
||||
MultiRect::MultiRect() {
|
||||
begin();
|
||||
}
|
||||
MultiRect::~MultiRect() {
|
||||
end();
|
||||
}
|
||||
|
||||
void MultiRect::begin() {
|
||||
DEV_CHECK_ONCE(!rects.size());
|
||||
rects.clear();
|
||||
sources.clear();
|
||||
|
||||
state.flags = 0;
|
||||
state_set = false;
|
||||
}
|
||||
|
||||
void MultiRect::add_rect(RID p_canvas_item, const Rect2 &p_rect, RID p_texture, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, RID p_normal_map, bool p_clip_uv) {
|
||||
bool new_common_data = true;
|
||||
|
||||
Rect2 rect = p_rect;
|
||||
Rect2 source = p_src_rect;
|
||||
|
||||
// To make the rendering code as efficient as possible,
|
||||
// a single MultiRect command should have identical flips and transpose etc.
|
||||
// If these change, it flushes the previous multirect and starts a new one.
|
||||
uint32_t flags = 0;
|
||||
|
||||
if (p_rect.size.x < 0) {
|
||||
flags |= RasterizerCanvas::CANVAS_RECT_FLIP_H;
|
||||
rect.size.x = -rect.size.x;
|
||||
}
|
||||
if (source.size.x < 0) {
|
||||
flags ^= RasterizerCanvas::CANVAS_RECT_FLIP_H;
|
||||
source.size.x = -source.size.x;
|
||||
}
|
||||
if (p_rect.size.y < 0) {
|
||||
flags |= RasterizerCanvas::CANVAS_RECT_FLIP_V;
|
||||
rect.size.y = -rect.size.y;
|
||||
}
|
||||
if (source.size.y < 0) {
|
||||
flags ^= RasterizerCanvas::CANVAS_RECT_FLIP_V;
|
||||
source.size.y = -source.size.y;
|
||||
}
|
||||
|
||||
if (p_transpose) {
|
||||
flags |= RasterizerCanvas::CANVAS_RECT_TRANSPOSE;
|
||||
SWAP(rect.size.x, rect.size.y);
|
||||
}
|
||||
|
||||
if (p_clip_uv) {
|
||||
flags |= RasterizerCanvas::CANVAS_RECT_CLIP_UV;
|
||||
}
|
||||
|
||||
VisualServerCanvasHelper::State s;
|
||||
s.item = p_canvas_item;
|
||||
s.texture = p_texture;
|
||||
s.modulate = p_modulate;
|
||||
s.normal_map = p_normal_map;
|
||||
s.flags = flags;
|
||||
|
||||
if (!is_empty()) {
|
||||
if ((state != s) ||
|
||||
(rects.size() >= MAX_RECTS)) {
|
||||
end();
|
||||
} else {
|
||||
new_common_data = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_common_data) {
|
||||
state = s;
|
||||
}
|
||||
|
||||
rects.push_back(rect);
|
||||
sources.push_back(source);
|
||||
}
|
||||
|
||||
void MultiRect::begin(const VisualServerCanvasHelper::State &p_state) {
|
||||
DEV_CHECK_ONCE(!rects.size());
|
||||
rects.clear();
|
||||
sources.clear();
|
||||
|
||||
state = p_state;
|
||||
state_set = true;
|
||||
}
|
||||
|
||||
uint32_t MultiRect::flags_from_rects(Rect2 &r_rect, Rect2 &r_source) {
|
||||
uint32_t flags = 0;
|
||||
|
||||
if (r_rect.size.x < 0) {
|
||||
flags |= RasterizerCanvas::CANVAS_RECT_FLIP_H;
|
||||
r_rect.size.x = -r_rect.size.x;
|
||||
}
|
||||
if (r_rect.size.y < 0) {
|
||||
flags |= RasterizerCanvas::CANVAS_RECT_FLIP_V;
|
||||
r_rect.size.y = -r_rect.size.y;
|
||||
}
|
||||
|
||||
if (r_source.size.x < 0) {
|
||||
flags ^= RasterizerCanvas::CANVAS_RECT_FLIP_H;
|
||||
r_source.size.x = -r_source.size.x;
|
||||
}
|
||||
if (r_source.size.y < 0) {
|
||||
flags ^= RasterizerCanvas::CANVAS_RECT_FLIP_V;
|
||||
r_source.size.y = -r_source.size.y;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
bool MultiRect::add_pre_flipped(const Rect2 &p_rect, const Rect2 &p_src_rect) {
|
||||
if (rects.is_full()) {
|
||||
return false;
|
||||
}
|
||||
*rects.request() = p_rect;
|
||||
*sources.request() = p_src_rect;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MultiRect::add(const Rect2 &p_rect, const Rect2 &p_src_rect, bool p_commit_on_flip_change) {
|
||||
if (rects.is_full()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rect2 rect = p_rect;
|
||||
Rect2 source = p_src_rect;
|
||||
|
||||
uint32_t flags = flags_from_rects(rect, source);
|
||||
|
||||
if (state_set) {
|
||||
// if we are changing these flips, we can no longer continue the same multirect
|
||||
if ((state.flags & (RasterizerCanvas::CANVAS_RECT_FLIP_H | RasterizerCanvas::CANVAS_RECT_FLIP_V)) != flags) {
|
||||
// different state requires a new multirect
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
state.flags |= flags;
|
||||
state_set = true;
|
||||
}
|
||||
|
||||
*rects.request() = rect;
|
||||
*sources.request() = source;
|
||||
return true;
|
||||
}
|
||||
|
||||
void MultiRect::end() {
|
||||
if (!is_empty()) {
|
||||
if (VisualServerCanvasHelper::_multirect_enabled) {
|
||||
VisualServer::get_singleton()->canvas_item_add_texture_multirect_region(state.item, rects, state.texture, sources, state.modulate, state.flags, state.normal_map);
|
||||
|
||||
} else {
|
||||
// legacy path
|
||||
bool transpose = state.flags & RasterizerCanvas::CANVAS_RECT_TRANSPOSE;
|
||||
bool clip_uv = state.flags & RasterizerCanvas::CANVAS_RECT_CLIP_UV;
|
||||
|
||||
for (uint32_t n = 0; n < rects.size(); n++) {
|
||||
VisualServer::get_singleton()->canvas_item_add_texture_rect_region(state.item, rects[n], state.texture, sources[n], state.modulate, transpose, state.normal_map, clip_uv);
|
||||
}
|
||||
}
|
||||
|
||||
rects.clear();
|
||||
sources.clear();
|
||||
}
|
||||
state_set = false;
|
||||
}
|
||||
|
||||
void VisualServerCanvasHelper::tilemap_begin() {
|
||||
if (_multirect_enabled) {
|
||||
_tilemap_mutex.lock();
|
||||
}
|
||||
}
|
||||
|
||||
void VisualServerCanvasHelper::tilemap_add_rect(RID p_canvas_item, const Rect2 &p_rect, RID p_texture, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, RID p_normal_map, bool p_clip_uv) {
|
||||
if (!_multirect_enabled) {
|
||||
VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, p_rect, p_texture, p_src_rect, p_modulate, p_transpose, p_normal_map, p_clip_uv);
|
||||
return;
|
||||
}
|
||||
|
||||
Rect2 rect = p_rect;
|
||||
Rect2 source = p_src_rect;
|
||||
|
||||
// To make the rendering code as efficient as possible,
|
||||
// a single MultiRect command should have identical flips and transpose etc.
|
||||
// If these change, it flushes the previous multirect and starts a new one.
|
||||
uint32_t flags = MultiRect::flags_from_rects(rect, source);
|
||||
|
||||
if (p_transpose) {
|
||||
flags |= RasterizerCanvas::CANVAS_RECT_TRANSPOSE;
|
||||
SWAP(rect.size.x, rect.size.y);
|
||||
}
|
||||
|
||||
if (p_clip_uv) {
|
||||
flags |= RasterizerCanvas::CANVAS_RECT_CLIP_UV;
|
||||
}
|
||||
|
||||
State state;
|
||||
state.item = p_canvas_item;
|
||||
state.texture = p_texture;
|
||||
state.modulate = p_modulate;
|
||||
state.normal_map = p_normal_map;
|
||||
state.flags = flags;
|
||||
|
||||
// attempt to add to existing multirect
|
||||
for (int n = _tilemap_multirects.size() - 1; n >= 0; n--) {
|
||||
MultiRect &mr = _tilemap_multirects[n];
|
||||
|
||||
// matches state?
|
||||
if (mr.state == state) {
|
||||
// add .. this may fail if the multirect is full
|
||||
if (mr.add_pre_flipped(rect, source)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// disallow if we overlap a multirect
|
||||
if (mr.overlaps(rect)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// create new multirect
|
||||
_tilemap_multirects.resize(_tilemap_multirects.size() + 1);
|
||||
MultiRect &mr = _tilemap_multirects[_tilemap_multirects.size() - 1];
|
||||
mr.begin(state);
|
||||
mr.add_pre_flipped(rect, source);
|
||||
}
|
||||
|
||||
void VisualServerCanvasHelper::tilemap_end() {
|
||||
if (!_multirect_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t n = 0; n < _tilemap_multirects.size(); n++) {
|
||||
_tilemap_multirects[n].end();
|
||||
}
|
||||
|
||||
_tilemap_multirects.clear();
|
||||
_tilemap_mutex.unlock();
|
||||
}
|
114
servers/visual/visual_server_canvas_helper.h
Normal file
114
servers/visual/visual_server_canvas_helper.h
Normal file
|
@ -0,0 +1,114 @@
|
|||
/**************************************************************************/
|
||||
/* visual_server_canvas_helper.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 VISUAL_SERVER_CANVAS_HELPER_H
|
||||
#define VISUAL_SERVER_CANVAS_HELPER_H
|
||||
|
||||
#include "core/color.h"
|
||||
#include "core/fixed_array.h"
|
||||
#include "core/local_vector.h"
|
||||
#include "core/math/rect2.h"
|
||||
#include "core/rid.h"
|
||||
|
||||
class MultiRect;
|
||||
|
||||
class VisualServerCanvasHelper {
|
||||
public:
|
||||
struct State {
|
||||
RID item;
|
||||
RID texture;
|
||||
Color modulate;
|
||||
RID normal_map;
|
||||
uint32_t flags = 0;
|
||||
|
||||
bool operator==(const State &p_state) const {
|
||||
return ((item == p_state.item) &&
|
||||
(texture == p_state.texture) &&
|
||||
(modulate == p_state.modulate) &&
|
||||
(normal_map == p_state.normal_map) &&
|
||||
(flags == p_state.flags));
|
||||
}
|
||||
bool operator!=(const State &p_state) const { return !(*this == p_state); }
|
||||
};
|
||||
|
||||
private:
|
||||
// There is a single mutex for tilemaps, only one quadrant can be adding
|
||||
// at a time.
|
||||
static LocalVector<MultiRect> _tilemap_multirects;
|
||||
static Mutex _tilemap_mutex;
|
||||
|
||||
public:
|
||||
static void tilemap_begin();
|
||||
static void tilemap_add_rect(RID p_canvas_item, const Rect2 &p_rect, RID p_texture, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, RID p_normal_map = RID(), bool p_clip_uv = false);
|
||||
static void tilemap_end();
|
||||
|
||||
static bool _multirect_enabled;
|
||||
};
|
||||
|
||||
class MultiRect {
|
||||
friend class VisualServerCanvasHelper;
|
||||
|
||||
public:
|
||||
enum { MAX_RECTS = 2048 };
|
||||
|
||||
private:
|
||||
VisualServerCanvasHelper::State state;
|
||||
bool state_set = false;
|
||||
FixedArray<Rect2, MAX_RECTS, true> rects;
|
||||
FixedArray<Rect2, MAX_RECTS, true> sources;
|
||||
|
||||
static uint32_t flags_from_rects(Rect2 &r_rect, Rect2 &r_source);
|
||||
bool overlaps(const Rect2 &p_rect) const {
|
||||
for (uint32_t n = 0; n < rects.size(); n++) {
|
||||
if (rects[n].intersects(p_rect)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool add_pre_flipped(const Rect2 &p_rect, const Rect2 &p_src_rect);
|
||||
|
||||
public:
|
||||
// Simple API
|
||||
void begin();
|
||||
void add_rect(RID p_canvas_item, const Rect2 &p_rect, RID p_texture, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, RID p_normal_map = RID(), bool p_clip_uv = false);
|
||||
|
||||
// Efficient API
|
||||
void begin(const VisualServerCanvasHelper::State &p_state);
|
||||
bool add(const Rect2 &p_rect, const Rect2 &p_src_rect, bool p_commit_on_flip_change = true);
|
||||
bool is_empty() const { return rects.is_empty(); }
|
||||
bool is_full() const { return rects.is_full(); }
|
||||
void end();
|
||||
|
||||
MultiRect();
|
||||
~MultiRect();
|
||||
};
|
||||
|
||||
#endif // VISUAL_SERVER_CANVAS_HELPER_H
|
|
@ -704,6 +704,7 @@ public:
|
|||
BIND4(canvas_item_add_circle, RID, const Point2 &, float, const Color &)
|
||||
BIND7(canvas_item_add_texture_rect, RID, const Rect2 &, RID, bool, const Color &, bool, RID)
|
||||
BIND8(canvas_item_add_texture_rect_region, RID, const Rect2 &, RID, const Rect2 &, const Color &, bool, RID, bool)
|
||||
BIND7(canvas_item_add_texture_multirect_region, RID, const Vector<Rect2> &, RID, const Vector<Rect2> &, const Color &, uint32_t, RID)
|
||||
BIND11(canvas_item_add_nine_patch, RID, const Rect2 &, const Rect2 &, RID, const Vector2 &, const Vector2 &, NinePatchAxisMode, NinePatchAxisMode, bool, const Color &, RID)
|
||||
BIND7(canvas_item_add_primitive, RID, const Vector<Point2> &, const Vector<Color> &, const Vector<Point2> &, RID, float, RID)
|
||||
BIND7(canvas_item_add_polygon, RID, const Vector<Point2> &, const Vector<Color> &, const Vector<Point2> &, RID, RID, bool)
|
||||
|
|
|
@ -605,6 +605,7 @@ public:
|
|||
FUNC4(canvas_item_add_circle, RID, const Point2 &, float, const Color &)
|
||||
FUNC7(canvas_item_add_texture_rect, RID, const Rect2 &, RID, bool, const Color &, bool, RID)
|
||||
FUNC8(canvas_item_add_texture_rect_region, RID, const Rect2 &, RID, const Rect2 &, const Color &, bool, RID, bool)
|
||||
FUNC7(canvas_item_add_texture_multirect_region, RID, const Vector<Rect2> &, RID, const Vector<Rect2> &, const Color &, uint32_t, RID)
|
||||
FUNC11(canvas_item_add_nine_patch, RID, const Rect2 &, const Rect2 &, RID, const Vector2 &, const Vector2 &, NinePatchAxisMode, NinePatchAxisMode, bool, const Color &, RID)
|
||||
FUNC7(canvas_item_add_primitive, RID, const Vector<Point2> &, const Vector<Color> &, const Vector<Point2> &, RID, float, RID)
|
||||
FUNC7(canvas_item_add_polygon, RID, const Vector<Point2> &, const Vector<Color> &, const Vector<Point2> &, RID, RID, bool)
|
||||
|
|
|
@ -2704,6 +2704,7 @@ VisualServer::VisualServer() {
|
|||
GLOBAL_DEF("rendering/batching/options/use_batching", true);
|
||||
GLOBAL_DEF_RST("rendering/batching/options/use_batching_in_editor", true);
|
||||
GLOBAL_DEF("rendering/batching/options/single_rect_fallback", false);
|
||||
GLOBAL_DEF("rendering/batching/options/use_multirect", true);
|
||||
GLOBAL_DEF("rendering/batching/parameters/max_join_item_commands", 16);
|
||||
GLOBAL_DEF("rendering/batching/parameters/colored_vertex_format_threshold", 0.25f);
|
||||
GLOBAL_DEF("rendering/batching/lights/scissor_area_threshold", 1.0f);
|
||||
|
@ -2720,7 +2721,7 @@ VisualServer::VisualServer() {
|
|||
|
||||
ProjectSettings::get_singleton()->set_custom_property_info("rendering/batching/parameters/max_join_item_commands", PropertyInfo(Variant::INT, "rendering/batching/parameters/max_join_item_commands", PROPERTY_HINT_RANGE, "0,65535"));
|
||||
ProjectSettings::get_singleton()->set_custom_property_info("rendering/batching/parameters/colored_vertex_format_threshold", PropertyInfo(Variant::REAL, "rendering/batching/parameters/colored_vertex_format_threshold", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"));
|
||||
ProjectSettings::get_singleton()->set_custom_property_info("rendering/batching/parameters/batch_buffer_size", PropertyInfo(Variant::INT, "rendering/batching/parameters/batch_buffer_size", PROPERTY_HINT_RANGE, "1024,65535,1024"));
|
||||
ProjectSettings::get_singleton()->set_custom_property_info("rendering/batching/parameters/batch_buffer_size", PropertyInfo(Variant::INT, "rendering/batching/parameters/batch_buffer_size", PROPERTY_HINT_RANGE, "8192,65536,1024"));
|
||||
ProjectSettings::get_singleton()->set_custom_property_info("rendering/batching/lights/scissor_area_threshold", PropertyInfo(Variant::REAL, "rendering/batching/lights/scissor_area_threshold", PROPERTY_HINT_RANGE, "0.0,1.0"));
|
||||
ProjectSettings::get_singleton()->set_custom_property_info("rendering/batching/lights/max_join_items", PropertyInfo(Variant::INT, "rendering/batching/lights/max_join_items", PROPERTY_HINT_RANGE, "0,512"));
|
||||
ProjectSettings::get_singleton()->set_custom_property_info("rendering/batching/parameters/item_reordering_lookahead", PropertyInfo(Variant::INT, "rendering/batching/parameters/item_reordering_lookahead", PROPERTY_HINT_RANGE, "0,256"));
|
||||
|
|
|
@ -1037,6 +1037,7 @@ public:
|
|||
virtual void canvas_item_add_circle(RID p_item, const Point2 &p_pos, float p_radius, const Color &p_color) = 0;
|
||||
virtual void canvas_item_add_texture_rect(RID p_item, const Rect2 &p_rect, RID p_texture, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, RID p_normal_map = RID()) = 0;
|
||||
virtual void canvas_item_add_texture_rect_region(RID p_item, const Rect2 &p_rect, RID p_texture, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, RID p_normal_map = RID(), bool p_clip_uv = false) = 0;
|
||||
virtual void canvas_item_add_texture_multirect_region(RID p_item, const Vector<Rect2> &p_rects, RID p_texture, const Vector<Rect2> &p_src_rects, const Color &p_modulate = Color(1, 1, 1), uint32_t p_canvas_rect_flags = 0, RID p_normal_map = RID()) = 0;
|
||||
virtual void canvas_item_add_nine_patch(RID p_item, const Rect2 &p_rect, const Rect2 &p_source, RID p_texture, const Vector2 &p_topleft, const Vector2 &p_bottomright, NinePatchAxisMode p_x_axis_mode = NINE_PATCH_STRETCH, NinePatchAxisMode p_y_axis_mode = NINE_PATCH_STRETCH, bool p_draw_center = true, const Color &p_modulate = Color(1, 1, 1), RID p_normal_map = RID()) = 0;
|
||||
virtual void canvas_item_add_primitive(RID p_item, const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs, RID p_texture, float p_width = 1.0, RID p_normal_map = RID()) = 0;
|
||||
virtual void canvas_item_add_polygon(RID p_item, const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), RID p_texture = RID(), RID p_normal_map = RID(), bool p_antialiased = false) = 0;
|
||||
|
|
Loading…
Reference in a new issue