1bf215546Sopenharmony_ci/* 2bf215546Sopenharmony_ci * Mesa 3-D graphics library 3bf215546Sopenharmony_ci * 4bf215546Sopenharmony_ci * Copyright (C) 1999-2008 Brian Paul All Rights Reserved. 5bf215546Sopenharmony_ci * 6bf215546Sopenharmony_ci * Permission is hereby granted, free of charge, to any person obtaining a 7bf215546Sopenharmony_ci * copy of this software and associated documentation files (the "Software"), 8bf215546Sopenharmony_ci * to deal in the Software without restriction, including without limitation 9bf215546Sopenharmony_ci * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10bf215546Sopenharmony_ci * and/or sell copies of the Software, and to permit persons to whom the 11bf215546Sopenharmony_ci * Software is furnished to do so, subject to the following conditions: 12bf215546Sopenharmony_ci * 13bf215546Sopenharmony_ci * The above copyright notice and this permission notice shall be included 14bf215546Sopenharmony_ci * in all copies or substantial portions of the Software. 15bf215546Sopenharmony_ci * 16bf215546Sopenharmony_ci * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17bf215546Sopenharmony_ci * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18bf215546Sopenharmony_ci * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19bf215546Sopenharmony_ci * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20bf215546Sopenharmony_ci * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21bf215546Sopenharmony_ci * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22bf215546Sopenharmony_ci * OTHER DEALINGS IN THE SOFTWARE. 23bf215546Sopenharmony_ci */ 24bf215546Sopenharmony_ci 25bf215546Sopenharmony_ci/* Author: 26bf215546Sopenharmony_ci * Keith Whitwell <keithw@vmware.com> 27bf215546Sopenharmony_ci */ 28bf215546Sopenharmony_ci 29bf215546Sopenharmony_ci#include <stdbool.h> 30bf215546Sopenharmony_ci#include "main/arrayobj.h" 31bf215546Sopenharmony_ci#include "main/glheader.h" 32bf215546Sopenharmony_ci#include "main/bufferobj.h" 33bf215546Sopenharmony_ci#include "main/context.h" 34bf215546Sopenharmony_ci#include "main/enable.h" 35bf215546Sopenharmony_ci#include "main/mesa_private.h" 36bf215546Sopenharmony_ci#include "main/macros.h" 37bf215546Sopenharmony_ci#include "main/light.h" 38bf215546Sopenharmony_ci#include "main/state.h" 39bf215546Sopenharmony_ci#include "main/varray.h" 40bf215546Sopenharmony_ci#include "util/bitscan.h" 41bf215546Sopenharmony_ci 42bf215546Sopenharmony_ci#include "vbo_private.h" 43bf215546Sopenharmony_ci 44bf215546Sopenharmony_cistatic void 45bf215546Sopenharmony_cicopy_vao(struct gl_context *ctx, const struct gl_vertex_array_object *vao, 46bf215546Sopenharmony_ci GLbitfield mask, GLbitfield state, GLbitfield pop_state, 47bf215546Sopenharmony_ci int shift, fi_type **data, bool *color0_changed) 48bf215546Sopenharmony_ci{ 49bf215546Sopenharmony_ci struct vbo_context *vbo = vbo_context(ctx); 50bf215546Sopenharmony_ci 51bf215546Sopenharmony_ci mask &= vao->Enabled; 52bf215546Sopenharmony_ci while (mask) { 53bf215546Sopenharmony_ci const int i = u_bit_scan(&mask); 54bf215546Sopenharmony_ci const struct gl_array_attributes *attrib = &vao->VertexAttrib[i]; 55bf215546Sopenharmony_ci unsigned current_index = shift + i; 56bf215546Sopenharmony_ci struct gl_array_attributes *currval = &vbo->current[current_index]; 57bf215546Sopenharmony_ci const GLubyte size = attrib->Format.Size; 58bf215546Sopenharmony_ci const GLenum16 type = attrib->Format.Type; 59bf215546Sopenharmony_ci fi_type tmp[8]; 60bf215546Sopenharmony_ci int dmul_shift = 0; 61bf215546Sopenharmony_ci 62bf215546Sopenharmony_ci if (type == GL_DOUBLE || 63bf215546Sopenharmony_ci type == GL_UNSIGNED_INT64_ARB) { 64bf215546Sopenharmony_ci dmul_shift = 1; 65bf215546Sopenharmony_ci memcpy(tmp, *data, size * 2 * sizeof(GLfloat)); 66bf215546Sopenharmony_ci } else { 67bf215546Sopenharmony_ci COPY_CLEAN_4V_TYPE_AS_UNION(tmp, size, *data, type); 68bf215546Sopenharmony_ci } 69bf215546Sopenharmony_ci 70bf215546Sopenharmony_ci if (memcmp(currval->Ptr, tmp, 4 * sizeof(GLfloat) << dmul_shift) != 0) { 71bf215546Sopenharmony_ci memcpy((fi_type*)currval->Ptr, tmp, 4 * sizeof(GLfloat) << dmul_shift); 72bf215546Sopenharmony_ci 73bf215546Sopenharmony_ci if (current_index == VBO_ATTRIB_COLOR0) 74bf215546Sopenharmony_ci *color0_changed = true; 75bf215546Sopenharmony_ci 76bf215546Sopenharmony_ci /* The fixed-func vertex program uses this. */ 77bf215546Sopenharmony_ci if (current_index == VBO_ATTRIB_MAT_FRONT_SHININESS || 78bf215546Sopenharmony_ci current_index == VBO_ATTRIB_MAT_BACK_SHININESS) 79bf215546Sopenharmony_ci ctx->NewState |= _NEW_FF_VERT_PROGRAM; 80bf215546Sopenharmony_ci 81bf215546Sopenharmony_ci ctx->NewState |= state; 82bf215546Sopenharmony_ci ctx->PopAttribState |= pop_state; 83bf215546Sopenharmony_ci } 84bf215546Sopenharmony_ci 85bf215546Sopenharmony_ci if (type != currval->Format.Type || 86bf215546Sopenharmony_ci (size >> dmul_shift) != currval->Format.Size) 87bf215546Sopenharmony_ci vbo_set_vertex_format(&currval->Format, size >> dmul_shift, type); 88bf215546Sopenharmony_ci 89bf215546Sopenharmony_ci *data += size; 90bf215546Sopenharmony_ci } 91bf215546Sopenharmony_ci} 92bf215546Sopenharmony_ci 93bf215546Sopenharmony_ci/** 94bf215546Sopenharmony_ci * After playback, copy everything but the position from the 95bf215546Sopenharmony_ci * last vertex to the saved state 96bf215546Sopenharmony_ci */ 97bf215546Sopenharmony_cistatic void 98bf215546Sopenharmony_ciplayback_copy_to_current(struct gl_context *ctx, 99bf215546Sopenharmony_ci const struct vbo_save_vertex_list *node) 100bf215546Sopenharmony_ci{ 101bf215546Sopenharmony_ci if (!node->cold->current_data) 102bf215546Sopenharmony_ci return; 103bf215546Sopenharmony_ci 104bf215546Sopenharmony_ci fi_type *data = node->cold->current_data; 105bf215546Sopenharmony_ci bool color0_changed = false; 106bf215546Sopenharmony_ci 107bf215546Sopenharmony_ci /* Copy conventional attribs and generics except pos */ 108bf215546Sopenharmony_ci copy_vao(ctx, node->cold->VAO[VP_MODE_SHADER], ~VERT_BIT_POS & VERT_BIT_ALL, 109bf215546Sopenharmony_ci _NEW_CURRENT_ATTRIB, GL_CURRENT_BIT, 0, &data, &color0_changed); 110bf215546Sopenharmony_ci /* Copy materials */ 111bf215546Sopenharmony_ci copy_vao(ctx, node->cold->VAO[VP_MODE_FF], VERT_BIT_MAT_ALL, 112bf215546Sopenharmony_ci _NEW_MATERIAL, GL_LIGHTING_BIT, 113bf215546Sopenharmony_ci VBO_MATERIAL_SHIFT, &data, &color0_changed); 114bf215546Sopenharmony_ci 115bf215546Sopenharmony_ci if (color0_changed && ctx->Light.ColorMaterialEnabled) { 116bf215546Sopenharmony_ci _mesa_update_color_material(ctx, ctx->Current.Attrib[VBO_ATTRIB_COLOR0]); 117bf215546Sopenharmony_ci } 118bf215546Sopenharmony_ci 119bf215546Sopenharmony_ci /* CurrentExecPrimitive 120bf215546Sopenharmony_ci */ 121bf215546Sopenharmony_ci if (node->cold->prim_count) { 122bf215546Sopenharmony_ci const struct _mesa_prim *prim = &node->cold->prims[node->cold->prim_count - 1]; 123bf215546Sopenharmony_ci if (prim->end) 124bf215546Sopenharmony_ci ctx->Driver.CurrentExecPrimitive = PRIM_OUTSIDE_BEGIN_END; 125bf215546Sopenharmony_ci else 126bf215546Sopenharmony_ci ctx->Driver.CurrentExecPrimitive = prim->mode; 127bf215546Sopenharmony_ci } 128bf215546Sopenharmony_ci} 129bf215546Sopenharmony_ci 130bf215546Sopenharmony_ci 131bf215546Sopenharmony_ci 132bf215546Sopenharmony_ci/** 133bf215546Sopenharmony_ci * Set the appropriate VAO to draw. 134bf215546Sopenharmony_ci */ 135bf215546Sopenharmony_cistatic void 136bf215546Sopenharmony_cibind_vertex_list(struct gl_context *ctx, 137bf215546Sopenharmony_ci const struct vbo_save_vertex_list *node) 138bf215546Sopenharmony_ci{ 139bf215546Sopenharmony_ci const gl_vertex_processing_mode mode = ctx->VertexProgram._VPMode; 140bf215546Sopenharmony_ci _mesa_set_draw_vao(ctx, node->cold->VAO[mode], _vbo_get_vao_filter(mode)); 141bf215546Sopenharmony_ci} 142bf215546Sopenharmony_ci 143bf215546Sopenharmony_ci 144bf215546Sopenharmony_cistatic void 145bf215546Sopenharmony_ciloopback_vertex_list(struct gl_context *ctx, 146bf215546Sopenharmony_ci const struct vbo_save_vertex_list *list) 147bf215546Sopenharmony_ci{ 148bf215546Sopenharmony_ci struct gl_buffer_object *bo = list->cold->VAO[0]->BufferBinding[0].BufferObj; 149bf215546Sopenharmony_ci void *buffer = _mesa_bufferobj_map_range(ctx, 0, bo->Size, GL_MAP_READ_BIT, /* ? */ 150bf215546Sopenharmony_ci bo, MAP_INTERNAL); 151bf215546Sopenharmony_ci 152bf215546Sopenharmony_ci /* TODO: in this case, we shouldn't create a bo at all and instead keep 153bf215546Sopenharmony_ci * the in-RAM buffer. */ 154bf215546Sopenharmony_ci _vbo_loopback_vertex_list(ctx, list, buffer); 155bf215546Sopenharmony_ci 156bf215546Sopenharmony_ci _mesa_bufferobj_unmap(ctx, bo, MAP_INTERNAL); 157bf215546Sopenharmony_ci} 158bf215546Sopenharmony_ci 159bf215546Sopenharmony_ci 160bf215546Sopenharmony_civoid 161bf215546Sopenharmony_civbo_save_playback_vertex_list_loopback(struct gl_context *ctx, void *data) 162bf215546Sopenharmony_ci{ 163bf215546Sopenharmony_ci const struct vbo_save_vertex_list *node = 164bf215546Sopenharmony_ci (const struct vbo_save_vertex_list *) data; 165bf215546Sopenharmony_ci 166bf215546Sopenharmony_ci FLUSH_FOR_DRAW(ctx); 167bf215546Sopenharmony_ci 168bf215546Sopenharmony_ci if (_mesa_inside_begin_end(ctx) && node->draw_begins) { 169bf215546Sopenharmony_ci /* Error: we're about to begin a new primitive but we're already 170bf215546Sopenharmony_ci * inside a glBegin/End pair. 171bf215546Sopenharmony_ci */ 172bf215546Sopenharmony_ci _mesa_error(ctx, GL_INVALID_OPERATION, 173bf215546Sopenharmony_ci "draw operation inside glBegin/End"); 174bf215546Sopenharmony_ci return; 175bf215546Sopenharmony_ci } 176bf215546Sopenharmony_ci /* Various degenerate cases: translate into immediate mode 177bf215546Sopenharmony_ci * calls rather than trying to execute in place. 178bf215546Sopenharmony_ci */ 179bf215546Sopenharmony_ci loopback_vertex_list(ctx, node); 180bf215546Sopenharmony_ci} 181bf215546Sopenharmony_ci 182bf215546Sopenharmony_cienum vbo_save_status { 183bf215546Sopenharmony_ci DONE, 184bf215546Sopenharmony_ci USE_SLOW_PATH, 185bf215546Sopenharmony_ci}; 186bf215546Sopenharmony_ci 187bf215546Sopenharmony_cistatic enum vbo_save_status 188bf215546Sopenharmony_civbo_save_playback_vertex_list_gallium(struct gl_context *ctx, 189bf215546Sopenharmony_ci const struct vbo_save_vertex_list *node, 190bf215546Sopenharmony_ci bool copy_to_current) 191bf215546Sopenharmony_ci{ 192bf215546Sopenharmony_ci /* Don't use this if selection or feedback mode is enabled. st/mesa can't 193bf215546Sopenharmony_ci * handle it. 194bf215546Sopenharmony_ci */ 195bf215546Sopenharmony_ci if (!ctx->Driver.DrawGalliumVertexState || ctx->RenderMode != GL_RENDER) 196bf215546Sopenharmony_ci return USE_SLOW_PATH; 197bf215546Sopenharmony_ci 198bf215546Sopenharmony_ci const gl_vertex_processing_mode mode = ctx->VertexProgram._VPMode; 199bf215546Sopenharmony_ci 200bf215546Sopenharmony_ci /* This sets which vertex arrays are enabled, which determines 201bf215546Sopenharmony_ci * which attribs have stride = 0 and whether edge flags are enabled. 202bf215546Sopenharmony_ci */ 203bf215546Sopenharmony_ci const GLbitfield enabled = node->enabled_attribs[mode]; 204bf215546Sopenharmony_ci ctx->Array._DrawVAOEnabledAttribs = enabled; 205bf215546Sopenharmony_ci _mesa_set_varying_vp_inputs(ctx, enabled); 206bf215546Sopenharmony_ci 207bf215546Sopenharmony_ci if (ctx->NewState) 208bf215546Sopenharmony_ci _mesa_update_state(ctx); 209bf215546Sopenharmony_ci 210bf215546Sopenharmony_ci /* Return precomputed GL errors such as invalid shaders. */ 211bf215546Sopenharmony_ci if (!ctx->ValidPrimMask) { 212bf215546Sopenharmony_ci _mesa_error(ctx, ctx->DrawGLError, "glCallList"); 213bf215546Sopenharmony_ci return DONE; 214bf215546Sopenharmony_ci } 215bf215546Sopenharmony_ci 216bf215546Sopenharmony_ci /* Use the slow path when there are vertex inputs without vertex 217bf215546Sopenharmony_ci * elements. This happens with zero-stride attribs and non-fixed-func 218bf215546Sopenharmony_ci * shaders. 219bf215546Sopenharmony_ci * 220bf215546Sopenharmony_ci * Dual-slot inputs are also unsupported because the higher slot is 221bf215546Sopenharmony_ci * always missing in vertex elements. 222bf215546Sopenharmony_ci * 223bf215546Sopenharmony_ci * TODO: Add support for zero-stride attribs. 224bf215546Sopenharmony_ci */ 225bf215546Sopenharmony_ci struct gl_program *vp = ctx->VertexProgram._Current; 226bf215546Sopenharmony_ci 227bf215546Sopenharmony_ci if (vp->info.inputs_read & ~enabled || vp->DualSlotInputs) 228bf215546Sopenharmony_ci return USE_SLOW_PATH; 229bf215546Sopenharmony_ci 230bf215546Sopenharmony_ci struct pipe_vertex_state *state = node->state[mode]; 231bf215546Sopenharmony_ci struct pipe_draw_vertex_state_info info; 232bf215546Sopenharmony_ci 233bf215546Sopenharmony_ci info.mode = node->mode; 234bf215546Sopenharmony_ci info.take_vertex_state_ownership = false; 235bf215546Sopenharmony_ci 236bf215546Sopenharmony_ci if (node->ctx == ctx) { 237bf215546Sopenharmony_ci /* This mechanism allows passing references to the driver without 238bf215546Sopenharmony_ci * using atomics to increase the reference count. 239bf215546Sopenharmony_ci * 240bf215546Sopenharmony_ci * This private refcount can be decremented without atomics but only 241bf215546Sopenharmony_ci * one context (ctx above) can use this counter (so that it's only 242bf215546Sopenharmony_ci * used by 1 thread). 243bf215546Sopenharmony_ci * 244bf215546Sopenharmony_ci * This number is atomically added to reference.count at 245bf215546Sopenharmony_ci * initialization. If it's never used, the same number is atomically 246bf215546Sopenharmony_ci * subtracted from reference.count before destruction. If this number 247bf215546Sopenharmony_ci * is decremented, we can pass one reference to the driver without 248bf215546Sopenharmony_ci * touching reference.count with atomics. At destruction we only 249bf215546Sopenharmony_ci * subtract the number of references we have not returned. This can 250bf215546Sopenharmony_ci * possibly turn a million atomic increments into 1 add and 1 subtract 251bf215546Sopenharmony_ci * atomic op over the whole lifetime of an app. 252bf215546Sopenharmony_ci */ 253bf215546Sopenharmony_ci int16_t * const private_refcount = (int16_t*)&node->private_refcount[mode]; 254bf215546Sopenharmony_ci assert(*private_refcount >= 0); 255bf215546Sopenharmony_ci 256bf215546Sopenharmony_ci if (unlikely(*private_refcount == 0)) { 257bf215546Sopenharmony_ci /* pipe_vertex_state can be reused through util_vertex_state_cache, 258bf215546Sopenharmony_ci * and there can be many display lists over-incrementing this number, 259bf215546Sopenharmony_ci * causing it to overflow. 260bf215546Sopenharmony_ci * 261bf215546Sopenharmony_ci * Guess that the same state can never be used by N=500000 display 262bf215546Sopenharmony_ci * lists, so one display list can only increment it by 263bf215546Sopenharmony_ci * INT_MAX / N. 264bf215546Sopenharmony_ci */ 265bf215546Sopenharmony_ci const int16_t add_refs = INT_MAX / 500000; 266bf215546Sopenharmony_ci p_atomic_add(&state->reference.count, add_refs); 267bf215546Sopenharmony_ci *private_refcount = add_refs; 268bf215546Sopenharmony_ci } 269bf215546Sopenharmony_ci 270bf215546Sopenharmony_ci (*private_refcount)--; 271bf215546Sopenharmony_ci info.take_vertex_state_ownership = true; 272bf215546Sopenharmony_ci } 273bf215546Sopenharmony_ci 274bf215546Sopenharmony_ci /* Fast path using a pre-built gallium vertex buffer state. */ 275bf215546Sopenharmony_ci if (node->modes || node->num_draws > 1) { 276bf215546Sopenharmony_ci ctx->Driver.DrawGalliumVertexState(ctx, state, info, 277bf215546Sopenharmony_ci node->start_counts, 278bf215546Sopenharmony_ci node->modes, 279bf215546Sopenharmony_ci node->num_draws, 280bf215546Sopenharmony_ci enabled & VERT_ATTRIB_EDGEFLAG); 281bf215546Sopenharmony_ci } else if (node->num_draws) { 282bf215546Sopenharmony_ci ctx->Driver.DrawGalliumVertexState(ctx, state, info, 283bf215546Sopenharmony_ci &node->start_count, 284bf215546Sopenharmony_ci NULL, 1, 285bf215546Sopenharmony_ci enabled & VERT_ATTRIB_EDGEFLAG); 286bf215546Sopenharmony_ci } 287bf215546Sopenharmony_ci 288bf215546Sopenharmony_ci if (copy_to_current) 289bf215546Sopenharmony_ci playback_copy_to_current(ctx, node); 290bf215546Sopenharmony_ci return DONE; 291bf215546Sopenharmony_ci} 292bf215546Sopenharmony_ci 293bf215546Sopenharmony_ci/** 294bf215546Sopenharmony_ci * Execute the buffer and save copied verts. 295bf215546Sopenharmony_ci * This is called from the display list code when executing 296bf215546Sopenharmony_ci * a drawing command. 297bf215546Sopenharmony_ci */ 298bf215546Sopenharmony_civoid 299bf215546Sopenharmony_civbo_save_playback_vertex_list(struct gl_context *ctx, void *data, bool copy_to_current) 300bf215546Sopenharmony_ci{ 301bf215546Sopenharmony_ci const struct vbo_save_vertex_list *node = 302bf215546Sopenharmony_ci (const struct vbo_save_vertex_list *) data; 303bf215546Sopenharmony_ci 304bf215546Sopenharmony_ci FLUSH_FOR_DRAW(ctx); 305bf215546Sopenharmony_ci 306bf215546Sopenharmony_ci if (_mesa_inside_begin_end(ctx) && node->draw_begins) { 307bf215546Sopenharmony_ci /* Error: we're about to begin a new primitive but we're already 308bf215546Sopenharmony_ci * inside a glBegin/End pair. 309bf215546Sopenharmony_ci */ 310bf215546Sopenharmony_ci _mesa_error(ctx, GL_INVALID_OPERATION, 311bf215546Sopenharmony_ci "draw operation inside glBegin/End"); 312bf215546Sopenharmony_ci return; 313bf215546Sopenharmony_ci } 314bf215546Sopenharmony_ci 315bf215546Sopenharmony_ci if (vbo_save_playback_vertex_list_gallium(ctx, node, copy_to_current) == DONE) 316bf215546Sopenharmony_ci return; 317bf215546Sopenharmony_ci 318bf215546Sopenharmony_ci bind_vertex_list(ctx, node); 319bf215546Sopenharmony_ci 320bf215546Sopenharmony_ci /* Need that at least one time. */ 321bf215546Sopenharmony_ci if (ctx->NewState) 322bf215546Sopenharmony_ci _mesa_update_state(ctx); 323bf215546Sopenharmony_ci 324bf215546Sopenharmony_ci /* Return precomputed GL errors such as invalid shaders. */ 325bf215546Sopenharmony_ci if (!ctx->ValidPrimMask) { 326bf215546Sopenharmony_ci _mesa_error(ctx, ctx->DrawGLError, "glCallList"); 327bf215546Sopenharmony_ci return; 328bf215546Sopenharmony_ci } 329bf215546Sopenharmony_ci 330bf215546Sopenharmony_ci assert(ctx->NewState == 0); 331bf215546Sopenharmony_ci 332bf215546Sopenharmony_ci struct pipe_draw_info *info = (struct pipe_draw_info *) &node->cold->info; 333bf215546Sopenharmony_ci void *gl_bo = info->index.gl_bo; 334bf215546Sopenharmony_ci if (node->modes) { 335bf215546Sopenharmony_ci ctx->Driver.DrawGalliumMultiMode(ctx, info, 336bf215546Sopenharmony_ci node->start_counts, 337bf215546Sopenharmony_ci node->modes, 338bf215546Sopenharmony_ci node->num_draws); 339bf215546Sopenharmony_ci } else if (node->num_draws == 1) { 340bf215546Sopenharmony_ci ctx->Driver.DrawGallium(ctx, info, 0, &node->start_count, 1); 341bf215546Sopenharmony_ci } else if (node->num_draws) { 342bf215546Sopenharmony_ci ctx->Driver.DrawGallium(ctx, info, 0, node->start_counts, 343bf215546Sopenharmony_ci node->num_draws); 344bf215546Sopenharmony_ci } 345bf215546Sopenharmony_ci info->index.gl_bo = gl_bo; 346bf215546Sopenharmony_ci 347bf215546Sopenharmony_ci if (copy_to_current) 348bf215546Sopenharmony_ci playback_copy_to_current(ctx, node); 349bf215546Sopenharmony_ci} 350