| File: | Source/Plugins/Plugin_VideoSoftware/Src/SWVertexLoader.cpp |
| Location: | line 313, column 32 |
| Description: | Function call argument is an uninitialized value |
| 1 | // Copyright 2013 Dolphin Emulator Project | |||
| 2 | // Licensed under GPLv2 | |||
| 3 | // Refer to the license.txt file included. | |||
| 4 | ||||
| 5 | #include "Common.h" | |||
| 6 | ||||
| 7 | #include "SWVertexLoader.h" | |||
| 8 | #include "VertexLoader_Position.h" | |||
| 9 | #include "VertexLoader_Normal.h" | |||
| 10 | #include "VertexLoader_Color.h" | |||
| 11 | #include "VertexLoader_TextCoord.h" | |||
| 12 | ||||
| 13 | #include "CPMemLoader.h" | |||
| 14 | #include "XFMemLoader.h" | |||
| 15 | ||||
| 16 | #include "TransformUnit.h" | |||
| 17 | #include "SetupUnit.h" | |||
| 18 | #include "SWStatistics.h" | |||
| 19 | #include "VertexManagerBase.h" | |||
| 20 | #include "DataReader.h" | |||
| 21 | ||||
| 22 | // Vertex loaders read these | |||
| 23 | extern int tcIndex; | |||
| 24 | extern int colIndex; | |||
| 25 | extern int colElements[2]; | |||
| 26 | extern float posScale; | |||
| 27 | extern float tcScale[8]; | |||
| 28 | ||||
| 29 | ||||
| 30 | SWVertexLoader::SWVertexLoader() : | |||
| 31 | m_VertexSize(0), | |||
| 32 | m_NumAttributeLoaders(0) | |||
| 33 | { | |||
| 34 | VertexLoader_Normal::Init(); | |||
| 35 | VertexLoader_Position::Init(); | |||
| 36 | VertexLoader_TextCoord::Init(); | |||
| 37 | ||||
| 38 | m_SetupUnit = new SetupUnit; | |||
| 39 | } | |||
| 40 | ||||
| 41 | SWVertexLoader::~SWVertexLoader() | |||
| 42 | { | |||
| 43 | delete m_SetupUnit; | |||
| 44 | m_SetupUnit = NULL__null; | |||
| 45 | } | |||
| 46 | ||||
| 47 | void SWVertexLoader::SetFormat(u8 attributeIndex, u8 primitiveType) | |||
| 48 | { | |||
| 49 | m_CurrentVat = &g_VtxAttr[attributeIndex]; | |||
| 50 | ||||
| 51 | posScale = 1.0f / float(1 << m_CurrentVat->g0.PosFrac); | |||
| 52 | tcScale[0] = 1.0f / float(1 << m_CurrentVat->g0.Tex0Frac); | |||
| 53 | tcScale[1] = 1.0f / float(1 << m_CurrentVat->g1.Tex1Frac); | |||
| 54 | tcScale[2] = 1.0f / float(1 << m_CurrentVat->g1.Tex2Frac); | |||
| 55 | tcScale[3] = 1.0f / float(1 << m_CurrentVat->g1.Tex3Frac); | |||
| 56 | tcScale[4] = 1.0f / float(1 << m_CurrentVat->g2.Tex4Frac); | |||
| 57 | tcScale[5] = 1.0f / float(1 << m_CurrentVat->g2.Tex5Frac); | |||
| 58 | tcScale[6] = 1.0f / float(1 << m_CurrentVat->g2.Tex6Frac); | |||
| 59 | tcScale[7] = 1.0f / float(1 << m_CurrentVat->g2.Tex7Frac); | |||
| 60 | ||||
| 61 | //TexMtx | |||
| 62 | const u32 tmDesc[8] = { | |||
| 63 | g_VtxDesc.Tex0MatIdx, g_VtxDesc.Tex1MatIdx, g_VtxDesc.Tex2MatIdx, g_VtxDesc.Tex3MatIdx, | |||
| 64 | g_VtxDesc.Tex4MatIdx, g_VtxDesc.Tex5MatIdx, g_VtxDesc.Tex6MatIdx, g_VtxDesc.Tex7MatIdx | |||
| 65 | }; | |||
| 66 | ||||
| 67 | // Colors | |||
| 68 | const u32 colDesc[2] = {g_VtxDesc.Color0, g_VtxDesc.Color1}; | |||
| 69 | colElements[0] = m_CurrentVat->g0.Color0Elements; | |||
| 70 | colElements[1] = m_CurrentVat->g0.Color1Elements; | |||
| 71 | const u32 colComp[2] = {m_CurrentVat->g0.Color0Comp, m_CurrentVat->g0.Color1Comp}; | |||
| 72 | ||||
| 73 | // TextureCoord | |||
| 74 | const u32 tcDesc[8] = { | |||
| 75 | g_VtxDesc.Tex0Coord, g_VtxDesc.Tex1Coord, g_VtxDesc.Tex2Coord, g_VtxDesc.Tex3Coord, | |||
| 76 | g_VtxDesc.Tex4Coord, g_VtxDesc.Tex5Coord, g_VtxDesc.Tex6Coord, (const u32)((g_VtxDesc.Hex >> 31) & 3) | |||
| 77 | }; | |||
| 78 | const u32 tcElements[8] = { | |||
| 79 | m_CurrentVat->g0.Tex0CoordElements, m_CurrentVat->g1.Tex1CoordElements, m_CurrentVat->g1.Tex2CoordElements, | |||
| 80 | m_CurrentVat->g1.Tex3CoordElements, m_CurrentVat->g1.Tex4CoordElements, m_CurrentVat->g2.Tex5CoordElements, | |||
| 81 | m_CurrentVat->g2.Tex6CoordElements, m_CurrentVat->g2.Tex7CoordElements | |||
| 82 | }; | |||
| 83 | ||||
| 84 | const u32 tcFormat[8] = { | |||
| 85 | m_CurrentVat->g0.Tex0CoordFormat, m_CurrentVat->g1.Tex1CoordFormat, m_CurrentVat->g1.Tex2CoordFormat, | |||
| 86 | m_CurrentVat->g1.Tex3CoordFormat, m_CurrentVat->g1.Tex4CoordFormat, m_CurrentVat->g2.Tex5CoordFormat, | |||
| 87 | m_CurrentVat->g2.Tex6CoordFormat, m_CurrentVat->g2.Tex7CoordFormat | |||
| 88 | }; | |||
| 89 | ||||
| 90 | m_VertexSize = 0; | |||
| 91 | ||||
| 92 | // Reset pipeline | |||
| 93 | m_positionLoader = NULL__null; | |||
| 94 | m_normalLoader = NULL__null; | |||
| 95 | m_NumAttributeLoaders = 0; | |||
| 96 | ||||
| 97 | // Reset vertex | |||
| 98 | // matrix index from xf regs or cp memory? | |||
| 99 | if (swxfregs.MatrixIndexA.PosNormalMtxIdx != MatrixIndexA.PosNormalMtxIdx || | |||
| 100 | swxfregs.MatrixIndexA.Tex0MtxIdx != MatrixIndexA.Tex0MtxIdx || | |||
| 101 | swxfregs.MatrixIndexA.Tex1MtxIdx != MatrixIndexA.Tex1MtxIdx || | |||
| 102 | swxfregs.MatrixIndexA.Tex2MtxIdx != MatrixIndexA.Tex2MtxIdx || | |||
| 103 | swxfregs.MatrixIndexA.Tex3MtxIdx != MatrixIndexA.Tex3MtxIdx || | |||
| 104 | swxfregs.MatrixIndexB.Tex4MtxIdx != MatrixIndexB.Tex4MtxIdx || | |||
| 105 | swxfregs.MatrixIndexB.Tex5MtxIdx != MatrixIndexB.Tex5MtxIdx || | |||
| 106 | swxfregs.MatrixIndexB.Tex6MtxIdx != MatrixIndexB.Tex6MtxIdx || | |||
| 107 | swxfregs.MatrixIndexB.Tex7MtxIdx != MatrixIndexB.Tex7MtxIdx) | |||
| 108 | { | |||
| 109 | WARN_LOG(VIDEO, "Matrix indices don't match")do { { if (LogTypes::LWARNING <= 3) GenericLog(LogTypes::LWARNING , LogTypes::VIDEO, "/home/anal/dolphin-emu/Source/Plugins/Plugin_VideoSoftware/Src/SWVertexLoader.cpp" , 109, "Matrix indices don't match"); } } while (0); | |||
| 110 | ||||
| 111 | // Just show the assert once | |||
| 112 | static bool showedAlert = false; | |||
| 113 | _assert_msg_(VIDEO, showedAlert, "Matrix indices don't match")if (!(showedAlert)) { if (!MsgAlert(true, WARNING, "Matrix indices don't match" )) {{asm ("int $3");};} }; | |||
| 114 | showedAlert = true; | |||
| 115 | } | |||
| 116 | ||||
| 117 | #if(1) | |||
| 118 | m_Vertex.posMtx = swxfregs.MatrixIndexA.PosNormalMtxIdx; | |||
| 119 | m_Vertex.texMtx[0] = swxfregs.MatrixIndexA.Tex0MtxIdx; | |||
| 120 | m_Vertex.texMtx[1] = swxfregs.MatrixIndexA.Tex1MtxIdx; | |||
| 121 | m_Vertex.texMtx[2] = swxfregs.MatrixIndexA.Tex2MtxIdx; | |||
| 122 | m_Vertex.texMtx[3] = swxfregs.MatrixIndexA.Tex3MtxIdx; | |||
| 123 | m_Vertex.texMtx[4] = swxfregs.MatrixIndexB.Tex4MtxIdx; | |||
| 124 | m_Vertex.texMtx[5] = swxfregs.MatrixIndexB.Tex5MtxIdx; | |||
| 125 | m_Vertex.texMtx[6] = swxfregs.MatrixIndexB.Tex6MtxIdx; | |||
| 126 | m_Vertex.texMtx[7] = swxfregs.MatrixIndexB.Tex7MtxIdx; | |||
| 127 | #else | |||
| 128 | m_Vertex.posMtx = MatrixIndexA.PosNormalMtxIdx; | |||
| 129 | m_Vertex.texMtx[0] = MatrixIndexA.Tex0MtxIdx; | |||
| 130 | m_Vertex.texMtx[1] = MatrixIndexA.Tex1MtxIdx; | |||
| 131 | m_Vertex.texMtx[2] = MatrixIndexA.Tex2MtxIdx; | |||
| 132 | m_Vertex.texMtx[3] = MatrixIndexA.Tex3MtxIdx; | |||
| 133 | m_Vertex.texMtx[4] = MatrixIndexB.Tex4MtxIdx; | |||
| 134 | m_Vertex.texMtx[5] = MatrixIndexB.Tex5MtxIdx; | |||
| 135 | m_Vertex.texMtx[6] = MatrixIndexB.Tex6MtxIdx; | |||
| 136 | m_Vertex.texMtx[7] = MatrixIndexB.Tex7MtxIdx; | |||
| 137 | #endif | |||
| 138 | ||||
| 139 | if (g_VtxDesc.PosMatIdx != NOT_PRESENT) | |||
| 140 | { | |||
| 141 | AddAttributeLoader(LoadPosMtx); | |||
| 142 | m_VertexSize++; | |||
| 143 | } | |||
| 144 | ||||
| 145 | for (int i = 0; i < 8; ++i) | |||
| 146 | { | |||
| 147 | if (tmDesc[i] != NOT_PRESENT) | |||
| 148 | { | |||
| 149 | AddAttributeLoader(LoadTexMtx, i); | |||
| 150 | m_VertexSize++; | |||
| 151 | } | |||
| 152 | } | |||
| 153 | ||||
| 154 | // Write vertex position loader | |||
| 155 | m_positionLoader = VertexLoader_Position::GetFunction(g_VtxDesc.Position, m_CurrentVat->g0.PosFormat, m_CurrentVat->g0.PosElements); | |||
| 156 | m_VertexSize += VertexLoader_Position::GetSize(g_VtxDesc.Position, m_CurrentVat->g0.PosFormat, m_CurrentVat->g0.PosElements); | |||
| 157 | AddAttributeLoader(LoadPosition); | |||
| 158 | ||||
| 159 | // Normals | |||
| 160 | if (g_VtxDesc.Normal != NOT_PRESENT) | |||
| 161 | { | |||
| 162 | m_VertexSize += VertexLoader_Normal::GetSize(g_VtxDesc.Normal, | |||
| 163 | m_CurrentVat->g0.NormalFormat, m_CurrentVat->g0.NormalElements, m_CurrentVat->g0.NormalIndex3); | |||
| 164 | ||||
| 165 | m_normalLoader = VertexLoader_Normal::GetFunction(g_VtxDesc.Normal, | |||
| 166 | m_CurrentVat->g0.NormalFormat, m_CurrentVat->g0.NormalElements, m_CurrentVat->g0.NormalIndex3); | |||
| 167 | ||||
| 168 | if (m_normalLoader == 0) | |||
| 169 | { | |||
| 170 | ERROR_LOG(VIDEO, "VertexLoader_Normal::GetFunction returned zero!")do { { if (LogTypes::LERROR <= 3) GenericLog(LogTypes::LERROR , LogTypes::VIDEO, "/home/anal/dolphin-emu/Source/Plugins/Plugin_VideoSoftware/Src/SWVertexLoader.cpp" , 170, "VertexLoader_Normal::GetFunction returned zero!"); } } while (0); | |||
| 171 | } | |||
| 172 | AddAttributeLoader(LoadNormal); | |||
| 173 | } | |||
| 174 | ||||
| 175 | for (int i = 0; i < 2; i++) | |||
| 176 | { | |||
| 177 | switch (colDesc[i]) | |||
| 178 | { | |||
| 179 | case NOT_PRESENT: | |||
| 180 | m_colorLoader[i] = NULL__null; | |||
| 181 | break; | |||
| 182 | case DIRECT: | |||
| 183 | switch (colComp[i]) | |||
| 184 | { | |||
| 185 | case FORMAT_16B_565: m_VertexSize += 2; m_colorLoader[i] = (Color_ReadDirect_16b_565); break; | |||
| 186 | case FORMAT_24B_888: m_VertexSize += 3; m_colorLoader[i] = (Color_ReadDirect_24b_888); break; | |||
| 187 | case FORMAT_32B_888x: m_VertexSize += 4; m_colorLoader[i] = (Color_ReadDirect_32b_888x); break; | |||
| 188 | case FORMAT_16B_4444: m_VertexSize += 2; m_colorLoader[i] = (Color_ReadDirect_16b_4444); break; | |||
| 189 | case FORMAT_24B_6666: m_VertexSize += 3; m_colorLoader[i] = (Color_ReadDirect_24b_6666); break; | |||
| 190 | case FORMAT_32B_8888: m_VertexSize += 4; m_colorLoader[i] = (Color_ReadDirect_32b_8888); break; | |||
| 191 | default: _assert_(0){}; break; | |||
| 192 | } | |||
| 193 | AddAttributeLoader(LoadColor, i); | |||
| 194 | break; | |||
| 195 | case INDEX8: | |||
| 196 | m_VertexSize += 1; | |||
| 197 | switch (colComp[i]) | |||
| 198 | { | |||
| 199 | case FORMAT_16B_565: m_colorLoader[i] = (Color_ReadIndex8_16b_565); break; | |||
| 200 | case FORMAT_24B_888: m_colorLoader[i] = (Color_ReadIndex8_24b_888); break; | |||
| 201 | case FORMAT_32B_888x: m_colorLoader[i] = (Color_ReadIndex8_32b_888x); break; | |||
| 202 | case FORMAT_16B_4444: m_colorLoader[i] = (Color_ReadIndex8_16b_4444); break; | |||
| 203 | case FORMAT_24B_6666: m_colorLoader[i] = (Color_ReadIndex8_24b_6666); break; | |||
| 204 | case FORMAT_32B_8888: m_colorLoader[i] = (Color_ReadIndex8_32b_8888); break; | |||
| 205 | default: _assert_(0){}; break; | |||
| 206 | } | |||
| 207 | AddAttributeLoader(LoadColor, i); | |||
| 208 | break; | |||
| 209 | case INDEX16: | |||
| 210 | m_VertexSize += 2; | |||
| 211 | switch (colComp[i]) | |||
| 212 | { | |||
| 213 | case FORMAT_16B_565: m_colorLoader[i] = (Color_ReadIndex16_16b_565); break; | |||
| 214 | case FORMAT_24B_888: m_colorLoader[i] = (Color_ReadIndex16_24b_888); break; | |||
| 215 | case FORMAT_32B_888x: m_colorLoader[i] = (Color_ReadIndex16_32b_888x); break; | |||
| 216 | case FORMAT_16B_4444: m_colorLoader[i] = (Color_ReadIndex16_16b_4444); break; | |||
| 217 | case FORMAT_24B_6666: m_colorLoader[i] = (Color_ReadIndex16_24b_6666); break; | |||
| 218 | case FORMAT_32B_8888: m_colorLoader[i] = (Color_ReadIndex16_32b_8888); break; | |||
| 219 | default: _assert_(0){}; break; | |||
| 220 | } | |||
| 221 | AddAttributeLoader(LoadColor, i); | |||
| 222 | break; | |||
| 223 | } | |||
| 224 | } | |||
| 225 | ||||
| 226 | // Texture matrix indices (remove if corresponding texture coordinate isn't enabled) | |||
| 227 | for (int i = 0; i < 8; i++) | |||
| 228 | { | |||
| 229 | const int desc = tcDesc[i]; | |||
| 230 | const int format = tcFormat[i]; | |||
| 231 | const int elements = tcElements[i]; | |||
| 232 | _assert_msg_(VIDEO, NOT_PRESENT <= desc && desc <= INDEX16, "Invalid texture coordinates description!\n(desc = %d)", desc)if (!(NOT_PRESENT <= desc && desc <= INDEX16)) { if (!MsgAlert(true, WARNING, "Invalid texture coordinates description!\n(desc = %d)" , desc)) {{asm ("int $3");};} }; | |||
| 233 | _assert_msg_(VIDEO, FORMAT_UBYTE <= format && format <= FORMAT_FLOAT, "Invalid texture coordinates format!\n(format = %d)", format)if (!(FORMAT_UBYTE <= format && format <= FORMAT_FLOAT )) { if (!MsgAlert(true, WARNING, "Invalid texture coordinates format!\n(format = %d)" , format)) {{asm ("int $3");};} }; | |||
| 234 | _assert_msg_(VIDEO, 0 <= elements && elements <= 1, "Invalid number of texture coordinates elements!\n(elements = %d)", elements)if (!(0 <= elements && elements <= 1)) { if (!MsgAlert (true, WARNING, "Invalid number of texture coordinates elements!\n(elements = %d)" , elements)) {{asm ("int $3");};} }; | |||
| 235 | ||||
| 236 | m_texCoordLoader[i] = VertexLoader_TextCoord::GetFunction(desc, format, elements); | |||
| 237 | m_VertexSize += VertexLoader_TextCoord::GetSize(desc, format, elements); | |||
| 238 | if (m_texCoordLoader[i]) | |||
| 239 | AddAttributeLoader(LoadTexCoord, i); | |||
| 240 | } | |||
| 241 | ||||
| 242 | // special case if only pos and tex coord 0 and tex coord input is AB11 | |||
| 243 | m_TexGenSpecialCase = | |||
| 244 | ((g_VtxDesc.Hex & 0x60600L) == g_VtxDesc.Hex) && // only pos and tex coord 0 | |||
| 245 | (g_VtxDesc.Tex0Coord != NOT_PRESENT) && | |||
| 246 | (swxfregs.texMtxInfo[0].projection == XF_TEXPROJ_ST0); | |||
| 247 | ||||
| 248 | m_SetupUnit->Init(primitiveType); | |||
| 249 | } | |||
| 250 | ||||
| 251 | ||||
| 252 | void SWVertexLoader::LoadVertex() | |||
| 253 | { | |||
| 254 | for (int i = 0; i < m_NumAttributeLoaders; i++) | |||
| 255 | m_AttributeLoaders[i].loader(this, &m_Vertex, m_AttributeLoaders[i].index); | |||
| 256 | ||||
| 257 | OutputVertexData* outVertex = m_SetupUnit->GetVertex(); | |||
| 258 | ||||
| 259 | // transform input data | |||
| 260 | TransformUnit::TransformPosition(&m_Vertex, outVertex); | |||
| 261 | ||||
| 262 | if (g_VtxDesc.Normal != NOT_PRESENT) | |||
| 263 | { | |||
| 264 | TransformUnit::TransformNormal(&m_Vertex, m_CurrentVat->g0.NormalElements, outVertex); | |||
| 265 | } | |||
| 266 | ||||
| 267 | TransformUnit::TransformColor(&m_Vertex, outVertex); | |||
| 268 | ||||
| 269 | TransformUnit::TransformTexCoord(&m_Vertex, outVertex, m_TexGenSpecialCase); | |||
| 270 | ||||
| 271 | m_SetupUnit->SetupVertex(); | |||
| 272 | ||||
| 273 | INCSTAT(swstats.thisFrame.numVerticesLoaded)(swstats.thisFrame.numVerticesLoaded)++; | |||
| 274 | } | |||
| 275 | ||||
| 276 | void SWVertexLoader::AddAttributeLoader(AttributeLoader loader, u8 index) | |||
| 277 | { | |||
| 278 | _assert_msg_(VIDEO, m_NumAttributeLoaders < 21, "Too many attribute loaders")if (!(m_NumAttributeLoaders < 21)) { if (!MsgAlert(true, WARNING , "Too many attribute loaders")) {{asm ("int $3");};} }; | |||
| 279 | m_AttributeLoaders[m_NumAttributeLoaders].loader = loader; | |||
| 280 | m_AttributeLoaders[m_NumAttributeLoaders++].index = index; | |||
| 281 | } | |||
| 282 | ||||
| 283 | void SWVertexLoader::LoadPosMtx(SWVertexLoader *vertexLoader, InputVertexData *vertex, u8 unused) | |||
| 284 | { | |||
| 285 | vertex->posMtx = DataReadU8() & 0x3f; | |||
| 286 | } | |||
| 287 | ||||
| 288 | void SWVertexLoader::LoadTexMtx(SWVertexLoader *vertexLoader, InputVertexData *vertex, u8 index) | |||
| 289 | { | |||
| 290 | vertex->texMtx[index] = DataReadU8() & 0x3f; | |||
| 291 | } | |||
| 292 | ||||
| 293 | void SWVertexLoader::LoadPosition(SWVertexLoader *vertexLoader, InputVertexData *vertex, u8 unused) | |||
| 294 | { | |||
| 295 | VertexManager::s_pCurBufferPointer = (u8*)&vertex->position; | |||
| 296 | vertexLoader->m_positionLoader(); | |||
| 297 | } | |||
| 298 | ||||
| 299 | void SWVertexLoader::LoadNormal(SWVertexLoader *vertexLoader, InputVertexData *vertex, u8 unused) | |||
| 300 | { | |||
| 301 | VertexManager::s_pCurBufferPointer = (u8*)&vertex->normal; | |||
| 302 | vertexLoader->m_normalLoader(); | |||
| 303 | } | |||
| 304 | ||||
| 305 | void SWVertexLoader::LoadColor(SWVertexLoader *vertexLoader, InputVertexData *vertex, u8 index) | |||
| 306 | { | |||
| 307 | u32 color; | |||
| ||||
| 308 | VertexManager::s_pCurBufferPointer = (u8*)&color; | |||
| 309 | colIndex = index; | |||
| 310 | vertexLoader->m_colorLoader[index](); | |||
| 311 | ||||
| 312 | // rgba -> abgr | |||
| 313 | *(u32*)vertex->color[index] = Common::swap32(color); | |||
| ||||
| 314 | } | |||
| 315 | ||||
| 316 | void SWVertexLoader::LoadTexCoord(SWVertexLoader *vertexLoader, InputVertexData *vertex, u8 index) | |||
| 317 | { | |||
| 318 | VertexManager::s_pCurBufferPointer = (u8*)&vertex->texCoords[index]; | |||
| 319 | tcIndex = index; | |||
| 320 | vertexLoader->m_texCoordLoader[index](); | |||
| 321 | } | |||
| 322 | ||||
| 323 | void SWVertexLoader::DoState(PointerWrap &p) | |||
| 324 | { | |||
| 325 | p.DoArray(m_AttributeLoaders, sizeof m_AttributeLoaders); | |||
| 326 | p.Do(m_VertexSize); | |||
| 327 | p.Do(*m_CurrentVat); | |||
| 328 | p.Do(m_positionLoader); | |||
| 329 | p.Do(m_normalLoader); | |||
| 330 | p.DoArray(m_colorLoader, sizeof m_colorLoader); | |||
| 331 | p.Do(m_NumAttributeLoaders); | |||
| 332 | m_SetupUnit->DoState(p); | |||
| 333 | p.Do(m_TexGenSpecialCase); | |||
| 334 | } |