mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-21 00:00:29 +02:00
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:
1 parent
6399ed9d4a
commit
a3277d94dc
15 files changed
+893
-63
No files matched your search
@@ -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
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user