Войти
ПрограммированиеФорумОбщее

Применение многопоточности в играх. (Комментарии к статье) (8 стр)

Страницы: 15 6 7 8 9 10 Следующая »
#105
3:16, 9 июня 2005

Nikopol
Традиционно: мы говорим о протоколе.
Клиент просит рендер отрендерить.  Пока рендер не отрендерил, клиент не имеет права изменять.
Это никак не проверяется кроме пачки ассертов.
Просто в подавляющем большинстве случаев клиент и не собирается изменять переданный по ссылке объект.
А в тех случаях, когда он очень даже собирается, причем прямо сейчас - тогда клиент должен работать уже с другим объектом.
Чаще всего, клиент только пишет (и никогда не делает readback), поэтому выполняется просто переключение, а не копирование.
(Например, если мы говорим о рендере, то для динамической геометрии там либо типично карусели (round-robin) либо immediate mode rendering.)


#106
10:17, 9 июня 2005

Спасибо.

Прошло более 7 месяцев
#107
15:29, 19 янв. 2006

Очень интересная статья.
Прочитал и её, и комментарии с удовольствием.

Подумалось тут:
А что если сделать рендерные объекты приватной собственностью рендера ?
То есть секция рендера хранит у себя внутри все выделенные на данный момент ресурсы вроде вертекс буферов, текстур и т.д.,
а другие секции прямого доступа к этим ресурсам не имеют.
Другие секции проводят манипуляции с объектами только посредством команд, содержащих операцию и дескриптор объекта.
То есть чтобы вместо

class CMySection
{
public:
	void Startup()
	{
		SendSectionCommand( CRenderSection::GetID(), CLoadObject( "data/tank.3ds" ) );
		// Рендер получает команду загрузить объект.
		// Наверняка, чтение с диска секция рендера перепоручит секции файловой системе.
	}
	void Reaction( const CObjectLoadingFinished& in_Command )
	{
		// Рендер загрузил объект
		if(in_Command.m_Success)
		{
			// Получили указатель на свежезагруженный объект
			m_pTank = in_Command.m_pObject;
			// Крутим его ствол
			m_pTank->FindSubnode("gun")->SetMatrix( SomeMatrix );
			// Рисуем.
			SendSectionCommand( CRenderSection::GetID(), CAddObjectToRenderQueue( m_pTank ) );
			// Небезопасный код.
			// Рендер получает команду рендерить танк,
			// но у нашей секции по-прежнему есть указатель на этот танк,
			// и следовательно мы можем сделать что-то с танком, пока рендер его тоже использует.
			// FUCKUP BEGIN
			m_pTank->FindSubnode("gun")->SetMatrix( AnotherMatrix );
			// FUCKUP END
		}
	}
private:
	CRenderableObject* m_pTank;
}

Код выглядел бы вот так:

class CMySection
{
public:
	void Startup()
	{
		SendSectionCommand( CRenderSection::GetID(), CLoadObject("data/tank.3ds") );
		// Рендер получает команду загрузить объект.
		// Как он этот объект загрузит - наша секция не должна знать.
		// Наверняка, чтение с диска секция рендера перепоручит секции файловой системе.
		// Рендер затем создаст все нужные вертекс/индекс буферы, а также и текстуры.
		// Вся эта работа от нашей секции скрыта.
	}
	void Reaction( const CObjectLoadingFinished& in_Command )
	{
		// Рендер послал нашей секции ответ "Я загрузил твой танк".
		if(in_Command.m_Success)
		{
			// получаем хэндл объекта
			m_TankHandle = in_Command.m_TankHandle;
			// Крутим ствол
			SendSectionCommand( CRenderSection::GetID(), CTransformObjectSubnode( m_TankHandle, "gun", SomeMatrix ) );
			// Рисуем
			SendSectionCommand( CRenderSection::GetID(), CAddObjectToRenderQueue( m_TankHandle ) );
		}
	}
private:
	unsigned long m_TankHandle;
};

#108
12:56, 20 янв. 2006

up :(

#109
16:09, 20 янв. 2006

Sergey.Ivanov
Поскольку вся система работает асинхронно, изменения в объектах не затрагивают текущий рендеринг.
Часть подробностей почему так было сделано - в compile-time vs run-time.

Общение между секциями типично происходит только с помощью команд.
Т.е. команда "отрендери объект" автоматически означает, что вся необходимые меры по гарантиям безопасности параллельной работы уже были сделаны.

Что-то типа того, что у Вас сделано в примере, только более эффективно и высокоуровнево.
Все мелкие изменения аккумулируются и сбрасываются пачками "между кадрами".

Чтобы понять, почему такой код

      // Получили указатель на свежезагруженный объект
      m_pTank = in_Command.m_pObject;
      // Крутим его ствол
      m_pTank->FindSubnode("gun")->SetMatrix( SomeMatrix );
      // Рисуем.
      SendSectionCommand( CRenderSection::GetID(), CAddObjectToRenderQueue( m_pTank ) );
      // Небезопасный код.
      // Рендер получает команду рендерить танк, но у нашей секции по-прежнему есть указатель на этот танк,
      // и следовательно мы можем сделать что-то с танком, пока рендер его тоже использует.
      // FUCKUP BEGIN
      m_pTank->FindSubnode("gun")->SetMatrix( AnotherMatrix );
в принципе невозможен - см. статью по поводу compile-time vs run-time.

Потенциально изменяемые "по месту" свойства объекта (о них было известно заранее - см. статью) разделялись на две категории:
- переключаемые свойства - пока идёт рендеринг, изменения в объекте происходят в другой "странице", потом - что-то типа page flip.
- копируемые свойства - пока идёт рендеринг, изменения в объекте происходят в другой копии, потом изменённые свойства копируются в область рендера.

#110
16:50, 20 янв. 2006

То есть,
идеи изложенные в compile-time vs run-time и Применение многопоточности в играх
применялись только совместно и никогда порознь ?

#111
16:57, 20 янв. 2006

Да нет, почему же :)
Я просто говорил о том, как это реально использовалось.

