1. improved AMS, 2. improved render scene structure for the future

This commit is contained in:
Dennis Eichhorn 2024-09-25 01:35:16 +02:00
parent ae2455a43d
commit e8bc8a1647
8 changed files with 181 additions and 104 deletions

View File

@ -46,6 +46,15 @@ struct Asset {
bool is_ram;
bool is_vram;
// Describes if the asset can be removed/garbage collected IF necessary
// This however only happens if space is needed
bool can_garbage_collect_ram;
bool can_garbage_collect_vram;
// Describes if the asset should be removed/garbage collected during CPU/GPU down time
bool should_garbage_collect_ram;
bool should_garbage_collect_vram;
Asset* next;
Asset* prev;

View File

@ -96,6 +96,7 @@ void ams_create(AssetManagementSystem* ams, byte* buf, int chunk_size, int count
ams->last = NULL;
}
inline
uint64 ams_get_vram_usage(AssetManagementSystem* ams)
{
uint64 size = 0;
@ -119,19 +120,18 @@ void ams_free_asset(AssetManagementSystem* ams, Asset* asset)
}
}
inline
Asset* ams_get_asset(AssetManagementSystem* ams, uint64 element)
{
return (Asset *) chunk_get_element(&ams->asset_memory, element, false);
}
inline
Asset* ams_get_asset(AssetManagementSystem* ams, const char* key)
{
HashEntryInt64* entry = (HashEntryInt64 *) hashmap_get_entry(&ams->hash_map, key);
if (entry == NULL) {
return NULL;
}
HashEntry* entry = hashmap_get_entry(&ams->hash_map, key);
return (Asset *) chunk_get_element(&ams->asset_memory, entry->value, false);
return entry ? (Asset *) entry->value : NULL;
}
// @todo implement defragment command to optimize memory layout since the memory layout will become fragmented over time
@ -153,7 +153,7 @@ Asset* ams_reserve_asset(AssetManagementSystem* ams, const char* name, uint64 el
strncpy(asset->name, name, name_length);
asset->name[name_length] = '\0';
hashmap_insert(&ams->hash_map, name, free_asset);
hashmap_insert(&ams->hash_map, name, (uintptr_t) asset);
chunk_reserve_index(&ams->asset_data_memory, free_asset, elements, true);
asset->self = chunk_get_element(&ams->asset_data_memory, free_asset);

View File

@ -9,8 +9,6 @@
#ifndef TOS_GPUAPI_OPENGL_H
#define TOS_GPUAPI_OPENGL_H
// @todo remove some of the unused consts below
//
#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12
#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13
@ -723,6 +721,11 @@
#define GL_TEXTURE_FREE_MEMORY_ATI 0x87FC
#define GL_RENDERBUFFER_FREE_MEMORY_ATI 0x87FD
#define GL_PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257
#define GL_PROGRAM_BINARY_LENGTH 0x8741
#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE
#define GL_PROGRAM_BINARY_FORMATS 0x87FF
typedef char GLchar;
typedef ptrdiff_t GLsizeiptr;
typedef ptrdiff_t GLintptr;

View File

@ -109,12 +109,12 @@ uint32 get_texture_data_type(uint32 texture_data_type)
// 4. load_texture_to_gpu
inline
void prepare_texture(Texture* texture, uint32 texture_unit)
void prepare_texture(Texture* texture)
{
uint32 texture_data_type = get_texture_data_type(texture->texture_data_type);
glGenTextures(1, (GLuint *) &texture->id);
glActiveTexture(GL_TEXTURE0 + texture_unit);
glActiveTexture(GL_TEXTURE0 + texture->sample_id);
glBindTexture(texture_data_type, (GLuint) texture->id);
}
@ -166,6 +166,7 @@ GLuint shader_make(GLenum type, const char *source, RingMemory* ring)
return shader;
}
inline
GLuint shader_load(GLenum type, const char* path, RingMemory* ring) {
uint64 temp = ring->pos;
@ -181,6 +182,15 @@ GLuint shader_load(GLenum type, const char* path, RingMemory* ring) {
return result;
}
inline
int32 program_get_size(uint32 program)
{
int32 size;
glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &size);
return size;
}
GLuint program_make(
GLuint vertex_shader,
GLuint fragment_shader,
@ -195,6 +205,7 @@ GLuint program_make(
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
glProgramParameteri(program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
glLinkProgram(program);
GLint status;
@ -263,27 +274,28 @@ void draw_triangles_3d(VertexRef* vertices, GLuint buffer, int count) {
glBindBuffer(GL_ARRAY_BUFFER, buffer);
// position attribute
glVertexAttribPointer(vertices->position, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void *) 0);
glEnableVertexAttribArray(vertices->position);
glVertexAttribPointer(vertices->data_id, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void *) 0);
glEnableVertexAttribArray(vertices->data_id);
// normal attribute
glVertexAttribPointer(vertices->normal, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void *) (sizeof(float) * 3));
glEnableVertexAttribArray(vertices->normal);
glVertexAttribPointer(vertices->normal_id, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void *) (sizeof(float) * 3));
glEnableVertexAttribArray(vertices->normal_id);
// texture coord attribute
glVertexAttribIPointer(vertices->tex_coord, 2, GL_UNSIGNED_INT, sizeof(Vertex3D), (void *) (sizeof(float) * 6));
glEnableVertexAttribArray(vertices->tex_coord);
// vs glVertexAttribPointer
glVertexAttribIPointer(vertices->tex_coord_id, 2, GL_UNSIGNED_INT, sizeof(Vertex3D), (void *) (sizeof(float) * 6));
glEnableVertexAttribArray(vertices->tex_coord_id);
// color attribute
glVertexAttribPointer(vertices->color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void *) (sizeof(float) * 8));
glEnableVertexAttribArray(vertices->color);
glVertexAttribPointer(vertices->color_id, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void *) (sizeof(float) * 8));
glEnableVertexAttribArray(vertices->color_id);
glDrawArrays(GL_TRIANGLES, 0, count);
glDisableVertexAttribArray(vertices->position);
glDisableVertexAttribArray(vertices->normal);
glDisableVertexAttribArray(vertices->tex_coord);
glDisableVertexAttribArray(vertices->color);
glDisableVertexAttribArray(vertices->data_id);
glDisableVertexAttribArray(vertices->normal_id);
glDisableVertexAttribArray(vertices->tex_coord_id);
glDisableVertexAttribArray(vertices->color_id);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
@ -293,22 +305,22 @@ void draw_triangles_3d_textureless(VertexRef* vertices, GLuint buffer, int count
glBindBuffer(GL_ARRAY_BUFFER, buffer);
// position attribute
glVertexAttribPointer(vertices->position, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void *) 0);
glEnableVertexAttribArray(vertices->position);
glVertexAttribPointer(vertices->data_id, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void *) 0);
glEnableVertexAttribArray(vertices->data_id);
// normal attribute
glVertexAttribPointer(vertices->normal, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void *) (sizeof(float) * 3));
glEnableVertexAttribArray(vertices->normal);
glVertexAttribPointer(vertices->normal_id, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void *) (sizeof(float) * 3));
glEnableVertexAttribArray(vertices->normal_id);
// color attribute
glVertexAttribPointer(vertices->color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void *) (sizeof(float) * 8));
glEnableVertexAttribArray(vertices->color);
glVertexAttribPointer(vertices->color_id, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void *) (sizeof(float) * 8));
glEnableVertexAttribArray(vertices->color_id);
glDrawArrays(GL_TRIANGLES, 0, count);
glDisableVertexAttribArray(vertices->position);
glDisableVertexAttribArray(vertices->normal);
glDisableVertexAttribArray(vertices->color);
glDisableVertexAttribArray(vertices->data_id);
glDisableVertexAttribArray(vertices->normal_id);
glDisableVertexAttribArray(vertices->color_id);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
@ -318,22 +330,22 @@ void draw_triangles_3d_colorless(VertexRef* vertices, GLuint buffer, int count)
glBindBuffer(GL_ARRAY_BUFFER, buffer);
// position attribute
glVertexAttribPointer(vertices->position, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void *) 0);
glEnableVertexAttribArray(vertices->position);
glVertexAttribPointer(vertices->data_id, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void *) 0);
glEnableVertexAttribArray(vertices->data_id);
// normal attribute
glVertexAttribPointer(vertices->normal, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void *) (sizeof(float) * 3));
glEnableVertexAttribArray(vertices->normal);
glVertexAttribPointer(vertices->normal_id, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void *) (sizeof(float) * 3));
glEnableVertexAttribArray(vertices->normal_id);
// texture coord attribute
glVertexAttribIPointer(vertices->tex_coord, 2, GL_UNSIGNED_INT, sizeof(Vertex3D), (void *) (sizeof(float) * 6));
glEnableVertexAttribArray(vertices->tex_coord);
glVertexAttribIPointer(vertices->tex_coord_id, 2, GL_UNSIGNED_INT, sizeof(Vertex3D), (void *) (sizeof(float) * 6));
glEnableVertexAttribArray(vertices->tex_coord_id);
glDrawArrays(GL_TRIANGLES, 0, count);
glDisableVertexAttribArray(vertices->position);
glDisableVertexAttribArray(vertices->normal);
glDisableVertexAttribArray(vertices->tex_coord);
glDisableVertexAttribArray(vertices->data_id);
glDisableVertexAttribArray(vertices->normal_id);
glDisableVertexAttribArray(vertices->tex_coord_id);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
@ -343,22 +355,23 @@ void draw_triangles_2d(VertexRef* vertices, GLuint buffer, int count) {
glBindBuffer(GL_ARRAY_BUFFER, buffer);
// position attribute
glVertexAttribPointer(vertices->position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (void *) 0);
glEnableVertexAttribArray(vertices->position);
glVertexAttribPointer(vertices->position_id, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (void *) 0);
glEnableVertexAttribArray(vertices->position_id);
// texture coord attribute
glVertexAttribIPointer(vertices->tex_coord, 2, GL_UNSIGNED_INT, sizeof(Vertex2D), (void *) (sizeof(float) * 2));
glEnableVertexAttribArray(vertices->tex_coord);
// vs glVertexAttribPointer
glVertexAttribIPointer(vertices->tex_coord_id, 2, GL_UNSIGNED_INT, sizeof(Vertex2D), (void *) (sizeof(float) * 2));
glEnableVertexAttribArray(vertices->tex_coord_id);
// color attribute
glVertexAttribPointer(vertices->color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (void *) (sizeof(float) * 4));
glEnableVertexAttribArray(vertices->color);
glVertexAttribPointer(vertices->color_id, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (void *) (sizeof(float) * 4));
glEnableVertexAttribArray(vertices->color_id);
glDrawArrays(GL_TRIANGLES, 0, count);
glDisableVertexAttribArray(vertices->position);
glDisableVertexAttribArray(vertices->tex_coord);
glDisableVertexAttribArray(vertices->color);
glDisableVertexAttribArray(vertices->position_id);
glDisableVertexAttribArray(vertices->tex_coord_id);
glDisableVertexAttribArray(vertices->color_id);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
@ -368,17 +381,17 @@ void draw_triangles_2d_textureless(VertexRef* vertices, GLuint buffer, int count
glBindBuffer(GL_ARRAY_BUFFER, buffer);
// position attribute
glVertexAttribPointer(vertices->position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (void *) 0);
glEnableVertexAttribArray(vertices->position);
glVertexAttribPointer(vertices->data_id, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (void *) 0);
glEnableVertexAttribArray(vertices->data_id);
// color attribute
glVertexAttribPointer(vertices->color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (void *) (sizeof(float) * 4));
glEnableVertexAttribArray(vertices->color);
glVertexAttribPointer(vertices->color_id, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (void *) (sizeof(float) * 4));
glEnableVertexAttribArray(vertices->color_id);
glDrawArrays(GL_TRIANGLES, 0, count);
glDisableVertexAttribArray(vertices->position);
glDisableVertexAttribArray(vertices->color);
glDisableVertexAttribArray(vertices->data_id);
glDisableVertexAttribArray(vertices->color_id);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
@ -388,36 +401,21 @@ void draw_triangles_2d_colorless(VertexRef* vertices, GLuint buffer, int count)
glBindBuffer(GL_ARRAY_BUFFER, buffer);
// position attribute
glVertexAttribPointer(vertices->position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (void *) 0);
glEnableVertexAttribArray(vertices->position);
glVertexAttribPointer(vertices->data_id, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (void *) 0);
glEnableVertexAttribArray(vertices->data_id);
// texture coord attribute
glVertexAttribIPointer(vertices->tex_coord, 2, GL_UNSIGNED_INT, sizeof(Vertex2D), (void *) (sizeof(float) * 2));
glEnableVertexAttribArray(vertices->tex_coord);
glVertexAttribIPointer(vertices->tex_coord_id, 2, GL_UNSIGNED_INT, sizeof(Vertex2D), (void *) (sizeof(float) * 2));
glEnableVertexAttribArray(vertices->tex_coord_id);
glDrawArrays(GL_TRIANGLES, 0, count);
glDisableVertexAttribArray(vertices->position);
glDisableVertexAttribArray(vertices->tex_coord);
glDisableVertexAttribArray(vertices->data_id);
glDisableVertexAttribArray(vertices->tex_coord_id);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
struct TextRender {
uint32 align;
uint32 x;
uint32 y;
float scale; // @is it really scale or is it position in texture map?
VertexRef vertices;
char* text;
TextShader shader_data;
};
#define TEXT_ALIGN_LEFT 0
#define TEXT_ALIGN_CENTER 1
#define TEXT_ALIGN_RIGHT 2
inline
int calculate_face_size(int components, int faces)
{
@ -511,42 +509,75 @@ int get_gpu_free_memory()
return available;
}
struct TextRender {
uint32 align;
uint32 x;
uint32 y;
float scale;
VertexRef vertices;
char* text;
uint32 sampler_id;
TextShader shader_data;
};
#define TEXT_ALIGN_LEFT 0
#define TEXT_ALIGN_CENTER 1
#define TEXT_ALIGN_RIGHT 2
// @performance This needs to handle batched randering, isolated rendering is WAY to inefficient
inline
void render_text(
void render_text_batched(
RingMemory* ring,
int width, int height,
TextRender* text_data
) {
glUseProgram(text_data->shader_data.program_id);
glUniform1i(text_data->shader_data.sampler_id, 1);
glUniform1i(text_data->shader_data.sampler_addr, text_data->vertices.sampler_id);
// @performance Instead of re-creating the matrix every render call just use the id to activate it
// 2d projection
if (text_data->shader_data.matrix_id == 0) {
float matrix[16] = {};
mat4_ortho_sparse_rh(matrix, 0.0f, (float) width, 0.0f, (float) height, -1.0f, 1.0f);
glUniformMatrix4fv(text_data->shader_data.matrix_id, 1, GL_FALSE, matrix);
glUniformMatrix4fv(text_data->shader_data.matrix_addr, 1, GL_FALSE, matrix);
text_data->shader_data.matrix_id = 1;
// @bug this is wrong, we need to make buffer instead. We don't want to upload the matrix every time
// Isn't this buffer the same for every text?
// If yes, consider to save the orth projection matrix globally and change it whenever we change the resolution/window dimensions
} else {
// @question Do we even need to bind it if we never change it?
// We also never bind our projection matrix apart from the first time and it works. This should be the same no?
//glBindVertexArray(text_data->shader_data.matrix_id);
}
int length = (int) strlen(text_data->text);
float x = text_data->x - text_data->scale * text_data->align * (length - 1) / 2;
// @todo fix
GLfloat *data = NULL; //malloc_faces(4, length); // sizeof(GLfloat) * 6 * 4 * length
// @performance Only create when the text got removed from memory
if (text_data->vertices.data_id == 0) {
GLfloat *data = (GLfloat *) ring_get_memory(ring, sizeof(GLfloat) * 6 * 4 * length);
for (int i = 0; i < length; i++) {
make_character(data + i * 24, x, (float) text_data->y, text_data->scale / 2, text_data->scale, text_data->text[i]);
x += text_data->scale;
for (int i = 0; i < length; i++) {
make_character(data + i * 24, x, (float) text_data->y, text_data->scale / 2, text_data->scale, text_data->text[i]);
x += text_data->scale;
}
text_data->vertices.data_id = gpuapi_buffer_generate(sizeof(GLfloat) * 6 * 4 * length, data);
} else {
glBindBuffer(GL_ARRAY_BUFFER, text_data->vertices.data_id);
//glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW); Is this required?
}
GLuint buffer = gpuapi_buffer_generate(sizeof(GLfloat) * 6 * 4 * length, data);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
draw_triangles_2d(&text_data->vertices, buffer, length * 6);
draw_triangles_2d(&text_data->vertices, text_data->vertices.data_id, length * 6);
glDisable(GL_BLEND);
glDeleteBuffers(1, &buffer);
// @performance We should only delete the buffer, when the text becomes invisible
// @todo remember to implement a remove logic for all the buffers
//glDeleteBuffers(1, &text_data->vertices.data_id);
}
/*

View File

@ -1040,7 +1040,6 @@ extern "C" {
(GLenum target, GLenum pname, GLfloat *params);
}
typedef void WINAPI type_glTexImage2DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations);
static type_glTexImage2DMultisample* glTexImage2DMultisample;
@ -1218,6 +1217,9 @@ static type_glDrawArraysInstanced* glDrawArraysInstanced;
typedef void WINAPI type_glDrawElementsInstanced(GLenum mode, GLint count, GLenum type, const void* indices, GLsizei instancecount);
static type_glDrawElementsInstanced* glDrawElementsInstanced;
typedef void WINAPI type_glProgramParameteri(GLuint program, GLenum pname, GLint value);
static type_glProgramParameteri* glProgramParameteri;
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093
@ -1298,7 +1300,6 @@ static type_glDrawElementsInstanced* glDrawElementsInstanced;
#define WGL_ALPHA_BITS_ARB 0x201B
#define WGL_DEPTH_BITS_ARB 0x2022
typedef HGLRC WINAPI wgl_create_context_attribs_arb(HDC hDC, HGLRC hShareContext, const int *attribList);
static wgl_create_context_attribs_arb* wglCreateContextAttribsARB;
@ -1496,10 +1497,7 @@ void opengl_init_gl()
glGetShaderiv = (type_glGetShaderiv *) wglGetProcAddress("glGetShaderiv");
glDrawArraysInstanced = (type_glDrawArraysInstanced *) wglGetProcAddress("glDrawArraysInstanced");
glDrawElementsInstanced = (type_glDrawElementsInstanced *) wglGetProcAddress("glDrawElementsInstanced");
if (wglSwapIntervalEXT) {
wglSwapIntervalEXT(0);
}
glProgramParameteri = (type_glProgramParameteri *) wglGetProcAddress("glProgramParameteri");
}
void opengl_init(Window* window)
@ -1524,6 +1522,10 @@ void opengl_init(Window* window)
}
opengl_init_gl();
if (wglSwapIntervalEXT) {
wglSwapIntervalEXT(0);
}
}
#endif

View File

@ -38,6 +38,7 @@
struct Texture {
uint64 id;
byte sample_id;
// @question Should the texture hold the texture unit?
// If yes remember to update prepare_texture()

View File

@ -25,20 +25,29 @@ struct Vertex2D {
};
struct VertexRef {
uint32 position;
uint32 normal;
uint32 tex_coord;
uint32 color;
uint32 index;
uint32 data_id;
uint32 position_id;
uint32 normal_id;
uint32 tex_coord_id;
uint32 sampler_id; // e.g. GL_TEXTURE0
uint32 color_id;
uint32 index_id;
};
// Data for the text shader
struct TextShader {
uint32 program_id;
uint32 matrix_id;
uint32 uv_id;
uint32 color_id;
uint32 sampler_id;
uint32 matrix_addr;
uint32 uv_addr;
uint32 color_addr;
uint32 sampler_addr;
};
enum VertexType {

View File

@ -31,6 +31,13 @@ struct HashEntryInt64 {
int64 value;
};
struct HashEntryUIntPtr {
int64 element_id;
char key[MAX_KEY_LENGTH];
HashEntryUIntPtr* next;
uintptr_t value;
};
struct HashEntryVoidP {
int64 element_id;
char key[MAX_KEY_LENGTH];
@ -141,6 +148,21 @@ void hashmap_insert(HashMap* hm, const char* key, int64 value) {
hm->table[index] = entry;
}
void hashmap_insert(HashMap* hm, const char* key, uintptr_t value) {
uint64 index = hash_djb2(key) % hm->buf.count;
int64 element = chunk_reserve(&hm->buf, 1);
HashEntryUIntPtr* entry = (HashEntryUIntPtr *) chunk_get_element(&hm->buf, element, true);
entry->element_id = element;
strncpy(entry->key, key, MAX_KEY_LENGTH);
entry->key[MAX_KEY_LENGTH - 1] = '\0';
entry->value = value;
entry->next = (HashEntryUIntPtr *) hm->table[index];
hm->table[index] = entry;
}
void hashmap_insert(HashMap* hm, const char* key, void* value) {
uint64 index = hash_djb2(key) % hm->buf.count;
@ -206,7 +228,7 @@ void hashmap_insert(HashMap* hm, const char* key, byte* value) {
hm->table[index] = entry;
}
void* hashmap_get_entry(HashMap* hm, const char* key) {
HashEntry* hashmap_get_entry(HashMap* hm, const char* key) {
uint64 index = hash_djb2(key) % hm->buf.count;
HashEntry* entry = (HashEntry *) hm->table[index];