diff --git a/port/android/host/host_watch_hash.h b/port/android/host/host_watch_hash.h index 121c4ce5..177a8e78 100644 --- a/port/android/host/host_watch_hash.h +++ b/port/android/host/host_watch_hash.h @@ -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 { diff --git a/port/linux/NETCODE.md b/port/linux/NETCODE.md index 913b12dd..c1207dbb 100644 --- a/port/linux/NETCODE.md +++ b/port/linux/NETCODE.md @@ -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, diff --git a/port/linux/README.md b/port/linux/README.md index 27960d63..10e9cc47 100644 --- a/port/linux/README.md +++ b/port/linux/README.md @@ -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: diff --git a/port/linux/src/d3d8_gl.c b/port/linux/src/d3d8_gl.c index 3dc40e06..81a9f852 100644 --- a/port/linux/src/d3d8_gl.c +++ b/port/linux/src/d3d8_gl.c @@ -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 */ diff --git a/port/linux/src/menu_files.c b/port/linux/src/menu_files.c index e46b2e29..fd79da2a 100644 --- a/port/linux/src/menu_files.c +++ b/port/linux/src/menu_files.c @@ -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; } diff --git a/port/linux/src/xbox_kernel.c b/port/linux/src/xbox_kernel.c index 7c188532..39e1e21c 100644 --- a/port/linux/src/xbox_kernel.c +++ b/port/linux/src/xbox_kernel.c @@ -17,6 +17,9 @@ threads, asynchronous procedure calls, time, memory and debug output. #include #include #include +#ifdef HALO_WEB +#include +#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); } diff --git a/port/linux/src/xgpu.h b/port/linux/src/xgpu.h index 982f24b7..a511c711 100644 --- a/port/linux/src/xgpu.h +++ b/port/linux/src/xgpu.h @@ -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 */ diff --git a/port/web/README.md b/port/web/README.md index 6a8770d4..35423684 100644 --- a/port/web/README.md +++ b/port/web/README.md @@ -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, diff --git a/source/objects/widgets/light_volumes.c b/source/objects/widgets/light_volumes.c index c0cca264..88706e74 100644 --- a/source/objects/widgets/light_volumes.c +++ b/source/objects/widgets/light_volumes.c @@ -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(); } } diff --git a/source/rasterizer/rasterizer_widgets.h b/source/rasterizer/rasterizer_widgets.h index c989e8d7..41cdd5c2 100644 --- a/source/rasterizer/rasterizer_widgets.h +++ b/source/rasterizer/rasterizer_widgets.h @@ -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); diff --git a/source/rasterizer/xbox/rasterizer_xbox_decals.c b/source/rasterizer/xbox/rasterizer_xbox_decals.c index de201e0a..54d91039 100644 --- a/source/rasterizer/xbox/rasterizer_xbox_decals.c +++ b/source/rasterizer/xbox/rasterizer_xbox_decals.c @@ -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); diff --git a/source/rasterizer/xbox/rasterizer_xbox_widgets.c b/source/rasterizer/xbox/rasterizer_xbox_widgets.c index 03c09cca..b0039a0e 100644 --- a/source/rasterizer/xbox/rasterizer_xbox_widgets.c +++ b/source/rasterizer/xbox/rasterizer_xbox_widgets.c @@ -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); diff --git a/source/structures/structure_render.c b/source/structures/structure_render.c index 8e9605b3..ea53a628 100644 --- a/source/structures/structure_render.c +++ b/source/structures/structure_render.c @@ -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; diff --git a/source/tag_files/tag_groups.h b/source/tag_files/tag_groups.h index 13a75626..6140f8a8 100644 --- a/source/tag_files/tag_groups.h +++ b/source/tag_files/tag_groups.h @@ -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); diff --git a/tools/web_build.py b/tools/web_build.py index 6f7a4fa8..fe1de076 100644 --- a/tools/web_build.py +++ b/tools/web_build.py @@ -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",