Fewer draws and GL calls

Decals and light volumes batched, web index and vertex array caches, no FULL_ES3. Prisoner, 15 players: 115 to 172 FPS (A/B).
This commit is contained in:
MrBruh committed 2026-10-11 22:28:34 +13:00
1 parent 6399ed9d4a
commit a3277d94dc
15 files changed
+893 -63

No files matched your search

+1 -1
View File
@@ -27,7 +27,7 @@ to that many frames late.
#define WATCH_HASH_PAGE_SIZE 0x1000u
#define WATCH_HASH_STABLE_CHECKS 30u
#define WATCH_HASH_STABLE_FRAMES 4u
#define WATCH_HASH_STABLE_FRAMES 60u
struct watch_hash
{
+2 -1
View File
@@ -620,7 +620,8 @@ plays the variants in turn, the next once a game is over, as the host's
button on the scores does). `debug.network_test_public` lists the host's
game in the server browser, and `browse` joins the one listed there with the
most players (not co-op, if it can), as the server browser does
(port/web/tests/internet.mjs plays the native build and browsers so). `debug.network_latency` and
(port/web/tests/internet.mjs plays the native build and browsers so).
`debug.network_latency` and
`debug.network_loss` hold back what a machine receives and drop some of its
datagrams, to test as over the internet. `debug.network_corrupt` damages
that share of the datagrams a machine receives at random (bytes changed,
+6
View File
@@ -1099,6 +1099,12 @@ Other changes:
| `render/render_objects.c`, `objects/objects.c` | In first person, your own body is drawn from the waist down with the pose `first_person_legs.c` makes (`display.first_person_legs`); the waist it found and the mesh `object_mesh.c` read are forgotten on a new map. |
| `objects/object_lights.c` | Glows the port lights itself (`shield_glow.c`): dynamic lights attached to an object's node, colored and sized each frame by their maker, drawn with a light definition the map has. |
| `render/render_objects.c`, `interface/first_person_weapons.c`, `rasterizer/xbox/rasterizer_xbox_plasma_energy.c` | A local player's shield plasma is drawn in the chosen armor color (`display.shield_color`, `shield_color.c`), on the body and on the first-person arms. |
| `rasterizer/xbox/rasterizer_xbox_text.c`, `rasterizer/rasterizer_text.c` | The characters of a string are one draw, not a draw each (and a second for the drop shadow). The batch is drawn early before a character is cached and before the high-res glyph atlas starts over (`src/text_hires.c`). |
| `rasterizer/xbox/rasterizer_xbox_decals.c` | Decals drawn one after another with the same blend function and bitmap are one draw, with each decal's color in its vertices, not a draw each: a long fight leaves hundreds in view. Within a run of decals whose blend functions commute (multiply, add, min, max, reverse subtract), they are drawn by bitmap, so more of them share a draw. |
| `rasterizer/xbox/rasterizer_xbox_widgets.c`, `objects/widgets/light_volumes.c` | A light volume's sprites (a plasma bolt's glow, a couple of hundred) are one draw. |
| `structures/structure_render.c` | The structure's visible surfaces are put in order by a radix sort, not `qsort_4byte`: the same order. |
| `tag_files/tag_groups.h` | `TAG_BLOCK_GET_ELEMENT` returns an element in the block without a call, and calls `tag_block_get_element_with_size` for anything else. |
| `input/input_xbox.c`, `physics/collision_usage.c` | The clock is read only for a button held, and for collision tests only when their times are shown: a read of the clock is slow in a browser. |
The x86 inline assembly of the game is replaced by C. Thus the compiler
can optimize that code for each processor:
+493 -36
View File
@@ -764,9 +764,33 @@ static struct
float attribute_values[XGPU_VERTEX_ATTRIBUTE_COUNT][4];
} gl_state;
#ifdef HALO_WEB
static void vertex_array_cache_invalidate(void);
#endif
void xgpu_gl_state_invalidate(void)
{
memset(&gl_state, 0xff, sizeof(gl_state));
#ifdef HALO_WEB
vertex_array_cache_invalidate();
#endif
}
/* (a unit the shadows still said had it would never be bound to a new
texture GL gives its name to; and a stage no draw samples keeps what was
bound to it: bind_textures) */
void xgpu_gl_texture_deleted(unsigned int texture)
{
int unit, slot;
for (unit = 0; unit < D3DTSS_MAXSTAGES; unit++)
{
for (slot = 0; slot < 3; slot++)
{
if (gl_state.textures[unit][slot] == texture)
gl_state.textures[unit][slot] = (GLuint)-1;
}
}
}
static void state_enable(unsigned char *shadow, GLenum capability, BOOL enabled)
@@ -972,6 +996,29 @@ static void layout_attribute(struct vertex_layout *layout, unsigned long index,
}
#endif
/* the value a disabled attribute reads (value, or the integer zero), which
is the context's, not the vertex array's */
static void state_attribute_constant(GLuint index, const float *value)
{
unsigned char kind = value ? 0 : 1;
if (gl_state.attribute_value_kind[index] == kind &&
(!value || !memcmp(gl_state.attribute_values[index], value, sizeof(gl_state.attribute_values[index]))))
{
return;
}
gl_state.attribute_value_kind[index] = kind;
if (value)
{
memcpy(gl_state.attribute_values[index], value, sizeof(gl_state.attribute_values[index]));
glVertexAttrib4fv(index, value);
}
else
{
glVertexAttribI4ui(index, 0, 0, 0, 0);
}
}
/* disables the attribute, which then reads value, or the integer zero */
static void state_attribute_value(GLuint index, const float *value)
{
@@ -3030,6 +3077,7 @@ static void render_target_delete(struct render_target_entry *entry)
}
}
}
xgpu_gl_texture_deleted(entry->target.texture);
glDeleteTextures(1, &entry->target.texture);
free(entry);
}
@@ -3286,8 +3334,13 @@ static void bind_textures(struct nv2a_pixel_shader_key *key, float texture_scale
}
}
#ifdef HALO_GLES
/* (a stage the pixel shader does not sample keeps what it had: unbinding
it, to bind it again at the next draw that has it, is two calls) */
for (stage = 0; stage < D3DTSS_MAXSTAGES; stage++)
state_texture(stage, gl_targets[stage], gl_textures[stage]);
{
if (gl_textures[stage] || key->sampler_type[stage] != _xgpu_sampler_none)
state_texture(stage, gl_targets[stage], gl_textures[stage]);
}
#else
{
/* the units whose texture changes, bound in one call (GL 4.4's
@@ -4283,7 +4336,7 @@ static void stream_reserve(unsigned long size)
static unsigned long stream_upload(const void *data, unsigned long size);
#ifdef HALO_WEB
#ifdef HALO_GLES
/* stream_upload, at a multiple of alignment into the buffer */
static unsigned long stream_upload_aligned(const void *data, unsigned long size, unsigned long alignment)
{
@@ -4429,6 +4482,198 @@ static BOOL stream_has_colors(const struct vertex_shader_object *declaration, un
}
#endif
#ifdef HALO_WEB
/* Each draw's attributes would be pointed again (a call into the browser
for each) whenever it reads its streams from another place than the draw
before it, which is nearly always. The draws whose streams all come from
the mirror read the same places frame after frame (a model's part, the
structure's), so each such set of places has a vertex array of its own,
made the first time, and the draw binds it: one call. Other draws use the
device's own vertex array, pointed as before. A vertex array holds the
element array buffer's binding too, kept here for each. Past
VERTEX_ARRAY_CACHE_MAXIMUM, they are all deleted and made again. */
#define VERTEX_ARRAY_CACHE_SLOTS 8192
#define VERTEX_ARRAY_CACHE_MAXIMUM 4096
struct vertex_array_cache_entry
{
const struct vertex_shader_object *declaration;
unsigned long streams;
GLuint buffers[16];
unsigned long offsets[16];
unsigned long strides[16];
GLuint vertex_array;
/* its element array buffer binding, when known: kept in the epoch */
GLuint element_array_buffer;
unsigned long element_epoch;
};
static struct
{
struct vertex_array_cache_entry *slots[VERTEX_ARRAY_CACHE_SLOTS];
unsigned long count;
/* the vertex array bound: an entry, or NULL for the device's own */
struct vertex_array_cache_entry *bound;
BOOL bound_known;
GLuint device_element_array_buffer;
/* the entries' element array buffer bindings are known only from the
latest invalidation on (xgpu_gl_state_invalidate) */
unsigned long element_epoch;
} vertex_array_cache;
static void vertex_array_cache_invalidate(void)
{
vertex_array_cache.bound_known = FALSE;
vertex_array_cache.device_element_array_buffer = (GLuint)-1;
vertex_array_cache.element_epoch++;
}
/* binds the entry's vertex array (NULL: the device's own) */
static void vertex_array_cache_bind(struct vertex_array_cache_entry *entry)
{
if (vertex_array_cache.bound_known && vertex_array_cache.bound == entry)
return;
if (vertex_array_cache.bound_known)
{
if (vertex_array_cache.bound)
{
vertex_array_cache.bound->element_array_buffer = gl_state.element_array_buffer;
vertex_array_cache.bound->element_epoch = vertex_array_cache.element_epoch;
}
else
vertex_array_cache.device_element_array_buffer = gl_state.element_array_buffer;
}
else
{
/* (what was bound to the device's own is not known) */
vertex_array_cache.device_element_array_buffer = (GLuint)-1;
}
glBindVertexArray(entry ? entry->vertex_array : device.vertex_array);
vertex_array_cache.bound = entry;
vertex_array_cache.bound_known = TRUE;
gl_state.element_array_buffer = !entry ? vertex_array_cache.device_element_array_buffer :
entry->element_epoch == vertex_array_cache.element_epoch ? entry->element_array_buffer : (GLuint)-1;
}
static void vertex_array_cache_clear(void)
{
unsigned long slot;
vertex_array_cache_bind(NULL);
for (slot = 0; slot < VERTEX_ARRAY_CACHE_SLOTS; slot++)
{
struct vertex_array_cache_entry *entry = vertex_array_cache.slots[slot];
if (entry)
{
glDeleteVertexArrays(1, &entry->vertex_array);
free(entry);
vertex_array_cache.slots[slot] = NULL;
}
}
vertex_array_cache.count = 0;
}
/* binds the vertex array of the declaration reading its streams (streams,
a bit each) from buffers at offsets; FALSE if there is none and none can be
made */
static BOOL vertex_array_cache_use(const struct vertex_shader_object *declaration, unsigned long streams,
const GLuint *buffers, const unsigned long *offsets)
{
unsigned long hash = 2166136261UL ^ (unsigned long)declaration, stream, slot, probe, index;
struct vertex_array_cache_entry *entry = NULL;
for (stream = 0; stream < 16; stream++)
{
if (streams & (1UL << stream))
{
hash = (hash ^ buffers[stream]) * 16777619UL;
hash = (hash ^ offsets[stream]) * 16777619UL;
hash = (hash ^ device.streams[stream].stride) * 16777619UL;
}
}
hash = (hash ^ streams) * 16777619UL;
slot = hash % VERTEX_ARRAY_CACHE_SLOTS;
for (probe = 0; probe < VERTEX_ARRAY_CACHE_SLOTS; probe++, slot = (slot + 1) % VERTEX_ARRAY_CACHE_SLOTS)
{
struct vertex_array_cache_entry *candidate = vertex_array_cache.slots[slot];
if (!candidate)
break;
if (candidate->declaration != declaration || candidate->streams != streams)
continue;
for (stream = 0; stream < 16; stream++)
{
if ((streams & (1UL << stream)) && (candidate->buffers[stream] != buffers[stream] ||
candidate->offsets[stream] != offsets[stream] || candidate->strides[stream] != device.streams[stream].stride))
{
break;
}
}
if (stream == 16)
{
entry = candidate;
break;
}
}
if (!entry)
{
if (vertex_array_cache.count >= VERTEX_ARRAY_CACHE_MAXIMUM)
{
vertex_array_cache_clear();
return vertex_array_cache_use(declaration, streams, buffers, offsets);
}
entry = calloc(1, sizeof(*entry));
if (!entry)
return FALSE;
entry->declaration = declaration;
entry->streams = streams;
for (stream = 0; stream < 16; stream++)
{
if (streams & (1UL << stream))
{
entry->buffers[stream] = buffers[stream];
entry->offsets[stream] = offsets[stream];
entry->strides[stream] = device.streams[stream].stride;
}
}
glGenVertexArrays(1, &entry->vertex_array);
entry->element_array_buffer = 0;
entry->element_epoch = vertex_array_cache.element_epoch;
vertex_array_cache.slots[slot] = entry;
vertex_array_cache.count++;
vertex_array_cache_bind(entry);
for (index = 0; index < declaration->element_count; index++)
{
const struct vertex_element *element = &declaration->elements[index];
GLint size;
GLenum type;
GLboolean normalized;
stream = element->stream;
if (!(streams & (1UL << stream)) || element->type == D3DVSDT_NONE)
continue;
state_array_buffer(buffers[stream]);
glEnableVertexAttribArray(element->reg);
if (element->type == D3DVSDT_NORMPACKED3)
{
glVertexAttribIPointer(element->reg, 1, GL_UNSIGNED_INT, (GLsizei)device.streams[stream].stride,
(const void *)(offsets[stream] + element->offset));
}
else
{
attribute_format(element, &size, &type, &normalized);
glVertexAttribPointer(element->reg, size, type, normalized, (GLsizei)device.streams[stream].stride,
(const void *)(offsets[stream] + element->offset));
}
}
return TRUE;
}
vertex_array_cache_bind(entry);
return TRUE;
}
#endif
static void setup_streams(unsigned long first, unsigned long count)
{
struct vertex_shader_object *declaration = device.vertex_shader;
@@ -4467,6 +4712,36 @@ static void setup_streams(unsigned long first, unsigned long count)
stream_buffers[stream] = 0;
total += (bytes + 15) & ~15UL;
}
#ifdef HALO_WEB
{
/* every stream from the mirror: a vertex array of their own
(vertex_array_cache) */
unsigned long streams = 0;
for (index = 0; index < 16; index++)
{
if (placed[index])
streams |= 1UL << index;
}
if (!total && streams && vertex_array_cache_use(declaration, streams, stream_buffers, stream_offsets))
{
for (index = 0; index < declaration->element_count; index++)
{
const struct vertex_element *element = &declaration->elements[index];
if (device.streams[element->stream].data && element->type != D3DVSDT_NONE)
enabled[element->reg] = TRUE;
}
for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++)
{
if (!enabled[index])
state_attribute_constant(index, declaration->packed_mask & (1UL << index) ? NULL : device.attributes[index]);
}
return;
}
vertex_array_cache_bind(NULL);
}
#endif
stream_reserve(total);
for (index = 0; index < declaration->element_count; index++)
{
@@ -4618,6 +4893,126 @@ void WINAPI D3DDevice_DrawVertices(D3DPRIMITIVETYPE primitive_type, UINT start_v
gl_check_errors("draw");
}
#ifdef HALO_WEB
/* WebGL has no base vertex, so the indices of a draw are rebased (less their
smallest) and uploaded with it, a call into the browser each and a check of
their range by it; and the game's index buffers are ordinary memory, which
the mirror does not hold. An index array drawn twice the same is kept
instead, rebased, in a buffer of its own: a draw of it compares its indices
with a copy and draws from the buffer. Arrays that change (the structure's
visible triangles, made each frame) are never the same twice, and are
uploaded as before. When the buffer is full, everything kept is forgotten. */
#define INDEX_CACHE_BUFFER_SIZE (8 * 1024 * 1024)
#define INDEX_CACHE_SLOTS 8192
#define INDEX_CACHE_PROBES 4
struct index_cache_entry
{
const WORD *address;
unsigned long count;
/* the indices as last drawn from address, and room for them */
WORD *copy;
unsigned long capacity;
WORD minimum, maximum;
/* in the buffer (when kept is the epoch), or not yet: seen once */
unsigned long offset;
unsigned long kept;
};
static struct
{
GLuint buffer;
unsigned long used;
/* entries kept in an earlier epoch are not in the buffer */
unsigned long epoch;
struct index_cache_entry entries[INDEX_CACHE_SLOTS];
} index_cache = { 0, 0, 1 };
/* the buffer offset of the rebased indices kept for the array, with their
extent; FALSE if it is not kept (yet) */
static BOOL index_cache_get(const WORD *indices, unsigned long count, unsigned long *minimum, unsigned long *maximum,
unsigned long *offset)
{
unsigned long slot = (((unsigned long)indices >> 1) ^ (count * 2654435761UL)) % INDEX_CACHE_SLOTS;
unsigned long probe, size = count * sizeof(WORD), index;
struct index_cache_entry *entry = NULL, *oldest = NULL;
WORD *rebased;
for (probe = 0; probe < INDEX_CACHE_PROBES; probe++)
{
struct index_cache_entry *candidate = &index_cache.entries[(slot + probe) % INDEX_CACHE_SLOTS];
if (candidate->address == indices && candidate->count == count)
{
entry = candidate;
break;
}
if (!oldest || candidate->kept < oldest->kept)
oldest = candidate;
}
if (entry && entry->copy && !memcmp(entry->copy, indices, size))
{
if (entry->kept == index_cache.epoch)
{
*minimum = entry->minimum;
*maximum = entry->maximum;
*offset = entry->offset;
return TRUE;
}
/* the second time the same: kept from now on */
if (index_cache.used + ((size + 15) & ~15UL) > INDEX_CACHE_BUFFER_SIZE)
{
index_cache.epoch++;
index_cache.used = 0;
}
if (!index_cache.buffer)
{
glGenBuffers(1, &index_cache.buffer);
state_element_array_buffer(index_cache.buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_CACHE_BUFFER_SIZE, NULL, GL_STATIC_DRAW);
}
rebased = malloc(size + 2);
if (!rebased)
return FALSE;
for (index = 0; index < count; index++)
rebased[index] = (WORD)(indices[index] - entry->minimum);
state_element_array_buffer(index_cache.buffer);
host_gl_buffer_write(GL_ELEMENT_ARRAY_BUFFER, (unsigned int)index_cache.used, (unsigned int)size, rebased);
free(rebased);
entry->offset = index_cache.used;
entry->kept = index_cache.epoch;
index_cache.used += (size + 15) & ~15UL;
*minimum = entry->minimum;
*maximum = entry->maximum;
*offset = entry->offset;
return TRUE;
}
/* seen for the first time, or changed: remembered */
if (!entry)
entry = oldest;
if (entry->capacity < count)
{
WORD *grown = realloc(entry->copy, size);
if (!grown)
{
entry->address = NULL;
return FALSE;
}
entry->copy = grown;
entry->capacity = count;
}
memcpy(entry->copy, indices, size);
entry->address = indices;
entry->count = count;
entry->kept = 0;
index_extent(indices, count, 0, FALSE, minimum, maximum);
entry->minimum = (WORD)*minimum;
entry->maximum = (WORD)*maximum;
return FALSE;
}
#endif
void WINAPI D3DDevice_DrawIndexedVertices(D3DPRIMITIVETYPE primitive_type, UINT vertex_count, CONST WORD *index_data)
{
unsigned long minimum, maximum, index, count, generation = 0, index_offset = 0;
@@ -4634,6 +5029,17 @@ void WINAPI D3DDevice_DrawIndexedVertices(D3DPRIMITIVETYPE primitive_type, UINT
xgpu_capabilities.base_vertex &&
#endif
mirror_range((unsigned long)index_data, vertex_count * sizeof(WORD), &index_buffer, &index_offset, &generation);
#ifdef HALO_WEB
if (!mirrored && primitive_type != D3DPT_QUADLIST &&
index_cache_get(index_data, vertex_count, &minimum, &maximum, &index_offset))
{
trace_draw("indexed", primitive_type, vertex_count, NULL);
setup_streams(device.base_vertex_index + minimum, maximum - minimum + 1);
state_element_array_buffer(index_cache.buffer);
glDrawElements(primitive_mode(primitive_type), (GLsizei)vertex_count, GL_UNSIGNED_SHORT, (const void *)index_offset);
return;
}
#endif
index_extent(index_data, vertex_count, generation, mirrored, &minimum, &maximum);
trace_draw("indexed", primitive_type, vertex_count, NULL);
/* (the streams from the base vertex on: index i is vertex base + i) */
@@ -4717,15 +5123,18 @@ void WINAPI D3DDevice_End(void)
return;
trace_draw("immediate", type, count, device.immediate_vertices);
#ifdef HALO_WEB
vertex_array_cache_bind(NULL);
#endif
{
/* Only the registers written since Begin are uploaded, one after
another in each vertex; the others are constants. The vertices start
a whole number of vertices into the streaming buffer and are drawn
from there (first_vertex), so that draws of the same registers keep
the attributes' pointers: each draw of text is a character, and each
pointer set is a call into the browser. All sixteen would be 256
bytes, more than WebGL's largest stride (255): each attribute then
goes in an array of its own. */
another in each vertex; the others are constants (most draws write 3
of the 16, and a vertex of all of them is 256 bytes). On ES the
vertices start a whole number of vertices into the streaming buffer
and are drawn from there (first_vertex), so that draws of the same
registers keep the attributes' pointers: each pointer set is a call
into the driver, or the browser. All sixteen would be 256 bytes, more
than WebGL's largest stride (255): each attribute then goes in an
array of its own there. */
static float *packed;
static unsigned long packed_capacity;
unsigned long attribute_size = 4 * sizeof(float);
@@ -4759,6 +5168,7 @@ void WINAPI D3DDevice_End(void)
attribute_size);
}
}
#ifdef HALO_GLES
offset = stream_upload_aligned(packed, count * vertex_size, vertex_size);
first_vertex = offset / vertex_size;
for (slot = 0; slot < register_count; slot++)
@@ -4766,6 +5176,39 @@ void WINAPI D3DDevice_End(void)
state_attribute_stream(registers[slot], 0, device.stream_buffer, 4, GL_FLOAT, GL_FALSE, FALSE,
(GLsizei)vertex_size, 0, slot * attribute_size);
}
#else
offset = stream_upload(packed, count * vertex_size);
{
/* (the vertex array of the last few sets of registers) */
static struct
{
unsigned long written;
struct vertex_array_entry *entry;
} arrays[8];
static unsigned long next_array;
struct vertex_array_entry *entry = NULL;
for (slot = 0; slot < 8; slot++)
{
if (arrays[slot].entry && arrays[slot].written == device.immediate_written)
entry = arrays[slot].entry;
}
if (!entry)
{
struct vertex_layout layout;
memset(&layout, 0, sizeof(layout));
for (slot = 0; slot < register_count; slot++)
layout_attribute(&layout, registers[slot], 0, 4, GL_FLOAT, GL_FALSE, FALSE, slot * attribute_size);
entry = vertex_array_get(&layout);
arrays[next_array % 8].written = device.immediate_written;
arrays[next_array % 8].entry = entry;
next_array++;
}
state_vertex_array(entry);
state_vertex_buffer(0, device.stream_buffer, offset, (GLsizei)vertex_size);
}
#endif
for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++)
{
if (!(device.immediate_written & (1UL << index)))
@@ -4774,6 +5217,7 @@ void WINAPI D3DDevice_End(void)
}
else
{
#ifdef HALO_WEB
for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++)
{
for (vertex = 0; vertex < count; vertex++)
@@ -4788,34 +5232,34 @@ void WINAPI D3DDevice_End(void)
state_attribute_stream(index, 0, device.stream_buffer, 4, GL_FLOAT, GL_FALSE, FALSE,
(GLsizei)attribute_size, offset, index * count * attribute_size);
}
}
}
#elif defined(HALO_GLES)
offset = stream_upload(device.immediate_vertices, count * stride);
for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++)
{
state_attribute_stream(index, 0, device.stream_buffer, 4, GL_FLOAT, GL_FALSE, FALSE, (GLsizei)stride,
offset, index * 4 * sizeof(float));
}
#else
offset = stream_upload(device.immediate_vertices, count * stride);
{
/* every attribute four floats, one after another */
static struct vertex_array_entry *immediate_array;
if (!immediate_array)
{
struct vertex_layout layout;
memset(&layout, 0, sizeof(layout));
offset = stream_upload(device.immediate_vertices, count * stride);
for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++)
layout_attribute(&layout, index, 0, 4, GL_FLOAT, GL_FALSE, FALSE, index * 4 * sizeof(float));
immediate_array = vertex_array_get(&layout);
}
state_vertex_array(immediate_array);
state_vertex_buffer(0, device.stream_buffer, offset, (GLsizei)stride);
}
{
state_attribute_stream(index, 0, device.stream_buffer, 4, GL_FLOAT, GL_FALSE, FALSE, (GLsizei)stride,
offset, index * 4 * sizeof(float));
}
#else
offset = stream_upload(device.immediate_vertices, count * stride);
{
/* every attribute four floats, one after another */
static struct vertex_array_entry *immediate_array;
if (!immediate_array)
{
struct vertex_layout layout;
memset(&layout, 0, sizeof(layout));
for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++)
layout_attribute(&layout, index, 0, 4, GL_FLOAT, GL_FALSE, FALSE, index * 4 * sizeof(float));
immediate_array = vertex_array_get(&layout);
}
state_vertex_array(immediate_array);
state_vertex_buffer(0, device.stream_buffer, offset, (GLsizei)stride);
}
#endif
}
}
if (type == D3DPT_QUADLIST)
{
unsigned long index_count;
@@ -4901,6 +5345,19 @@ void WINAPI D3DDevice_SetVertexDataColor(INT reg, D3DCOLOR color)
/* ---------- clearing */
/* the state a clear changed, no longer what the shadows say: the masks it
cleared through, the scissor (left disabled) and its rectangle. The rest
of the state is as it was, so the next draw sets only these again (all of
it again, at every clear, was hundreds of calls a frame) */
static void clear_state_forget(void)
{
gl_state.color_mask = 0xff;
gl_state.depth_mask = 0xff;
gl_state.stencil_write_mask = (GLuint)-1;
gl_state.scissor_test = 0;
memset(gl_state.scissor, 0xff, sizeof(gl_state.scissor));
}
void WINAPI D3DDevice_Clear(DWORD count, CONST D3DRECT *rectangles, DWORD flags, D3DCOLOR color, float z, DWORD stencil)
{
float rgba[4];
@@ -4952,7 +5409,7 @@ void WINAPI D3DDevice_Clear(DWORD count, CONST D3DRECT *rectangles, DWORD flags,
target_pixel((float)(device.viewport.Y + device.viewport.Height), 1) - y0);
glClear(mask);
glDisable(GL_SCISSOR_TEST);
xgpu_gl_state_invalidate();
clear_state_forget();
return;
}
glEnable(GL_SCISSOR_TEST);
@@ -4975,7 +5432,7 @@ void WINAPI D3DDevice_Clear(DWORD count, CONST D3DRECT *rectangles, DWORD flags,
glClear(mask);
}
glDisable(GL_SCISSOR_TEST);
xgpu_gl_state_invalidate();
clear_state_forget();
}
/* ---------- the anti-aliasing passes */
+6
View File
@@ -1059,7 +1059,10 @@ void halo_menus_art_register(void const *texture, char const *png)
{
free(art[index].png);
if (art[index].texture)
{
xgpu_gl_texture_deleted(art[index].texture);
glDeleteTextures(1, &art[index].texture);
}
}
memset(&art[index], 0, sizeof(art[index]));
art[index].data = data;
@@ -1074,7 +1077,10 @@ void halo_menus_art_forget(void)
{
free(art[index].png);
if (art[index].texture)
{
xgpu_gl_texture_deleted(art[index].texture);
glDeleteTextures(1, &art[index].texture);
}
}
art_count = 0;
}
+10 -1
View File
@@ -17,6 +17,9 @@ threads, asynchronous procedure calls, time, memory and debug output.
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#ifdef HALO_WEB
#include <emscripten.h>
#endif
#define PLATFORM_HANDLE_SIGNATURE 0x686e646cUL /* 'hndl' */
@@ -649,11 +652,17 @@ is by the time the game runs. */
static unsigned long long platform_clock_nanoseconds(void)
{
static unsigned long long start;
struct timespec now;
unsigned long long value, expected = 0;
#ifdef HALO_WEB
/* (the browser's clock, in milliseconds, which clock_gettime reads too,
without the 64-bit conversions of its way there) */
value = (unsigned long long)(emscripten_get_now() * 1000000.0);
#else
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
value = (unsigned long long)now.tv_sec * 1000000000ULL + (unsigned long long)now.tv_nsec;
#endif
__atomic_compare_exchange_n(&start, &expected, value - 10000000000ULL, 0, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
return value - __atomic_load_n(&start, __ATOMIC_RELAXED);
}
+3
View File
@@ -46,6 +46,9 @@ changes GL state behind it (binding a texture to upload it, deleting one)
must call this afterwards. */
void xgpu_gl_state_invalidate(void);
/* a texture about to be deleted: the binding shadows forget it, since GL
may give its name to a new texture (call before glDeleteTextures) */
void xgpu_gl_texture_deleted(unsigned int texture);
/* a compiled shader, or a linked program of two, or 0 with the log
written */
+17 -5
View File
@@ -255,20 +255,32 @@ multiply-adds). `tools/web_build.py` writes the graph.
converted (`xbox_textures.c`).
- S3TC only for 2D textures whose sides are multiples of 4: the others
are decoded.
- A vertex stride of at most 255 bytes: the 256-byte immediate-mode
vertex is drawn from an array for each attribute (`d3d8_gl.c`).
- A vertex stride of at most 255 bytes: an immediate-mode draw uploads
only the registers written since `Begin` (as every build does), and one
that writes all sixteen (256 bytes a vertex) is drawn from an array for
each attribute (`d3d8_gl.c`).
- No feedback loops: a draw that samples the target it draws into samples
a copy of it.
- No `glCopyImageSubData`, `glDrawElementsBaseVertex` or
`glMemoryBarrier`: the ES renderer's blits and rebased indices.
`glMemoryBarrier`: the ES renderer's blits and rebased indices. An
index array drawn twice the same is kept rebased in a buffer of its own
and drawn from there, not uploaded at each draw (`d3d8_gl.c`'s index
cache).
- Each call into WebGL costs a crossing from WebAssembly into the
browser, so draws whose streams all come from the mirror bind a vertex
array made for those places once (`d3d8_gl.c`'s vertex array cache),
rather than pointing each attribute again. The renderer draws only from
buffers, so Emscripten's emulation of client-side arrays (`FULL_ES3`),
which wrapped every draw, is not linked.
- Buffer writes are copies the browser orders with the draws, so the
streaming buffers never wait for the GPU (`port/web/src/web_gl_host.c`).
- **Memory watch.** WebAssembly cannot protect pages, so the textures'
memory is watched by hashing it once a frame, as the Android host does
under the x86 emulator (`port/web/src/web_memory_watch.c`,
`port/android/host/host_watch_hash.c`). A page that has stayed the same
for 30 checks is hashed every 4 frames, until the game locks it or a
file is read into it.
for 30 checks is hashed every 60 frames, until the game locks it or a
file is read into it (no other write into such a page was seen in a
match).
- **Sockets.** `port/web/src/web_net.c` gives `posix.h`'s sockets inside
the page: one machine with a loopback address and an address of its own
network (`10.0.0.1`, or the room's); datagrams to either, or broadcast,
+2
View File
@@ -280,6 +280,7 @@ void light_volume_render(
0,
definition->map.index,
definition->sequence_index);
rasterizer_widget_batch_sprites(TRUE); /* port: one draw */
count = definition->count;
for (sprite_index = 0; sprite_index < count; sprite_index++)
{
@@ -315,6 +316,7 @@ void light_volume_render(
0.0f,
real_argb_color_to_pixel32(&color));
}
rasterizer_widget_batch_sprites(FALSE); /* port: (drawn) */
rasterizer_widget_end();
}
}
+4
View File
@@ -99,6 +99,10 @@ long _rasterizer_widget_submit_occlusion_test(
long index);
long _rasterizer_widget_get_occlusion_test_result(
long index);
/* port: the 3D sprites drawn until it is turned off, or a widget's state
changes, are one draw (rasterizer_xbox_widgets.c) */
void rasterizer_widget_batch_sprites(
boolean batch);
void _rasterizer_widget_end(
void);
+171 -16
View File
@@ -291,6 +291,117 @@ extern struct pixel_shader_definition pixel_shader;
static long last_decal_index_queried_by_lruv_cache = NONE;
/* port: the decals drawn one after another with the same blend function and
bitmap are one draw, a list of their quads with each decal's color in its
vertices, instead of a draw each. A long fight leaves hundreds of decals in
view, and a draw is a call into the browser on the web and work for the
driver everywhere; on the Xbox each was a few words of push buffer. The
vertices are the decal's own (position, then its texture coordinates as the
declaration's normalized shorts), given as immediate data. */
#define DECAL_BATCH_MAXIMUM_DECALS 256
static struct
{
struct decal_vertex const *vertices;
short vertex_count;
byte color[4];
} decal_batch[DECAL_BATCH_MAXIMUM_DECALS];
static short decal_batch_count = 0;
static void rasterizer_decals_draw_batch(void);
/* port: the order the decals of a cluster are drawn in. Where it makes no
difference (a run of decals whose blend functions commute: they do not
write depth, and multiplying, adding, the least or the most of the
framebuffer and them comes to the same in any order, but for rounding in
the last bit), they go by their
blend function and bitmap, so that more of them share a batch
(decal_batch): a definition's decals pick among its bitmaps at random. */
static long decal_draw_order[MAXIMUM_DECALS_PER_MAP];
static unsigned long decal_draw_keys[MAXIMUM_DECALS_PER_MAP];
static long decal_sort_scratch[MAXIMUM_DECALS_PER_MAP];
static unsigned long decal_sort_scratch_keys[MAXIMUM_DECALS_PER_MAP];
static boolean rasterizer_decal_blend_function_commutes(
short framebuffer_blend_function)
{
return framebuffer_blend_function == _shader_framebuffer_blend_function_multiply ||
framebuffer_blend_function == _shader_framebuffer_blend_function_add ||
framebuffer_blend_function == _shader_framebuffer_blend_function_min ||
framebuffer_blend_function == _shader_framebuffer_blend_function_max ||
framebuffer_blend_function == _shader_framebuffer_blend_function_reverse_subtract;
}
/* sorts first to last (exclusive) of decal_draw_order by decal_draw_keys,
keeping the order of equal keys (a merge sort) */
static void rasterizer_decals_sort_run(
long first,
long last)
{
long width, start;
for (width = 1; width < last - first; width *= 2)
{
for (start = first; start < last; start += 2 * width)
{
long middle = MIN(start + width, last);
long end = MIN(start + 2 * width, last);
long left = start, right = middle, out = start;
while (left < middle || right < end)
{
long from = right < end && (left >= middle || decal_draw_keys[right] < decal_draw_keys[left]) ?
right++ : left++;
decal_sort_scratch[out] = decal_draw_order[from];
decal_sort_scratch_keys[out] = decal_draw_keys[from];
out++;
}
}
csmemcpy(&decal_draw_order[first], &decal_sort_scratch[first], (last - first) * sizeof(long));
csmemcpy(&decal_draw_keys[first], &decal_sort_scratch_keys[first], (last - first) * sizeof(unsigned long));
}
return;
}
/* the cluster's decals of the layer in the order they are drawn, into
decal_draw_order; their count */
static long rasterizer_decals_draw_order(
short cluster_index)
{
long count = 0, first, decal_index;
for (decal_index = decal_get_first_decal_index(cluster_index, local_layer);
decal_index != NONE && count < MAXIMUM_DECALS_PER_MAP;
decal_index = DECAL_GET(decal_index)->next_decal_index)
{
struct decal_datum *decal = DECAL_GET(decal_index);
struct decal_definition *definition = decal_definition_get(decal->definition_index);
short framebuffer_blend_function = definition->shader.framebuffer_blend_function;
decal_draw_order[count] = decal_index;
decal_draw_keys[count] = rasterizer_decal_blend_function_commutes(framebuffer_blend_function) &&
local_layer != _decal_layer_alpha_tested ?
((unsigned long)(framebuffer_blend_function & 0xF) << 28) |
((unsigned long)(definition->shader.map.index & 0xFFFFF) << 8) |
(decal->bitmap_index & 0xFF) :
0xFFFFFFFF;
count++;
}
for (first = 0; first < count; )
{
long last = first;
while (last < count && decal_draw_keys[last] != 0xFFFFFFFF)
last++;
if (last - first > 1)
rasterizer_decals_sort_run(first, last);
first = last + 1;
}
return count;
}
/* ---------- public code */
void rasterizer_decal_vertices_end_update(
@@ -636,6 +747,7 @@ void _rasterizer_decals_draw(
short cluster_index)
{
long decal_index;
long decal_count, decal_order_index; /* port: (rasterizer_decals_draw_order) */
match_assert(
"c:\\halo\\SOURCE\\rasterizer\\xbox\\rasterizer_xbox_decals.c",
@@ -646,10 +758,11 @@ void _rasterizer_decals_draw(
if (!rasterizer_debug_options.draw_environment_decals)
return;
decal_index = decal_get_first_decal_index(cluster_index, local_layer);
while (decal_index != NONE)
/* port: in the order rasterizer_decals_draw_order gives */
decal_count = rasterizer_decals_draw_order(cluster_index);
for (decal_order_index = 0; decal_order_index < decal_count; decal_order_index++)
{
struct decal_datum *decal = DECAL_GET(decal_index);
struct decal_datum *decal = DECAL_GET(decal_index = decal_draw_order[decal_order_index]);
struct decal_definition *definition = decal_definition_get(decal->definition_index);
struct decal_shader_definition *shader = &definition->shader;
short framebuffer_blend_function = shader->framebuffer_blend_function;
@@ -659,6 +772,7 @@ void _rasterizer_decals_draw(
if (local_framebuffer_blend_function != framebuffer_blend_function)
{
rasterizer_decals_draw_batch(); /* port: (decal_batch) */
local_framebuffer_blend_function = framebuffer_blend_function;
if (framebuffer_blend_function == _shader_framebuffer_blend_function_multiply ||
framebuffer_blend_function == _shader_framebuffer_blend_function_double_multiply)
@@ -726,6 +840,7 @@ void _rasterizer_decals_draw(
if (rasterizer_decal_cached_bitmap_group_index != shader->map.index ||
rasterizer_decal_cached_bitmap_index != (char)decal->bitmap_index)
{
rasterizer_decals_draw_batch(); /* port: (decal_batch) */
rasterizer_decal_cached_bitmap_group_index = shader->map.index;
rasterizer_decal_cached_bitmap_index = (char)decal->bitmap_index;
rasterizer_set_texture(
@@ -749,18 +864,25 @@ void _rasterizer_decals_draw(
"c:\\halo\\SOURCE\\rasterizer\\xbox\\rasterizer_xbox_decals.c",
511,
vertex_data_offset%sizeof(struct decal_vertex)==0);
IDirect3DDevice8_SetVertexData4ub(
global_d3d_device,
9,
color >> 16,
color >> 8,
color,
PIXEL32_COMPONENT_MASK - intensity);
IDirect3DDevice8_DrawPrimitive(
global_d3d_device,
D3DPT_QUADLIST,
vertex_data_offset/sizeof(struct decal_vertex),
decal->quad_count);
/* port: batched (decal_batch), in place of
SetVertexData4ub(9, color) and DrawPrimitive(D3DPT_QUADLIST) */
{
byte *vertex_data;
IDirect3DVertexBuffer8_Lock(local_d3d_vertex_buffer, 0, 0, &vertex_data, D3DLOCK_READONLY);
if (vertex_data)
{
if (decal_batch_count == DECAL_BATCH_MAXIMUM_DECALS)
rasterizer_decals_draw_batch();
decal_batch[decal_batch_count].vertices = (struct decal_vertex const *)(vertex_data + vertex_data_offset);
decal_batch[decal_batch_count].vertex_count = (short)(4 * decal->quad_count);
decal_batch[decal_batch_count].color[0] = (byte)(color >> 16);
decal_batch[decal_batch_count].color[1] = (byte)(color >> 8);
decal_batch[decal_batch_count].color[2] = (byte)color;
decal_batch[decal_batch_count].color[3] = (byte)(PIXEL32_COMPONENT_MASK - intensity);
decal_batch_count++;
}
}
if (rasterizer_debug_options.statistics_mode == _rasterizer_statistics_mode_geometry)
{
rasterizer_frame_statistics.decal_draw_count++;
@@ -768,8 +890,40 @@ void _rasterizer_decals_draw(
rasterizer_frame_statistics.decal_vertex_count += 4*decal->quad_count;
}
decal_index = decal->next_decal_index;
}
/* port: (the batch goes on into the next cluster's decals:
_rasterizer_decals_end draws it) */
return;
}
/* port: draws the decals batched (decal_batch) */
static void rasterizer_decals_draw_batch(
void)
{
short batch_index;
if (!decal_batch_count)
return;
IDirect3DDevice8_Begin(global_d3d_device, D3DPT_QUADLIST);
for (batch_index = 0; batch_index < decal_batch_count; batch_index++)
{
byte const *color = decal_batch[batch_index].color;
short vertex_index;
for (vertex_index = 0; vertex_index < decal_batch[batch_index].vertex_count; vertex_index++)
{
struct decal_vertex const *vertex = &decal_batch[batch_index].vertices[vertex_index];
IDirect3DDevice8_SetVertexData4ub(global_d3d_device, 9, color[0], color[1], color[2], color[3]);
IDirect3DDevice8_SetVertexData2f(global_d3d_device, 4,
(real)(short)(vertex->texcoord & 0xFFFF) / 32767.0f, (real)(short)(vertex->texcoord >> 16) / 32767.0f);
IDirect3DDevice8_SetVertexData4f(global_d3d_device, 0,
vertex->position.x, vertex->position.y, vertex->position.z, 1.0f);
}
}
IDirect3DDevice8_End(global_d3d_device);
decal_batch_count = 0;
return;
}
@@ -777,6 +931,7 @@ void _rasterizer_decals_draw(
void _rasterizer_decals_end(
void)
{
rasterizer_decals_draw_batch(); /* port: (decal_batch) */
if (local_layer == _decal_layer_alpha_tested)
rasterizer_set_stencil_mode(RASTERIZER_STENCIL_MODE_REJECT);
@@ -165,11 +165,69 @@ extern struct pixel_shader_definition pixel_shader;
static boolean warned_about_too_many_transparent_geometry_groups;
/* port: the 3D sprites of a light volume (a plasma bolt's glow, a couple
of hundred of them) as one list of quads, instead of a draw each: each
draw is a call into the browser on the web, and work for the driver
everywhere; the Xbox drew them from its push buffer. Only the sprites'
own registers differ between them (rasterizer_widget_batch_sprites). */
#define WIDGET_SPRITE_BATCH_MAXIMUM 256
struct widget_sprite_batch_vertex
{
real x, y, z;
short u, v;
};
static struct
{
unsigned long color;
struct widget_sprite_batch_vertex vertices[4];
} widget_sprite_batch[WIDGET_SPRITE_BATCH_MAXIMUM];
static short widget_sprite_batch_count = 0;
static boolean widget_sprite_batching = FALSE;
/* port: draws the sprites batched (widget_sprite_batch) */
static void rasterizer_widget_draw_sprite_batch(
void)
{
short sprite_index;
short vertex_index;
if (!widget_sprite_batch_count)
return;
IDirect3DDevice8_Begin(global_d3d_device, D3DPT_QUADLIST);
for (sprite_index = 0; sprite_index < widget_sprite_batch_count; sprite_index++)
{
IDirect3DDevice8_SetVertexDataColor(global_d3d_device, 9, widget_sprite_batch[sprite_index].color);
for (vertex_index = 0; vertex_index < 4; vertex_index++)
{
struct widget_sprite_batch_vertex const *vertex = &widget_sprite_batch[sprite_index].vertices[vertex_index];
IDirect3DDevice8_SetVertexData2s(global_d3d_device, 4, vertex->u, vertex->v);
IDirect3DDevice8_SetVertexData4f(global_d3d_device, 0, vertex->x, vertex->y, vertex->z, 1.0f);
}
}
IDirect3DDevice8_End(global_d3d_device);
widget_sprite_batch_count = 0;
return;
}
void rasterizer_widget_batch_sprites(
boolean batch)
{
rasterizer_widget_draw_sprite_batch();
widget_sprite_batching = batch;
return;
}
/* ---------- public code */
void _rasterizer_widget_end(
void)
{
rasterizer_widget_batch_sprites(FALSE); /* port: (widget_sprite_batch) */
return;
}
@@ -548,6 +606,7 @@ boolean _rasterizer_widget_set_texture(
"c:\\halo\\SOURCE\\rasterizer\\xbox\\rasterizer_xbox_widgets.c",
285,
global_d3d_device);
rasterizer_widget_draw_sprite_batch(); /* port: (widget_sprite_batch) */
if (bitmap_group_index != NONE)
success = rasterizer_set_texture_non_blocking(
stage_index,
@@ -600,6 +659,7 @@ void _rasterizer_widget_set_tint_factor(
"c:\\halo\\SOURCE\\rasterizer\\xbox\\rasterizer_xbox_widgets.c",
318,
global_d3d_device);
rasterizer_widget_draw_sprite_batch(); /* port: (widget_sprite_batch) */
IDirect3DDevice8_SetVertexData2f(
global_d3d_device,
10,
@@ -616,6 +676,7 @@ void _rasterizer_widget_set_zbuffer_enable(
"c:\\halo\\SOURCE\\rasterizer\\xbox\\rasterizer_xbox_widgets.c",
334,
global_d3d_device);
rasterizer_widget_draw_sprite_batch(); /* port: (widget_sprite_batch) */
D3DDevice_SetRenderState_ZEnable(zbuffer_enable);
return;
@@ -790,6 +851,35 @@ void _rasterizer_widget_draw_sprite3d(
scale_x = scale_y = 1.0f;
}
/* port: (widget_sprite_batch) */
if (widget_sprite_batching)
{
struct widget_sprite_batch_vertex *vertices;
if (widget_sprite_batch_count == WIDGET_SPRITE_BATCH_MAXIMUM)
rasterizer_widget_draw_sprite_batch();
widget_sprite_batch[widget_sprite_batch_count].color = color;
vertices = widget_sprite_batch[widget_sprite_batch_count].vertices;
vertices[0].x = projected_center.x - scale_x * axis_x;
vertices[0].y = projected_center.y - scale_y * axis_y;
vertices[0].u = 0;
vertices[0].v = 0;
vertices[1].x = projected_center.x + scale_x * axis_y;
vertices[1].y = projected_center.y - scale_y * axis_x;
vertices[1].u = 1;
vertices[1].v = 0;
vertices[2].x = projected_center.x + scale_x * axis_x;
vertices[2].y = projected_center.y + scale_y * axis_y;
vertices[2].u = 1;
vertices[2].v = 1;
vertices[3].x = projected_center.x - scale_x * axis_y;
vertices[3].y = projected_center.y + scale_y * axis_x;
vertices[3].u = 0;
vertices[3].v = 1;
vertices[0].z = vertices[1].z = vertices[2].z = vertices[3].z = projected_center.z;
widget_sprite_batch_count++;
return;
}
IDirect3DDevice8_Begin(
global_d3d_device,
D3DPT_TRIANGLEFAN);
+70 -1
View File
@@ -326,6 +326,75 @@ static void structure_render_dynamic_triangles_from_bitvector(
return;
}
/* port: the surface indices in ascending order, as qsort_4byte with
compare_surface_indices puts them, sorted by their bytes (a radix sort)
instead of by comparisons through a function: the sort took a twentieth of
the web build's frame */
static void structure_render_sort_surface_indices(
long *surface_indices,
long surface_count)
{
static long *scratch = NULL;
static long scratch_count = 0;
unsigned long counts[256];
unsigned long maximum = 0;
long *source = surface_indices;
long *destination;
long index;
short shift;
for (index = 0; index < surface_count; index++)
{
if (surface_indices[index] < 0)
break;
if ((unsigned long)surface_indices[index] > maximum)
maximum = (unsigned long)surface_indices[index];
}
if (index < surface_count || surface_count <= 32)
{
qsort_4byte(surface_indices, surface_count, compare_surface_indices);
return;
}
if (scratch_count < surface_count)
{
long *grown = (long *)realloc(scratch, surface_count * sizeof(long));
if (!grown)
{
qsort_4byte(surface_indices, surface_count, compare_surface_indices);
return;
}
scratch = grown;
scratch_count = surface_count;
}
destination = scratch;
for (shift = 0; shift < 32 && (maximum >> shift); shift += 8)
{
unsigned long total = 0;
long *swap;
memset(counts, 0, sizeof(counts));
for (index = 0; index < surface_count; index++)
counts[((unsigned long)source[index] >> shift) & 0xff]++;
for (index = 0; index < 256; index++)
{
unsigned long bucket = counts[index];
counts[index] = total;
total += bucket;
}
for (index = 0; index < surface_count; index++)
destination[counts[((unsigned long)source[index] >> shift) & 0xff]++] = source[index];
swap = source;
source = destination;
destination = swap;
}
if (source != surface_indices)
memcpy(surface_indices, source, surface_count * sizeof(long));
return;
}
static void structure_render_dynamic_triangles_from_indices(
long surface_count,
long *surface_indices,
@@ -334,7 +403,7 @@ static void structure_render_dynamic_triangles_from_indices(
struct structure_bsp *structure_bsp = global_structure_bsp_get();
long surface_index_index;
qsort_4byte(surface_indices, surface_count, compare_surface_indices);
structure_render_sort_surface_indices(surface_indices, surface_count); /* port: (not qsort_4byte) */
for (surface_index_index = 0;
surface_index_index < surface_count;
+18 -1
View File
@@ -35,7 +35,8 @@ enum tag_field_type
/* ---------- macros */
#define TAG_BLOCK_GET_ELEMENT(block_address, index, type) ((type *)tag_block_get_element_with_size((block_address), (index), sizeof(type)))
/* port: tag_block_get_element_fast (below) */
#define TAG_BLOCK_GET_ELEMENT(block_address, index, type) ((type *)tag_block_get_element_fast((block_address), (index), sizeof(type)))
/* ---------- structures */
@@ -125,6 +126,22 @@ void *tag_empty_data(void);
void *tag_data_get_pointer(struct tag_data const *data, long offset, long size);
void *tag_block_get_element_with_size(struct tag_block const *block, long index, long element_size);
/* port: tag_block_get_element_with_size without a call where all it checks
holds (the game's most frequent call: a fortieth of the web build's frame),
and that function where something does not, which notes and handles it */
static __inline void *tag_block_get_element_fast(
struct tag_block const *block,
long index,
long element_size)
{
if (block && index >= 0 && index < block->count && block->address &&
(!block->definition || block->definition->element_size == element_size))
{
return (char *)block->address + index * element_size;
}
return tag_block_get_element_with_size(block, index, element_size);
}
/* ---------- prototypes/CACHE_FILES.C */
long tag_loaded(long group_tag, const char *name);
-1
View File
@@ -305,7 +305,6 @@ def generate_web_build(n: Writer, sln: Any) -> None:
"-sOFFSCREENCANVASES_TO_PTHREAD=#canvas",
"-sMIN_WEBGL_VERSION=2",
"-sMAX_WEBGL_VERSION=2",
"-sFULL_ES3=1",
"-sGL_ENABLE_GET_PROC_ADDRESS=1",
"-sWASMFS=1",
"-lopfs.js",