Собственно, я и в теории бы аккумулировал изменения, и только потом их пачкой сбрасывал.
Так правильнее.

#112
17:01, 20 янв. 2006

Ok, спасибо за идеи.
Убежал реализовывать.

#113
17:08, 20 янв. 2006

> Убежал реализовывать.
Ужас какой...

LFlip
Может, отговорим?
Расскажем про версию 2 и про версию 3?..

#114
17:18, 20 янв. 2006

Расскажите :)
Интересно будет послушать :)

#115
23:23, 23 янв. 2006

Добавлю касаемо увеличения количества отсылаемых команд на рендер. Я бы так не делал, по причине далеко не полной бесплатности отсылки команды. Чем больше команд отсылается на рендер, тем больше на это тратиться время и памяти. Т.к. каждую команду как минимум требуется скопировать в очередь приёма потока, перед этим выделив там место. При этом иногда будут вызываться и системные функции, связанные с отсылкой/приёмом команд, требующие переключение в ядро. Разумеется, время на отсылку команды ничтожно, но если их слать на каждый пук то ... Поэтому лучше именно аккумулировать группы действий, стараясь не перегружать очередь команд.

aruslan
>Расскажем про версию 2 и про версию 3?..
Была ещё и 5-ая версия. А вот четвёртой не было, видимо сбились со счёта где-то.

Sergey.Ivanov
>Расскажите :)
>Интересно будет послушать :)
У.. тут можно с лёгкостью ещё десяток статей написать. Было довольно много забавных вариантов.
Последнее моё атцкое изобретение было, например, призвано бороться с нелинейностью кода при использовании асинхронных операций.

Например, имеем раздробленный код вида.

void Reaction1() {
   ...
   Send(c1);
   ...
}

void Reaction2() {
   ...
   Send(c2);
   ...
}

void Reaction3() {
   ...
   Send(c2);
   ...
}
void Reaction10() {
   ...
   Send(c11);
   ...
}
Не очень то понятно, в какой последовательности отсылаются и принимаются команды. Если понадобиться модифицировать такой асинхронный код придётся помучиться.
Вместо этого можно написать так:
void Reaction1() {
   StarLoadObjects();
}

void Reaction2() {
   Generate(E2);
}

void Reaction3() {
   Generate(E3);
}

void Reaction10() {
   Generate(E10);
}

void StarLoadObjects() {
   ...
   Send(c1);
   ...
   Wait(E2||E3);
   ...
   Send(c2);
   ...
   Wait(E10);
   ...
   Send(c11);
   ...
}
Wait - блокирует текущий fiber связанный с функцией StarLoadObjects, но не поток. Реакции секций и т.п. продолжают нормально работать, т.е. блокируется только StarLoadObjects.
В данном варианте последовательность действий в функции StarLoadObjects более наглядна и легче корректируется в случае надобности.
Это всего лишь небольшой псевдокод, только для пояснения общих принципов.

#116
15:06, 2 фев. 2006

(думы новечка, не судите строго)

А я вот начинаю сомневаться в целесообразности такой многопоточности вообще: огромные ресурсы пропадают на передачу данных между процессами, как не крути. А что именно мы получаем? "Умножение потоков не умножает процессоры"  (тут оговорка: всё что я говорю не относится к HT-процессорам).
Так вот, потоки преимужественны когда выполняются обработки другими устройствами, вне CPU, которых приходится "ждать". Что я предлагаю, это сделать динамическую многопоточность: для медленных операций будут на ходу создаваться новые триады, которые выполняют задачу, посылают какое-либо сообщение главному потоку, что они готовы, и завершаются.

Пример:
допустим герой стоит негде в лесу. В его навправлении (ещё в дали) летит самолёт, его надо зарендерить, а ни сетка, ни текстуры не загружены. Винчестер - самая тормозная часть PC, соответственно создаётся новый thread, который грузит в буффер сам самолёт и текстуры, и когда он готов - говорит это главному потоку и завершается. За это время уже вероятно пробегут несколько кадров - без этого самолёта - он ведь ещё слишком далеко ;)

Тут опять же 2 оговорки:
1 Не знаю насколько это реализуемо с сетью
2 Надо следить, чтобы не вызвать DirectX-функции из разных потоков - он это не любит, при включении мультитреадинга он теряет производительность.. С OpernGL проблем не должно быть.

В конце концов, некоторые встроенные функции C++ тоже используют многопоточность такого типа...


Жду мнений профи..

#117
16:00, 2 фев. 2006

Создание нового потока - медленная операция.
А создание новой секции, как описано в статье, - относительно лёгкая.

#118
17:41, 2 фев. 2006

Sergey.Ivanov
можно не создавать потока, а брать из пула, да и по сравнению с дисковыми операциями потоки создаются мгновенно.

#119
18:13, 2 фев. 2006

fr
> можно не создавать потока, а брать из пула
Грубо говоря, это сродно тому что описано в статье. И я обоими руками "За".
А Basic89 предлагает потоки плодить, если я его правильно понял.

Страницы: 15 6 7 8 9 10 Следующая »
ПрограммированиеФорумОбщее

Тема в архиве.