View Full Version : چند نکته که باید به هنگام برنامه نویسی بازی به اون ها توجه بشه!
hi.alir
جمعه 13 خرداد 1390, 19:33 عصر
اینجا به چند نکته و اشتباهاتی که ممکنه به دلیل ندونستن اونا اتفاق بیافته اشاره میشه که باید موقع برنامه نویسی بازی به اون ها توجه کرد. شما هم اگر نکته ای چیزی هست بگید که دیگران بدونند.
Memory Alignment ( تراز حافظه ) (http://barnamenevis.org/showthread.php?290051-%DA%86%D9%86%D8%AF-%D9%86%DA%A9%D8%AA%D9%87-%DA%A9%D9%87-%D8%A8%D8%A7%DB%8C%D8%AF-%D8%A8%D9%87-%D9%87%D9%86%DA%AF%D8%A7%D9%85-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87-%D9%86%D9%88%DB%8C%D8%B3%DB%8C-%D8%A8%D8%A7%D8%B2%DB%8C-%D8%A8%D9%87-%D8%A7%D9%88%D9%86-%D9%87%D8%A7-%D8%AA%D9%88%D8%AC%D9%87-%D8%A8%D8%B4%D9%87%21&p=1275335&viewfull=1#post1275335)
Overwrite ( دوباره نویسی ) (http://barnamenevis.org/showthread.php?290051-%DA%86%D9%86%D8%AF-%D9%86%DA%A9%D8%AA%D9%87-%DA%A9%D9%87-%D8%A8%D8%A7%DB%8C%D8%AF-%D8%A8%D9%87-%D9%87%D9%86%DA%AF%D8%A7%D9%85-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87-%D9%86%D9%88%DB%8C%D8%B3%DB%8C-%D8%A8%D8%A7%D8%B2%DB%8C-%D8%A8%D9%87-%D8%A7%D9%88%D9%86-%D9%87%D8%A7-%D8%AA%D9%88%D8%AC%D9%87-%D8%A8%D8%B4%D9%87%21&p=1275849&viewfull=1#post1275849)
hi.alir
جمعه 13 خرداد 1390, 19:57 عصر
اطلاعات تراز شده در حافظه ( Memory Aligned Data ): اگر آدرس شروع یک داده ی n بایتی بر n بخش پذیر باشه، اون وقت اون داده تو حافظه تراز شده. مثلا یه int در معماری 64bit که دارای 64bit حجم هست، اگر آدرس شروعش 0x60000 باشه تراز شده هست و اگر 0x60001 باشه تراز شده نیست.
CPU اطلاعاتی که تو حافظه تراز شده هستند رو خیلی سریعتر می خونه و می نویسه. پس بهتره که داده ها رو تراز شده تو CPU نگر دارید. البته اگر شما اینکار رو نکنید به شرطی که کامپایلر خوبی داشته باشید خود کامپایلر انجام میده. برای مثال structure زیر رو در نظر بگیرید:
struct NonMemoryAligned
{
char a;
__int64 b;
int c;
};
وقتی یه نمونه از این structure میسازید، کامپایلر خوب همیشه اون رو جایی از حافظه قرار میده که تراز شده باشه. چون این ساختار 13 بایت حجم داره پس باید تو آدرسی قرار بده که ضریب 13 باشه. البته دقت کنید که آدرسی توی حافظه که ضریب 13 باشه خیلی کم پیدا میشه تا آدرسی که توانی از 2 باشه. ما اون آدرس رو برای راحتی کار 0 در نظر می گیریم ( 0 ضریب تمام اعداد هست ). حالا باید نگاه کنیم که داده های این ساختار هم تراز شده هستند؟ متغیر a طبیعتا چون اولین متغیر هست همیشه تراز شده هست، 0 (آدرس شروع حافظه برای a) تقسیم بر 1 (حجم نوع داده ای char) برابر هست با 0، بنابراین char a تراز شده هست. اما در مورد b چطور؟ الان آدرس شروع b باید 1 باشه، چون خانه ی 0 برای a بود. 1 تقسیم بر 8 برابر است با 0.125، بنابراین این داده تراز شده نیست و به همین ترتیب int c هم تراز شده نیست. حالا به ساختار زیر دقت کنید:
struct MemoryAligned
{
__int64 b;
int c;
char a;
char unused[3];
};
در این ساختار نه تنها حجم کل برابر 16 (توانی از 2) است بلکه تمامی داده های درون اون هم تراز شده هستند. سرعت کار با ساختار دوم تقریبا 1.15 برابر ساختار اول هست.
خطای پنهان
حالا فرض کنید که یه ساختار به شکل زیر در ++C برای نور درست کردید:
enum LightType : unsigned int
{
LightType_Point = 0,
LightType_Spot = LightType_Point + 1,
};
struct Light
{
LightType Type;
Vector3 Location;
Vector4 Color;
Vector3 Dir;
float SpotPower;
Vector3 Atten;
float Range;
};
و عین همین رو در HLSL. حالا وقتی کل این ساختار رو به یکباره می خواید منتقل کنید به یه نمونه از این ساختار تو شیدر خطایی رخ نمیده ولی وقتی نتیجه رو می بینید اصلا درست نیست. برای مثال شما رنگ آبی رو انتخاب کردید و دارید رنگ نور رو قهوه ای میبینید و یا محل نور رو بالای خونه گزاشتید ولی الان جای دیگریست و ...
این به این دلیل هست که وقتی کد رو کامپایل می کنید ساختار توی ++C توسط کامپایلر تغییر داده میشه تا Memory Aligned بشه، بنابراین دیگه ساختار توی ++C با HLSL شبیه به هم نیست. واسه همین اطلاعات در جا های اشتباهی منتقل میشه. راه حل اینه که این بهینه سازی کامپایلر رو تعطیل کنید و یا ساختار رو در هر دو جا به گونه ای بنویسید که کامپایلر نیازی به تغییر اون نداشته باشه، مثل زیر:
struct Light
{
Vector4 Color;
LightType Type;
float SpotPower;
Vector3 Location;
Vector3 Dir;
Vector3 Atten;
float Rang;
};
hi.alir
شنبه 14 خرداد 1390, 19:08 عصر
فرض کنید دو شی A و B رو دارید. A جلوی B قرار گرفته به طوری که تقریبا نیمی از B رو پوشش داده. حالا اگر اول B رو رسم کنید و بعد A رو، ابتدا B به طور کامل رسم میشه و بعد A هم به طور کامل رسم میشه. ولی اگر اول A رو رسم کنید و بعد B، ابتدا A به طور کامل رسم میشه ولی B نصفه ( فقط قسمت هایی که دیده میشه ) رسم میشه، چون نیمی از اون پشت A هست و در Depth test رد میشه. بنابراین اگر ترتیب رسم اشیا از نزدیکترین شی به دوربین به سمت دورترین شی به دوربین باشه Performance بهتری نسیبتون میشه.
یک اشتباه
بعضی ها متد رسم آسمون رو به جای متد خالی کردن صفحه قرار میدند. اینطوری دیگه متد Clear صدا زده نمیشه و سپس اشیا دیگر رو رسم می کنند و پیش خودشون فکر می کنند Optimization انجام دادند! اما این کار اشتباه هست. چون اگر آسمون اول رسم بشه باید کل اون رسم بشه ولی اگر اول صفحه رو Clear کنید و بعد دونه دونه اشیا رو مثل قبل رسم کنید و در آخر آسمون رو رسم کنید ممکنه حتی تمام آسمون در Depth test رد بشه و تقریبا هیچ هزینه ای برای رسم آسمون پرداخت نکنید. پس اگر آسمون در آخر رسم بشه در بیشتر موارد از Performance بهتری برخوردار خواهید بود، چون هزینه ی رسم آسمون به مراتب بیشتر از هزینه ی خالی کردن صفحه است.
کنترل مقدار Overwrite
اگر موقع رندر کردن قسمت هایی که دوباره نویسی میشند رو با یه رنگ خاص رندر کنید، اون وقت می تونید ببینید که تو صحنه چه مقدار و کجا ها Overwrite دارید تا اون ها رو درست کنید.
pswin.pooya
شنبه 14 خرداد 1390, 20:22 عصر
سلام
عمليات رستي ( مثل تست عمق و آلفا) بعد از پيكسل (فرگمنت) شيدر اعمال ميشن پس توي موردهاي بالا نمي تونن صادق باشن. سوال اصلي اينه كه چه شكلي جلوي محاسبات پيكسل شيدر و ورتكس شيدر براي مواردي كه ديده نمي شن گرفته بشه. خب ساده ترين و در عين حال پيچيده ترين راه حال:
scene management: يه مدير صحنه كه بتونه تا حد ممكنه شكلهايي رو كه ديده نمي شن رو رسم نكنه مثل Octree و يا BSP
frustum culling: بعد از اينكه آبجكتهايي كه بايد رسم بشن مشخص شدن بايد بررسي كرد كه ببينيم كدوم يكي از اونها توي ديد دوربين هستند يا نه معمولا اول اينكار با Bounding Sphere شكل انجام ميشه اگر تاييد شد برخورد frustum با bounding box بررسي ميشه.
توي بعضي از بازيها بهتره كه يه occlusion culling (بهترين حالتش نرم افزاي هست) انجام بشه كه باعث ميشه تقريبا به شكل 100 درصد تمام آبجكتهايي رو كه ديده نميشن رو حذف كرد.
اگر فرضيه شما يعني اول تست عمق بعدش پيكسل شيدر درست بود كه مواردي مثل occlusion culling معني خاصي پيدا نمي كردن. (يعني توي اين حالت تنها ورتكس شيدر كار مي كرد و rasterize b كه در مقابل هزينه محاسبه OC ميشد از اونها هم صرف نظر كرد.
درسته زمان پاك كردن صفحه خيلي كمه اما وقتي كه قراره آسمون همه اون رو بگيره چه نيازي به پاك كردن صفحه هست؟
راه حل اینه که این بهینه سازی کامپایلر رو تعطیل کنید و یا ساختار رو در هر دو جا به گونه ای بنویسید که کامپایلر نیازی به تغییر اون نداشته باشه،بدترين كار ممكن اينه. اين مشكل رو ميشه بصورت زير برطرف كرد:
#pragma pack(push, packing)
#pragma pack(1)
struct Light
{
Vector4 Color;
LightType Type;
float SpotPower;
Vector3 Location;
Vector3 Dir;
Vector3 Atten;
float Rang;
};
#pragma pack(pop, packing)
hi.alir
شنبه 14 خرداد 1390, 21:15 عصر
من هم تو این مورد یه مقدار به مشکل برخورد کردم. ولی مثل اینکه این چیزا هزار مدل داره که hardware یکمیش رو پیاده میکنه. تو AMD Developer Center یه فایل pdf هست که اونجا نوشته ی من رو تایید می کنه. قبلا لینکش این (http://ati.amd.com/developer/SDK/AMD_SDK_Samples_May2007/Documentations/ATI_Radeon_HD_2000_programming_guide.pdf) بود ولی مثل اینکه الان دیگه این نیست و جای دیگری قرار داره. الان وقتی تو اون لینک میریم منتقل میشیم به صفحه ی اصلی.
درسته زمان پاك كردن صفحه خيلي كمه اما وقتي كه قراره آسمون همه اون رو بگيره چه نيازي به پاك كردن صفحه هست؟
گفتم دیگه. اگر آسمون آخر رسم بشه Overwrite کمتر میشه.
بدترين كار ممكن اينه. اين مشكل رو ميشه بصورت زير برطرف كرد:بدترین کار ممکن کاری بود که اولش گفتم و بعد از اون کار بهتر رو گفتم. طبق توضیحاتی که دادم خواننده باید متوجه بشه که تعطیل کردن یک بهینه سازی کار چندان عاقلانه ای نیست.
pswin.pooya
شنبه 14 خرداد 1390, 21:47 عصر
گفتم دیگه. اگر آسمون آخر رسم بشه Overwrite کمتر میشه.
همنطور كه گفتم تست عمق بعد از همه چيزه (حتي تست آلفا) براي اين مورد خيلي راحت ميتوني سرچ بزني. زمان رسم آسمون تست عمق، آلفا و ... غير فعال هستند و اين يعني فورا بعد از فرگمنت همه چي نوشته ميشه (زمان نوشتن داخل بافر براي عمليات پاك كردن و يا نوشتن پيكسل محاسبه شده فرقي نميكنه (طبق تئوري. شايد توي عمل فرق كنه) پس حالا فقط ميمونه شيدر فرگمنت كه بايد براي كل صفحه (چون آسمونه) محاسبه بشه اين مورد براي آسمون فرقي نميكنه چه بخواد overwrite باشه چه نه چون بالاخره عميلت fragment stage براي هر دو حالت تكرار شده. قضيه مهم اينه كه پاك كردن تاثير چنداني روي كارايي نداره. مسائل خيلي مهمتري هستن كه بايد به اونها رسيده بشه.
من هم تو این مورد یه مقدار به مشکل برخورد کردم. ولی مثل اینکه این چیزا هزار مدل داره که hardware یکمیش رو پیاده میکنه.
اگر منظورت ترتيب تستها هست كه يه مدل بيشتر نيست و هميشه تست عمق بعد از fragment stage قرار داره و به خاطر همين هم هست كه ميشه توي فرگمنت شيدر مقدار عمق رو خوند يا تغيير داد (به كمك pbo داخل OpenGL ميشه اينكار رو كرد)
سپول
شنبه 14 خرداد 1390, 22:11 عصر
همنطور كه گفتم تست عمق بعد از همه چيزه (حتي تست آلفا)
تست عمق و آلفا قبل از پیکسل شیدر هست. تغییر مقدار عمق هم معمولا در پیکسل شیدر پیشنهاد نمیشه چون در اون شرایط تست عمق برای پیکسل شیدر قطع می شه.
hi.alir
شنبه 14 خرداد 1390, 22:18 عصر
بله، منم الان دوباره به Pipeline نگاه انداختم، حرف شما درسته ولی چیزی بود که AMD می گفت.
First, the depth/stencil buffer needs to be explicitly cleared for internal hardware depth optimizations to perform well. The situation is similar with render targets. Second, typically most of the sky is occluded by other geometry such as buildings and terrain. Therefore, if we draw the sky first, then we are wasting resources by drawing pixels that will only get overridden later by geometry closer to the camera. Therefore, it is now recommended to always clear, and to draw the sky last.
تست عمق و آلفا قبل از پیکسل شیدر هست. تغییر مقدار عمق هم معمولا در پیکسل شیدر پیشنهاد نمیشه چون در اون شرایط تست عمق برای پیکسل شیدر قطع می شه.
مثل اینکه بعدش هست. این تست ها تو Direct3D تو Output merger stage هستند که آخر از همه هم هست. ( OpenGL رو نمی دونم )
pswin.pooya
شنبه 14 خرداد 1390, 22:32 عصر
تست عمق و آلفا قبل از پیکسل شیدر هست. تغییر مقدار عمق هم معمولا در پیکسل شیدر پیشنهاد نمیشه چون در اون شرایط تست عمق برای پیکسل شیدر قطع می شه.
تست عمق جزئي از عمليات رستري هست:
http://www.iamthomasvogel.de/wp-content/uploads//graphics_pipeline.jpg
اصلا تست آلفا چيه؟ زماني كه مقدار آلفا معلوم نيست (قبل از پيكسل شيدر) چه شكلي مي خواد آلفا تست بشه؟ من 100 درصد مطمئن هستم اونها بعد از پيكسل شيدرنcliping. تنها چيزيه كه cull ميكنه و قبل از پيكسل شيدر هست
سپول
یک شنبه 15 خرداد 1390, 00:31 صبح
تصحیح می کنم، تست عمق قبل از pixel shader انجام می شه، نه تست آلفا.
اگه تا حالا از z-prepass چیزی شنیدین با استفاده از همین تکنیک های کارت گرافیک کار می کنه (hierarchal z-test) که کارمک در بازی دوم 3 پیاده کرد و از اون به بعد توی خیلی از موتورها استفاده شده. صحنه رو یک بازی می کشند با pixel shader خالی ، اطلاعات depth رو ذخیره می کنه، در مرحله بعد که نور پردازی هست و پیکسل شیدر های سنگین تر استفاده می شه، با استفاده از depth-buffer قبلی، پیسکل ها cull می شوند و در پیکسل شیدر overdraw نداریم.
pswin.pooya
یک شنبه 15 خرداد 1390, 01:14 صبح
گه تا حالا از z-prepass چیزی شنیدین با استفاده از همین تکنیک های کارت گرافیک کار می کنه
خيلي ممنون. نه تا حالا نشنيده بودم.
(hierarchal z-test) که کارمک در بازی دوم 3 پیاده کرد و از اون به بعد توی خیلی از موتورها استفاده شده
توي اين فاصله من يه مقدار مطالعه كردم ولي چيز خاصي دستگيرم نشد. امكانش هست دربارش توضيح بديد
سپول
یک شنبه 15 خرداد 1390, 13:48 عصر
بهش می گن همون early-z test (carmack's z-prepass) i... معمولا بافر depth در کارت گرافیک در resolution های مختلف ذخیره می شه . و کارت گرافیک برای تست از level پایین (رزولوشن کم) شروع می کنه تست کردن و بالا تر میاد. صرفا برای سرعت بیشتر هست و حتی در الگوریتم های occlusion culling هم استفاده می شه.
اگه در اینترنت گشتید و چیزی پیدا نکردید این هم یک لینک (صفحه 19) :
http://developer.amd.com/gpu_assets/GDC06-Advanced_D3D_Tutorial_Day-Huddy-Optimization.pdf
اونجا هم گفته که سعی کنید در شیدر مقدار depth رو تغییر ندهید چون early-z رو از کار می اندازه.
vBulletin® v4.2.5, Copyright ©2000-1404, Jelsoft Enterprises Ltd.