From e5789437006f440b5bd4b10368a583d2e3bbcd5d Mon Sep 17 00:00:00 2001 From: MysterD Date: Sat, 1 Apr 2023 23:44:10 -0700 Subject: [PATCH] Started optimizing text rendering --- bin/segment2.c | 3 + src/pc/djui/djui_font.c | 24 ++-- src/pc/djui/djui_gbi.h | 56 +++++++++ src/pc/djui/djui_gfx.c | 20 ++-- src/pc/gfx/gfx_pc.c | 111 ++++++++++++++++-- src/pc/pc_main.c | 2 +- .../custom_font_normal.rgba32.png | Bin 0 -> 3868 bytes 7 files changed, 192 insertions(+), 24 deletions(-) create mode 100644 textures/custom_font_title/custom_font_normal.rgba32.png diff --git a/bin/segment2.c b/bin/segment2.c index 1211249cc..7265be0e0 100644 --- a/bin/segment2.c +++ b/bin/segment2.c @@ -3853,6 +3853,9 @@ ALIGNED8 static const u8 texture_font_normal_char_punc_sp_qu[] = { #include "textures/segment2/custom_font_normal_char_punc_sp_qu.ia4.inc.c" }; +ALIGNED8 const u8 texture_font_normal[] = { +#include "textures/custom_font_title/custom_font_normal.rgba32.inc.c" +}; const u8* const font_normal_chars[] = { texture_font_char_us_exclamation, // ! diff --git a/src/pc/djui/djui_font.c b/src/pc/djui/djui_font.c index 01bb024f8..bd1177bae 100644 --- a/src/pc/djui/djui_font.c +++ b/src/pc/djui/djui_font.c @@ -27,9 +27,9 @@ const Gfx dl_font_normal_display_list_begin[] = { const Gfx dl_font_normal_display_list[] = { gsDPLoadBlock(G_TX_LOADTILE, 0, 0, ((16 * 8 + G_IM_SIZ_4b_INCR) >> G_IM_SIZ_4b_SHIFT) - 1, CALC_DXT(16, G_IM_SIZ_4b_BYTES)), - gsSPVertex(djui_font_normal_vertices, 4, 0), + gsSPVertexDjui(djui_font_normal_vertices, 4, 0), gsSPExecuteDjui(G_TEXCLIP_DJUI), - gsSP2Triangles(0, 1, 2, 0x0, 0, 2, 3, 0x0), + gsSP2TrianglesDjui(0, 1, 2, 0x0, 0, 2, 3, 0x0), gsSPEndDisplayList(), }; @@ -39,11 +39,19 @@ static void djui_font_normal_render_char(char* c) { u32 index = djui_unicode_get_sprite_index(c); - extern const u8* const font_normal_chars[]; - void* fontChar = (void*)font_normal_chars[index]; + //extern const u8* const font_normal_chars[]; + //void* fontChar = (void*)font_normal_chars[index]; + extern ALIGNED8 const u8 texture_font_normal[]; + void* fontChar = texture_font_normal; - gDPSetTextureImage(gDisplayListHead++, G_IM_FMT_IA, G_IM_SIZ_16b, 1, (void*)fontChar); - gSPDisplayList(gDisplayListHead++, dl_font_normal_display_list); + //djui_gfx_render_texture(texture_font_normal, 256, 128, 32); + //djui_gfx_render_texture_tile(texture_font_normal, 8, 16, 32, 0, 0, 256, 128); + u32 tx = index % 32; + u32 ty = index / 32; + djui_gfx_render_texture_tile(texture_font_normal, 256, 128, 32, tx * 8, ty * 16, 8, 16); + + //gDPSetTextureImage(gDisplayListHead++, G_IM_FMT_IA, G_IM_SIZ_16b, 1, (void*)fontChar); + //gSPDisplayList(gDisplayListHead++, dl_font_normal_display_list); } static f32 djui_font_normal_char_width(char* c) { @@ -57,8 +65,8 @@ static const struct DjuiFont sDjuiFontNormal = { .charHeight = 1.0f, .lineHeight = 0.8125f, .defaultFontScale = 32.0f, - .rotatedUV = true, - .textBeginDisplayList = dl_font_normal_display_list_begin, + .rotatedUV = false, + .textBeginDisplayList = NULL, //dl_font_normal_display_list_begin, .render_char = djui_font_normal_render_char, .char_width = djui_font_normal_char_width, }; diff --git a/src/pc/djui/djui_gbi.h b/src/pc/djui/djui_gbi.h index d718f9291..e16adf830 100644 --- a/src/pc/djui/djui_gbi.h +++ b/src/pc/djui/djui_gbi.h @@ -2,6 +2,8 @@ #define G_TEXCLIP_DJUI 0xe1 #define G_TEXOVERRIDE_DJUI 0xe0 +#define G_DJUI_SIMPLE_VERT 0x11 +#define G_DJUI_SIMPLE_TRI2 0x12 #define G_EXECUTE_DJUI 0xdd #define gSetClippingDjui(pkt, cmd, rot, x1, y1, x2, y2) \ @@ -20,10 +22,64 @@ _g->words.w1 = (uintptr_t)(texture); \ } +# define gSPVertexDjui(pkt, v, n, v0) \ +{ \ + Gfx *_g = (Gfx *)(pkt); \ + _g->words.w0 = \ + _SHIFTL(G_DJUI_SIMPLE_VERT,24,8)|_SHIFTL((n),12,8)|_SHIFTL((v0)+(n),1,7); \ + _g->words.w1 = (uintptr_t)(v); \ +} + +#define gSP2TrianglesDjui(pkt, v00, v01, v02, flag0, v10, v11, v12, flag1) \ +{ \ + Gfx *_g = (Gfx *)(pkt); \ + \ + _g->words.w0 = (_SHIFTL(G_DJUI_SIMPLE_TRI2, 24, 8)| \ + __gsSP1Triangle_w1f(v00, v01, v02, flag0)); \ + _g->words.w1 = __gsSP1Triangle_w1f(v10, v11, v12, flag1); \ +} + #define gsSPExecuteDjui(word) \ {{ \ _SHIFTL(G_EXECUTE_DJUI, 24, 8), (unsigned int)(word) \ }} +#define gDPLoadTextureBlockWithoutTexture(pkt, timg, fmt, siz, width, height, \ + pal, cms, cmt, masks, maskt, shifts, shiftt) \ +{ \ + gDPSetTile(pkt, fmt, siz##_LOAD_BLOCK, 0, 0, G_TX_LOADTILE, \ + 0 , cmt, maskt, shiftt, cms, masks, shifts); \ + gDPLoadSync(pkt); \ + gDPLoadBlock(pkt, G_TX_LOADTILE, 0, 0, \ + (((width)*(height) + siz##_INCR) >> siz##_SHIFT) -1, \ + CALC_DXT(width, siz##_BYTES)); \ + gDPPipeSync(pkt); \ + gDPSetTile(pkt, fmt, siz, \ + (((width) * siz##_LINE_BYTES)+7)>>3, 0, \ + G_TX_RENDERTILE, pal, cmt, maskt, shiftt, cms, masks, \ + shifts); \ + gDPSetTileSize(pkt, G_TX_RENDERTILE, 0, 0, \ + ((width)-1) << G_TEXTURE_IMAGE_FRAC, \ + ((height)-1) << G_TEXTURE_IMAGE_FRAC) \ +} + #define gDPSetTextureClippingDjui(pkt, rot, x1, y1, x2, y2) gSetClippingDjui(pkt, G_TEXCLIP_DJUI, rot, x1, y1, x2, y2) #define gDPSetTextureOverrideDjui(pkt, texture, w, h, bitSize) gSetOverrideDjui(pkt, G_TEXOVERRIDE_DJUI, texture, w, h, bitSize) + + +// DO NOT COMMIT // + +# define gsSPVertexDjui(v, n, v0) \ +{{ \ + (_SHIFTL(G_DJUI_SIMPLE_VERT,24,8)|_SHIFTL((n),12,8)|_SHIFTL((v0)+(n),1,7)), \ + (uintptr_t)(v) \ +}} + +#define gsSP2TrianglesDjui(v00, v01, v02, flag0, v10, v11, v12, flag1) \ +{{ \ + (_SHIFTL(G_DJUI_SIMPLE_TRI2, 24, 8)| \ + __gsSP1Triangle_w1f(v00, v01, v02, flag0)), \ + __gsSP1Triangle_w1f(v10, v11, v12, flag1) \ +}} + +// DO NOT COMMIT // diff --git a/src/pc/djui/djui_gfx.c b/src/pc/djui/djui_gfx.c index 49f68b852..85b9bb557 100644 --- a/src/pc/djui/djui_gfx.c +++ b/src/pc/djui/djui_gfx.c @@ -84,21 +84,25 @@ void djui_gfx_render_texture(const u8* texture, u32 w, u32 h, u32 bitSize) { void djui_gfx_render_texture_tile(const u8* texture, u32 w, u32 h, u32 bitSize, u32 tileX, u32 tileY, u32 tileW, u32 tileH) { Vtx *vtx = alloc_display_list(sizeof(Vtx) * 4); - vtx[0] = (Vtx) {{{ 0, -1, 0 }, 0, { ( tileX * 512) / w, ((tileY + tileH) * 512) / h }, { 0xff, 0xff, 0xff, 0xff }}}; - vtx[1] = (Vtx) {{{ 1, -1, 0 }, 0, { ((tileX + tileW) * 512) / w, ((tileY + tileH) * 512) / h }, { 0xff, 0xff, 0xff, 0xff }}}; - vtx[2] = (Vtx) {{{ 1, 0, 0 }, 0, { ((tileX + tileW) * 512) / w, ( tileY * 512) / h }, { 0xff, 0xff, 0xff, 0xff }}}; - vtx[3] = (Vtx) {{{ 0, 0, 0 }, 0, { ( tileX * 512) / w, ( tileY * 512) / h }, { 0xff, 0xff, 0xff, 0xff }}}; - gDPSetTextureOverrideDjui(gDisplayListHead++, texture, djui_gfx_power_of_two(w), djui_gfx_power_of_two(h), bitSize); + f32 aspect = w ? ((f32)h / (f32)w) : 1; + vtx[0] = (Vtx) {{{ 0, -1, 0 }, 0, { ( tileX * 512) / w, ((tileY + tileH) * 512) / h }, { 0xff, 0xff, 0xff, 0xff }}}; + vtx[1] = (Vtx) {{{ 1 * aspect, -1, 0 }, 0, { ((tileX + tileW) * 512) / w, ((tileY + tileH) * 512) / h }, { 0xff, 0xff, 0xff, 0xff }}}; + vtx[2] = (Vtx) {{{ 1 * aspect, 0, 0 }, 0, { ((tileX + tileW) * 512) / w, ( tileY * 512) / h }, { 0xff, 0xff, 0xff, 0xff }}}; + vtx[3] = (Vtx) {{{ 0, 0, 0 }, 0, { ( tileX * 512) / w, ( tileY * 512) / h }, { 0xff, 0xff, 0xff, 0xff }}}; + gSPClearGeometryMode(gDisplayListHead++, G_LIGHTING); gDPSetCombineMode(gDisplayListHead++, G_CC_FADEA, G_CC_FADEA); gDPSetRenderMode(gDisplayListHead++, G_RM_XLU_SURF, G_RM_XLU_SURF2); gDPSetTextureFilter(gDisplayListHead++, G_TF_POINT); gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON); - gDPLoadTextureBlock(gDisplayListHead++, NULL, G_IM_FMT_RGBA, G_IM_SIZ_16b, 16, 16, 0, G_TX_CLAMP, G_TX_CLAMP, 5, 5, G_TX_NOLOD, G_TX_NOLOD); + + gDPSetTextureOverrideDjui(gDisplayListHead++, texture, djui_gfx_power_of_two(w), djui_gfx_power_of_two(h), bitSize); + gDPLoadTextureBlockWithoutTexture(gDisplayListHead++, NULL, G_IM_FMT_RGBA, G_IM_SIZ_16b, 16, 16, 0, G_TX_CLAMP, G_TX_CLAMP, 5, 5, G_TX_NOLOD, G_TX_NOLOD); + *(gDisplayListHead++) = (Gfx) gsSPExecuteDjui(G_TEXOVERRIDE_DJUI); - gSPVertex(gDisplayListHead++, vtx, 4, 0); + gSPVertexDjui(gDisplayListHead++, vtx, 4, 0); *(gDisplayListHead++) = (Gfx) gsSPExecuteDjui(G_TEXCLIP_DJUI); - gSP2Triangles(gDisplayListHead++, 0, 1, 2, 0x0, 0, 2, 3, 0x0); + gSP2TrianglesDjui(gDisplayListHead++, 0, 1, 2, 0x0, 0, 2, 3, 0x0); gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_OFF); gDPSetCombineMode(gDisplayListHead++, G_CC_SHADE, G_CC_SHADE); } diff --git a/src/pc/gfx/gfx_pc.c b/src/pc/gfx/gfx_pc.c index f249077a0..4651cc683 100644 --- a/src/pc/gfx/gfx_pc.c +++ b/src/pc/gfx/gfx_pc.c @@ -900,9 +900,7 @@ static void OPTIMIZE_O3 gfx_sp_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t struct LoadedVertex *v2 = &rsp.loaded_vertices[vtx2_idx]; struct LoadedVertex *v3 = &rsp.loaded_vertices[vtx3_idx]; struct LoadedVertex *v_arr[3] = {v1, v2, v3}; - - //if (rand()%2) return; - + if (v1->clip_rej & v2->clip_rej & v3->clip_rej) { // The whole triangle lies outside the visible area return; @@ -1293,9 +1291,9 @@ static void gfx_dp_load_block(uint8_t tile, uint32_t uls, uint32_t ult, uint32_t } uint32_t size_bytes = (lrs + 1) << word_size_shift; rdp.loaded_texture[rdp.texture_to_load.tile_number].size_bytes = size_bytes; + rdp.textures_changed[rdp.texture_to_load.tile_number] = rdp.loaded_texture[rdp.texture_to_load.tile_number].addr != rdp.texture_to_load.addr; rdp.loaded_texture[rdp.texture_to_load.tile_number].addr = rdp.texture_to_load.addr; - rdp.textures_changed[rdp.texture_to_load.tile_number] = true; } static void gfx_dp_load_tile(uint8_t tile, uint32_t uls, uint32_t ult, uint32_t lrs, uint32_t lrt) { @@ -1997,8 +1995,9 @@ static void OPTIMIZE_O3 djui_gfx_dp_execute_override(void) { uint32_t lrs = (sDjuiOverrideW * sDjuiOverrideH) - 1; uint32_t sizeBytes = (lrs + 1) << wordSizeShift; rdp.loaded_texture[rdp.texture_to_load.tile_number].size_bytes = sizeBytes; + rdp.textures_changed[rdp.texture_to_load.tile_number] = rdp.loaded_texture[rdp.texture_to_load.tile_number].addr != rdp.texture_to_load.addr; rdp.loaded_texture[rdp.texture_to_load.tile_number].addr = rdp.texture_to_load.addr; - rdp.textures_changed[rdp.texture_to_load.tile_number] = true; + //rdp.textures_changed[rdp.texture_to_load.tile_number] = true; // gsDPSetTile uint32_t line = (((sDjuiOverrideW * 2) + 7) >> 3); @@ -2009,8 +2008,6 @@ static void OPTIMIZE_O3 djui_gfx_dp_execute_override(void) { rdp.texture_tile.ult = 0; rdp.texture_tile.lrs = (sDjuiOverrideW - 1) << G_TEXTURE_IMAGE_FRAC; rdp.texture_tile.lrt = (sDjuiOverrideH - 1) << G_TEXTURE_IMAGE_FRAC;*/ - rdp.textures_changed[0] = true; - rdp.textures_changed[1] = true; } static void OPTIMIZE_O3 djui_gfx_dp_execute_djui(uint32_t opcode) { @@ -2037,6 +2034,99 @@ static void OPTIMIZE_O3 djui_gfx_dp_set_override(void* texture, uint32_t w, uint sDjuiOverride = (texture != NULL); } +static void OPTIMIZE_O3 djui_gfx_sp_simple_vertex(size_t n_vertices, size_t dest_index, const Vtx *vertices) { + for (size_t i = 0; i < n_vertices; i++, dest_index++) { + const Vtx_t *v = &vertices[i].v; + struct LoadedVertex *d = &rsp.loaded_vertices[dest_index]; + + float x = v->ob[0] * rsp.MP_matrix[0][0] + v->ob[1] * rsp.MP_matrix[1][0] + v->ob[2] * rsp.MP_matrix[2][0] + rsp.MP_matrix[3][0]; + float y = v->ob[0] * rsp.MP_matrix[0][1] + v->ob[1] * rsp.MP_matrix[1][1] + v->ob[2] * rsp.MP_matrix[2][1] + rsp.MP_matrix[3][1]; + float z = v->ob[0] * rsp.MP_matrix[0][2] + v->ob[1] * rsp.MP_matrix[1][2] + v->ob[2] * rsp.MP_matrix[2][2] + rsp.MP_matrix[3][2]; + float w = v->ob[0] * rsp.MP_matrix[0][3] + v->ob[1] * rsp.MP_matrix[1][3] + v->ob[2] * rsp.MP_matrix[2][3] + rsp.MP_matrix[3][3]; + + x = gfx_adjust_x_for_aspect_ratio(x); + + short U = v->tc[0] * rsp.texture_scaling_factor.s >> 16; + short V = v->tc[1] * rsp.texture_scaling_factor.t >> 16; + + d->color.r = v->cn[0]; + d->color.g = v->cn[1]; + d->color.b = v->cn[2]; + + d->u = U; + d->v = V; + + d->x = x; + d->y = y; + d->z = z; + d->w = w; + + d->color.a = v->cn[3]; + } +} + +static void OPTIMIZE_O3 djui_gfx_sp_simple_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx) { + struct LoadedVertex *v1 = &rsp.loaded_vertices[vtx1_idx]; + struct LoadedVertex *v2 = &rsp.loaded_vertices[vtx2_idx]; + struct LoadedVertex *v3 = &rsp.loaded_vertices[vtx3_idx]; + struct LoadedVertex *v_arr[3] = {v1, v2, v3}; + + uint32_t cc_id = rdp.combine_mode; + + bool use_alpha = true; + cc_id |= SHADER_OPT_ALPHA; + + if (!use_alpha) { + cc_id &= ~0xfff000; + } + + struct ColorCombiner *comb = gfx_lookup_or_create_color_combiner(cc_id); + struct ShaderProgram *prg = comb->prg; + if (prg != rendering_state.shader_program) { + gfx_flush(); + gfx_rapi->unload_shader(rendering_state.shader_program); + gfx_rapi->load_shader(prg); + rendering_state.shader_program = prg; + } + + if (rdp.textures_changed[0]) { + gfx_flush(); + import_texture(0); + rdp.textures_changed[0] = false; + } + + uint32_t tex_width = (rdp.texture_tile.lrs - rdp.texture_tile.uls + 4) / 4; + uint32_t tex_height = (rdp.texture_tile.lrt - rdp.texture_tile.ult + 4) / 4; + + bool z_is_from_0_to_1 = gfx_rapi->z_is_from_0_to_1(); + + for (int32_t i = 0; i < 3; i++) { + float z = v_arr[i]->z, w = v_arr[i]->w; + if (z_is_from_0_to_1) { + z = (z + w) / 2.0f; + } + buf_vbo[buf_vbo_len++] = v_arr[i]->x; + buf_vbo[buf_vbo_len++] = v_arr[i]->y; + buf_vbo[buf_vbo_len++] = z; + buf_vbo[buf_vbo_len++] = w; + + float u = (v_arr[i]->u - rdp.texture_tile.uls * 8) / 32.0f; + float v = (v_arr[i]->v - rdp.texture_tile.ult * 8) / 32.0f; + buf_vbo[buf_vbo_len++] = u / tex_width; + buf_vbo[buf_vbo_len++] = v / tex_height; + + struct RGBA *color; + color = &rdp.env_color; + buf_vbo[buf_vbo_len++] = color->r / 255.0f; + buf_vbo[buf_vbo_len++] = color->g / 255.0f; + buf_vbo[buf_vbo_len++] = color->b / 255.0f; + buf_vbo[buf_vbo_len++] = color->a / 255.0f; + } + if (++buf_vbo_num_tris == MAX_BUFFERED) { + gfx_flush(); + } +} + void OPTIMIZE_O3 djui_gfx_run_dl(Gfx* cmd) { uint32_t opcode = cmd->words.w0 >> 24; switch (opcode) { @@ -2046,6 +2136,13 @@ void OPTIMIZE_O3 djui_gfx_run_dl(Gfx* cmd) { case G_TEXOVERRIDE_DJUI: djui_gfx_dp_set_override(seg_addr(cmd->words.w1), 1 << C0(16, 8), 1 << C0(8, 8), C0(0, 8)); break; + case G_DJUI_SIMPLE_VERT: + djui_gfx_sp_simple_vertex(C0(12, 8), C0(1, 7) - C0(12, 8), seg_addr(cmd->words.w1)); + break; + case G_DJUI_SIMPLE_TRI2: + djui_gfx_sp_simple_tri1(C0(16, 8) / 2, C0(8, 8) / 2, C0(0, 8) / 2); + djui_gfx_sp_simple_tri1(C1(16, 8) / 2, C1(8, 8) / 2, C1(0, 8) / 2); + break; case G_EXECUTE_DJUI: djui_gfx_dp_execute_djui(cmd->words.w1); break; diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c index a1ce19f83..7f0a14a06 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -202,7 +202,7 @@ void produce_interpolation_frames_and_delay(void) { sFrameTargetTime += sFrameTime * gGameSpeed; gRenderingInterpolated = false; - //printf(">>> fpt: %llu, fps: %f :: %f\n", frames, sAvgFps, fps); + printf(">>> fpt: %llu, fps: %f :: %f\n", frames, sAvgFps, fps); } void produce_one_frame(void) { diff --git a/textures/custom_font_title/custom_font_normal.rgba32.png b/textures/custom_font_title/custom_font_normal.rgba32.png new file mode 100644 index 0000000000000000000000000000000000000000..43e8a70f7a0d1469f18640c143e92241144295ee GIT binary patch literal 3868 zcmeHK`8N~}_Z~u)ER(X!E)`+MmWi>AQG^zIOd%s{h>*4N$v!4z&oX_=E+!;05@Q>N z5ZOj%OlSrf+c0?hobx^J_x<7h6W(*4d(U&v{q5d+p6A>oTWdH!uLLgu0N}r3VRi!m zVEaP>aok*gGNrNkPlL_>zNxA0eGd-+Km{F&ZngYkdkP<$CenRe`k`&mQpv*b6O}~F zw^B=B1+Tqp{-)AvZAG;kw>d?hKY6;e;JXeTwtHYF!~qiu3fXD2Nc!27AX1?_Qz%3F zMEXJD^9GLg>eq~B3@*~W^(ct+n%(I+Z`c>U%^v=q{Jdh+`r2Mh2WBPOc5Cm*ZFD@( zLZ2&qD&Z{uz!2YnmYELY<$~)EHw9rEZW+Gge&V*f=F5@Uh|_PYSQV3QsY6fRMDC69 zSL?n**JC>|ou*FMJ7d)Ah_BfMH8l(A41+||EDbr<(kaItMH^cmYmo1%WYsQpQV1m% zm?Gb#_tmpYQm9eZHS@;Jva^et=PGB8sLQXF@l~^}CiBrjv#7h?(@%r@LSwCOH%IZR z5g%k%?dvMOtxzAa4E0~)j+%ZD7wyENaEgci^4zi=Xqe^6aoP7o%|=*zRt$5{%pzaikb1P za5s8UTEL9niD%RDYe!vdkq?4SoJydis@$)cEjlLP|MFz4;f*Z6x={pfvzR+E?*G7& zU1u_x6NY|AF()er)G02?NKYNE?=1xb%ZIar@NJy%?&zV|X=1W+hbm~yyRtiaaDR7q zS6b=l1?;1X_}7rgpi=V|mzdvuJ_H&@^9)jJ!VjZg{cVz`1<4S_FY!44-D{LJuX<^2 zNP_ue8E%-SsFhND?%mB~S!Y?Nr?9h9?|Eb+3R+chz%N#@b%M)vy*9o6)ME=ojjOHT zo0r{k=zJG~6)$w4DLWZnl17rFiM0@3<>^a#nQq@YuuQ8UdcT}AVdRbGNuqR%RDIGe zoQ4}LIJX-1$c!(lXSFNLoFn^EglD0bm^?|0>38hTfy_OPkC2*x1<+5pAk6w@vDS88$NoiO`s?zPvR}BSkov<0 zav52@U}nI29|{g!%vBju5nWQG(#=j-hUtP1@YM$`&{s%(OvDHfN*?<*p%4sq$8@&W zEYlR%iGp~eCZp@Wkq-=hwsmcHcLJo6d_8GP>?Uu^ulij_V~Fs4lfQM!q8g&Gk?Sn+ zS;q_+i=i>;(OmOQjfmcj75`TgNCb*)WQ3bOG!bRf!%gs2ja1a-rHkVwTYUt=@ZOgy zrrt$GaypJPtr)qMciu-#Ex@-vUtS4QZ+=8KJkS)RU!FV1cE;66J5{Gdy1Vho+R{Z) zx)J)!HX}xAPn`bD`>?!?NLpf*n*(ESAuZCa6U#4RB?kO#NE~fa9{R%A%y8YyZIXI~ zG~h9O`aPEE6}{_oYewJCskvu}*zPEuzr|;uK#_T&K=n3!p>R-LSYuijp9X@Z=4lV$ zx3tK%$4RGqX{*FtZEUN?p0={`dLk?NNLYAyA!>mFti6EaP~-=)0fqhpdI?6m$+U9< z{enQfBBRGLvXT8SX8Yg_XxquQ&!LKM1Cr+V)nWFlHG$RdnT#bN&crvsz0l%QPKtiHHNh*?Pe1VW;A4e z*Tjq~k57uE+OQL|l07-jeiK89h-#BhJxWx?k-r*T96O zoT@hsYsgQd#uNc+*~SIFNqDuVCmIoY9lA8!af?mKKN1gw@l8R{{Gfmzgp-fH|OmD~_NjZ9J=na56gWs$%$Bc#xz9Ka^u8#?Cov$$UpHq8di` zN9$@$wjOMkWl!82z$fT;x8!nLK z^#f20ajKo=P9uOS2}faqvM!Xcw*1ZAc@31jmvju?OoMNAtW%~>G?_6ZwpLvL@kdeW^hM@4{7x%~ zpMLMQlBic}peNRFYb!xwFF(g3n5D!#4l2Qy>0cT>q`IZ9Hrb zv5adPDpo*8o02`nrx911I;WSrp*8bIDqfp2+BsJM8~lCZlEll@Y%;uFj+yP?b)p|S zY843?6OC_!i}vP?2Qy09d6~o~DSqI+EpM+-m<36NtuCg5OT$M+0wu!hf$TAHZ|53J zy~~R2(Y&y6m_B3fKGe?gJbNOL-qFlvV=?Qq9&k0=-;a{;^ zB}wk(EBB~!)7Xudfl4@)*j`-NZ#x=-Q5C2Uf*e#f8z~6*6TGTqere~H8*gfJuCZyr z^dR^`SU1QQ>1F6rj?u|*Qd{w23XV`a0oh@Xg)B|nj87t=Pq!e~ykO*Rd+{xZ1LcJF&x`BiGlJYVH`8 z)-@+oVKNzw;bPh~;>v!5^oMgRkNn-x{12t-`jIry#EQy}x*U?X8qX{3%b{nV$pynG;zo$;0hh2KDqSQwK_8jrNH9ln5z#C~BQ{Ay+D5N-v zG1VQIGxvAlh1@JgR@9Q!DE1osP1K;s|1Ex zPkHtvYvvr1tz=)Ym7)^gy66^A1|!$cv~=uE+F+rQkW3dXu9}*n9E9LOf^0UqpA$*F z_iC1LLG%8QFVF~q#+AX=Zocz~41UvGd4xDN`N_g-3%uAVg?=O*gd`W_LBTuX!8l0! zHaE0|K<<=pnJFral8E1F!LSjIu@7njkdTCp$L-Bk=M@P6N0({7y=`$-_m=lRM2 zei}Fo^lN5ief3J-I)y-Vqh`-$&F(=Xk3}nda5+4mP^M=#Z^mvD$`kkTM^c>42G8%u zkC;}q)==Z;hY^q%zJ-7mR}W3y^#JqFII2 zvEBS_iJA3KJHyf4GdiN7lg}^7o65_wqm)Xzj~wS3l-d6j{{`#L95Fb1y<}<;Pq=Q% zNV8E|&fwNz^f9XBOFHYSKm!x|@xmc1_vvaE<|hGbeQQerCytx?`C%m7^Xd{u#eotDdt}814?U^R1ol}j+eG;IB%D!nWUELuBKb(txWT+C; zg+dcg>0xlvqZ8+oQ4uC_I^v$>{Jr1)77@1|ab12q( z;-h|`hyC1yGn7c7e^^?xSAW$uKCpiGV8=(x1zO`z#Cy{d$Y}X7CneGNqHE3%quIS@ zBhzTlm0z2!!%D8+*?#EQxPOt6+n5r(ezE%0+y7!w|Msf?k;D!@pl+kxi) PJik|9)@HRPcb@zU-D_?2 literal 0 HcmV?d00001