diff --git a/plugins/GSdx/GSRasterizer.cpp b/plugins/GSdx/GSRasterizer.cpp index 258b913834..7219c9e55c 100644 --- a/plugins/GSdx/GSRasterizer.cpp +++ b/plugins/GSdx/GSRasterizer.cpp @@ -24,6 +24,24 @@ #include "StdAfx.h" #include "GSRasterizer.h" +#include "pthread.h" + +// Using a spinning finish on the main (MTGS) thread is apparently a big win still, over trying +// to wait out all the pending m_finished semaphores. It leaves one spinwait in the rasterizer, +// but that's still worlds better than 2-6 spinning threads like before. +#define UseSpinningFinish 1 + +// Set this to 1 to remove a lot of non-const div/modulus ops from the rasterization process. +// Might likely be a measurable speedup but limits threading to 1, 2, 4, and 8 threads. +#define UseConstThreadCount 0 + +#if !UseConstThreadCount + // ThreadsConst - const number of threads. User-configured threads (in GSdx panel) must match + // this value if UseConstThreadCount is enabled. [yeah, it's hacky for now] + static const int ThreadsConst = 2; + static const int ThreadMaskConst = ThreadsConst-1; +#endif + GSRasterizer::GSRasterizer(IDrawScanline* ds, int id, int threads) : m_ds(ds) , m_id(id) @@ -36,6 +54,15 @@ GSRasterizer::~GSRasterizer() delete m_ds; } +__forceinline bool GSRasterizer::IsOneOfMyScanlines(int scanline) const +{ +#if UseConstThreadCount + return (ThreadMaskConst==0) || ((scanline & ThreadMaskConst) == m_id); +#else + return (scanline % m_threads) == m_id; +#endif +} + void GSRasterizer::Draw(const GSRasterizerData* data) { m_dsf.ssl = NULL; @@ -96,7 +123,7 @@ void GSRasterizer::DrawPoint(const GSVertexSW* v, const GSVector4i& scissor) if(scissor.left <= p.x && p.x < scissor.right && scissor.top <= p.y && p.y < scissor.bottom) { - if((p.y % m_threads) == m_id) + if(IsOneOfMyScanlines(p.y)) { m_dsf.ssp(v, *v); @@ -458,7 +485,7 @@ void GSRasterizer::DrawTriangleSection(int top, int bottom, GSVertexSW& l, const { do { - if((top % m_threads) == m_id) + if(IsOneOfMyScanlines(top)) { GSVector4 lr = l.p.xyxy(r).ceil(); @@ -499,7 +526,7 @@ void GSRasterizer::DrawTriangleSection(int top, int bottom, GSVertexSW& l, const { do { - if((top % m_threads) == m_id) + if(IsOneOfMyScanlines(top)) { GSVector4 lr = l.p.ceil(); @@ -586,7 +613,7 @@ void GSRasterizer::DrawSprite(const GSVertexSW* vertices, const GSVector4i& scis for(; r.top < r.bottom; r.top++, scan.t += dedge.t) { - if((r.top % m_threads) == m_id) + if(IsOneOfMyScanlines(r.top)) { m_dsf.ssl(r.right, r.left, r.top, scan); @@ -661,7 +688,7 @@ void GSRasterizer::DrawEdge(const GSVertexSW& v0, const GSVertexSW& v1, const GS int xi = x >> 16; int xf = x & 0xffff; - if(scissor.left <= xi && xi < scissor.right && (xi % m_threads) == m_id) + if(scissor.left <= xi && xi < scissor.right && IsOneOfMyScanlines(xi)) { m_stats.pixels++; @@ -689,7 +716,7 @@ void GSRasterizer::DrawEdge(const GSVertexSW& v0, const GSVertexSW& v1, const GS int xi = (x >> 16) + 1; int xf = x & 0xffff; - if(scissor.left <= xi && xi < scissor.right && (xi % m_threads) == m_id) + if(scissor.left <= xi && xi < scissor.right && IsOneOfMyScanlines(xi)) { m_stats.pixels++; @@ -759,7 +786,7 @@ void GSRasterizer::DrawEdge(const GSVertexSW& v0, const GSVertexSW& v1, const GS int yi = y >> 16; int yf = y & 0xffff; - if(scissor.top <= yi && yi < scissor.bottom && (yi % m_threads) == m_id) + if(scissor.top <= yi && yi < scissor.bottom && IsOneOfMyScanlines(yi)) { m_stats.pixels++; @@ -787,7 +814,7 @@ void GSRasterizer::DrawEdge(const GSVertexSW& v0, const GSVertexSW& v1, const GS int yi = (y >> 16) + 1; int yf = y & 0xffff; - if(scissor.top <= yi && yi < scissor.bottom && (yi % m_threads) == m_id) + if(scissor.top <= yi && yi < scissor.bottom && IsOneOfMyScanlines(yi)) { m_stats.pixels++; @@ -811,108 +838,108 @@ void GSRasterizer::DrawEdge(const GSVertexSW& v0, const GSVertexSW& v1, const GS // -GSRasterizerMT::GSRasterizerMT(IDrawScanline* ds, int id, int threads, long* sync) +GSRasterizerMT::GSRasterizerMT(IDrawScanline* ds, int id, int threads, sem_t& finished, volatile long& sync) : GSRasterizer(ds, id, threads) + , m_finished(finished) , m_sync(sync) , m_exit(false) , m_data(NULL) { - if(id > 0) - { - CreateThread(); - } + sem_init(&m_semaphore, false, 0); + sem_init(&m_stopped, false, 0); + CreateThread(); } GSRasterizerMT::~GSRasterizerMT() { m_exit = true; + sem_post(&m_semaphore); + sem_wait(&m_stopped); + + sem_destroy(&m_semaphore); + sem_destroy(&m_stopped); } void GSRasterizerMT::Draw(const GSRasterizerData* data) { - if(m_id == 0) - { - __super::Draw(data); - } - else - { - m_data = data; - - _interlockedbittestandset(m_sync, m_id); - } + m_data = data; + sem_post(&m_semaphore); } void GSRasterizerMT::ThreadProc() { // _mm_setcsr(MXCSR); - while(!m_exit) + while( true ) { - if(*m_sync & (1 << m_id)) - { - __super::Draw(m_data); + sem_wait(&m_semaphore); - _interlockedbittestandreset(m_sync, m_id); - } + if(m_exit) break; + + __super::Draw(m_data); + + if( UseSpinningFinish ) + _interlockedbittestandreset( &m_sync, m_id ); else - { - _mm_pause(); - } + sem_post(&m_finished); } + + sem_post(&m_stopped); } // GSRasterizerList::GSRasterizerList() { - // User/Source Coding Rule 24. (M impact, ML generality) Place each - // synchronization variable alone, separated by 128 bytes or in a separate cache - // line. - - m_sync = (long*)_aligned_malloc(128, 64); - - *m_sync = 0; + m_threadcount = 0; + sem_init(&m_finished, false, 0); } GSRasterizerList::~GSRasterizerList() { FreeRasterizers(); - - _aligned_free(m_sync); + sem_destroy(&m_finished); } void GSRasterizerList::FreeRasterizers() { - for_each(begin(), end(), delete_object()); + for(unsigned i=0; i::reverse_iterator i = rbegin(); i != rend(); i++) + m_sync = m_syncstart; + + for(unsigned i=1; iDraw(data); + (*this)[i]->Draw(data); } - while(*m_sync) + (*this)[0]->Draw(data); + + if( UseSpinningFinish ) { - _mm_pause(); + while(m_sync) _mm_pause(); + } + else + { + for(unsigned i=1; i::iterator i = begin(); i != end(); i++) + for(unsigned i=0; iGetStats(s); + (*this)[i]->GetStats(s); m_stats.pixels += s.pixels; m_stats.prims = max(m_stats.prims, s.prims); diff --git a/plugins/GSdx/GSRasterizer.h b/plugins/GSdx/GSRasterizer.h index b1063b45c9..d3aec94779 100644 --- a/plugins/GSdx/GSRasterizer.h +++ b/plugins/GSdx/GSRasterizer.h @@ -27,6 +27,9 @@ #include "GSThread.h" #include "GSAlignedClass.h" +#include "pthread.h" +#include "semaphore.h" + __declspec(align(16)) class GSRasterizerData { public: @@ -93,6 +96,8 @@ protected: void DrawEdge(const GSVertexSW& v0, const GSVertexSW& v1, const GSVertexSW& dv, const GSVector4i& scissor, int orientation, int side); + inline bool IsOneOfMyScanlines(int scanline) const; + public: GSRasterizer(IDrawScanline* ds, int id = 0, int threads = 0); virtual ~GSRasterizer(); @@ -106,14 +111,18 @@ public: class GSRasterizerMT : public GSRasterizer, private GSThread { - long* m_sync; +protected: + sem_t& m_finished; + volatile long& m_sync; + sem_t m_semaphore; + sem_t m_stopped; bool m_exit; const GSRasterizerData* m_data; void ThreadProc(); public: - GSRasterizerMT(IDrawScanline* ds, int id, int threads, long* sync); + GSRasterizerMT(IDrawScanline* ds, int id, int threads, sem_t& finished, volatile long& sync); virtual ~GSRasterizerMT(); // IRasterizer @@ -121,11 +130,14 @@ public: void Draw(const GSRasterizerData* data); }; -class GSRasterizerList : protected list, public IRasterizer +class GSRasterizerList : protected vector, public IRasterizer { - long* m_sync; +protected: + int m_threadcount; + sem_t m_finished; + volatile long m_sync; + long m_syncstart; GSRasterizerStats m_stats; - void FreeRasterizers(); public: @@ -138,9 +150,13 @@ public: threads = max(threads, 1); // TODO: min(threads, number of cpu cores) - for(int i = 0; i < threads; i++) + push_back(new GSRasterizer(new DS(parent, 0), 0, threads)); + + m_syncstart = 0; + for(int i = 1; i < threads; i++) { - push_back(new GSRasterizerMT(new DS(parent, i), i, threads, m_sync)); + push_back(new GSRasterizerMT(new DS(parent, i), i, threads, m_finished, m_sync)); + _interlockedbittestandset(&m_syncstart, i); } } diff --git a/plugins/GSdx/GSdx_vs2008.vcproj b/plugins/GSdx/GSdx_vs2008.vcproj index 4469fba51b..0ac69b195b 100644 --- a/plugins/GSdx/GSdx_vs2008.vcproj +++ b/plugins/GSdx/GSdx_vs2008.vcproj @@ -22,7 +22,7 @@ @@ -83,449 +83,10 @@ Name="VCPostBuildEventTool" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -773,10 +527,74 @@ Name="VCPostBuildEventTool" /> + + + + + + + + + + + + + + + + + + + @@ -836,10 +654,74 @@ Name="VCPostBuildEventTool" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -964,10 +906,70 @@ Name="VCPostBuildEventTool" /> + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - @@ -1837,6 +1799,14 @@ UsePrecompiledHeader="1" /> + + + @@ -1845,6 +1815,14 @@ UsePrecompiledHeader="1" /> + + + @@ -1853,6 +1831,14 @@ UsePrecompiledHeader="1" /> + + + @@ -1861,6 +1847,14 @@ UsePrecompiledHeader="1" /> + + + @@ -1869,6 +1863,14 @@ UsePrecompiledHeader="1" /> + + + @@ -2289,46 +2291,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -2337,6 +2299,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2345,6 +2315,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2353,6 +2331,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2361,6 +2347,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2369,6 +2363,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2409,46 +2411,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -2457,6 +2419,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2465,6 +2435,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2473,6 +2451,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2481,6 +2467,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2489,6 +2483,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2529,46 +2531,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -2577,6 +2539,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2585,6 +2555,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2593,6 +2571,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2601,6 +2587,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2609,6 +2603,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2649,46 +2651,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -2697,6 +2659,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2705,6 +2675,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2713,6 +2691,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2721,6 +2707,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2729,6 +2723,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2769,46 +2771,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -2817,6 +2779,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2825,6 +2795,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2833,6 +2811,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2841,6 +2827,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2849,6 +2843,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2889,46 +2891,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -2937,6 +2899,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2945,6 +2915,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2953,6 +2931,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2961,6 +2947,14 @@ UsePrecompiledHeader="0" /> + + + @@ -2969,6 +2963,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3025,46 +3027,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -3073,6 +3035,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3081,6 +3051,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3089,6 +3067,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3097,6 +3083,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3105,6 +3099,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3145,46 +3147,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -3193,6 +3155,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3201,6 +3171,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3209,6 +3187,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3217,6 +3203,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3225,6 +3219,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3265,46 +3267,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -3313,6 +3275,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3321,6 +3291,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3329,6 +3307,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3337,6 +3323,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3345,6 +3339,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3385,46 +3387,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -3433,6 +3395,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3441,6 +3411,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3449,6 +3427,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3457,6 +3443,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3465,6 +3459,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3505,46 +3507,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -3553,6 +3515,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3561,6 +3531,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3569,6 +3547,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3577,6 +3563,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3585,6 +3579,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3629,46 +3631,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -3677,6 +3639,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3685,6 +3655,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3693,6 +3671,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3701,6 +3687,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3709,6 +3703,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3749,46 +3751,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -3797,6 +3759,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3805,6 +3775,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3813,6 +3791,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3821,6 +3807,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3829,6 +3823,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3869,46 +3871,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -3917,6 +3879,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3925,6 +3895,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3933,6 +3911,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3941,6 +3927,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3949,6 +3943,14 @@ UsePrecompiledHeader="0" /> + + + @@ -3989,46 +3991,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -4037,6 +3999,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4045,6 +4015,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4053,6 +4031,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4061,6 +4047,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4069,6 +4063,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4113,46 +4115,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -4161,6 +4123,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4169,6 +4139,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4177,6 +4155,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4185,6 +4171,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4193,6 +4187,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4233,46 +4235,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -4281,6 +4243,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4289,6 +4259,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4297,6 +4275,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4305,6 +4291,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4313,6 +4307,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4353,46 +4355,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -4401,6 +4363,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4409,6 +4379,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4417,6 +4395,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4425,6 +4411,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4433,6 +4427,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4473,46 +4475,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -4521,6 +4483,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4529,6 +4499,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4537,6 +4515,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4545,6 +4531,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4553,6 +4547,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4593,46 +4595,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -4641,6 +4603,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4649,6 +4619,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4657,6 +4635,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4665,6 +4651,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4673,6 +4667,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4713,46 +4715,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -4761,6 +4723,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4769,6 +4739,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4777,6 +4755,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4785,6 +4771,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4793,6 +4787,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4833,46 +4835,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -4881,6 +4843,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4889,6 +4859,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4897,6 +4875,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4905,6 +4891,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4913,6 +4907,14 @@ UsePrecompiledHeader="0" /> + + + @@ -4953,46 +4955,6 @@ UsePrecompiledHeader="0" /> - - - - - - - - - - - - - - - @@ -5001,6 +4963,14 @@ UsePrecompiledHeader="0" /> + + + @@ -5009,6 +4979,14 @@ UsePrecompiledHeader="0" /> + + + @@ -5017,6 +4995,14 @@ UsePrecompiledHeader="0" /> + + + @@ -5025,6 +5011,14 @@ UsePrecompiledHeader="0" /> + + + @@ -5033,6 +5027,14 @@ UsePrecompiledHeader="0" /> + + +