Господа. Кто мне может помочь в одной маленькой проблеме? Я программирую под MFC (7.0).
Проблема вот в чем:
Есть окно, на DC которого надо рисовать. По таймеру рисуются несколько графиков. Таймер срабатывает 25 раз в секунду.
Упрощая задачу, можно сказать, что есть здоровый двусвязный список из структур, содержащих несколько (вещественных) чисел. Программа рисует в реальном времени пары этих чисел и соединяющии их линии (каждую предыдущую пару с последующей).
Есть два указателя, часть очереди между которыми отрисовывается на экране. По каждому тику таймера объект типа график продвигает эти указатели вперед на немного, стирает "хвост" и дорисовует переднюю часть.
Теперь нужно добавить оси координат и сетку. Если это делать в той же функции, что выполняется по таймеру, получается хреново: оси накладуются на "зад" отрисовуемой части очереди.
Надо в функции по таймеру сначала отобразить координатную сетку, потом наложить на нее отрисовуемую часть очереди.
Это два слоя, которые надо хранить в объекте график, причем нужно перерисовывать их по необходимости (например, юзер мышкой передвигает начало координат по графику).
Как это сделать? Device context'ы, кажется живут недолго, а на битмапе нельзя рисовать...
Здравствуйте, PhantomIvan, Вы писали:
PI>Это два слоя, которые надо хранить в объекте график, причем нужно перерисовывать их по необходимости (например, юзер мышкой передвигает начало координат по графику).
"Слои" — это термины из логической структуры вашей программы. В DC никаких слоев нету. И вообще, мне непонятно, в чем проблема. Надо рисовать сетку — рисуйте. Надо рисовать координатные оси — рисуйте.
Когда юзер мышкой чего-то двигает, вы просто перерисовываете изменившуюся область
PI>Как это сделать? Device context'ы, кажется живут недолго, а на битмапе нельзя рисовать...
На битмапе рисовать можно, но вы этого сделать не захотите, птому что это будет медленно и вам враяд ли поможет.
Занимался схожей проблемой, только картинка не скроллировалась, а обновлялась целиком 25 раз/сек.
Итак:
1. DC будут долго жить, если в стиле класса (WNDCLASS::style) указать CS_OWNDC. Благодаря этому не приходится постоянно при каждой отрисовке вызывать кучу SelectObject(). Создать их можно, как
memDC.CreateCompatibleDC(GetDC()); // DC для вывода графика
gridDC.CreateCompatibleDC(GetDC());// DC для вывода подложки (осей, подписей, координатной сетки)
2. Понадобятся 2 CBitmap, (пере)создавать которые нужно при изменении размеров окна. Можно удалять их в OnSize() и создавать в
OnPaint().
3. Отрисовку подложки можно делать не всегда (у меня регулируется флажком m_flAxisReCalculate).
Здравствуйте, PhantomIvan, Вы писали:
PI>Упрощая задачу, можно сказать, что есть здоровый двусвязный список из структур, содержащих несколько (вещественных) чисел. Программа рисует в реальном времени пары этих чисел и соединяющии их линии (каждую предыдущую пару с последующей). PI>Есть два указателя, часть очереди между которыми отрисовывается на экране. По каждому тику таймера объект типа график продвигает эти указатели вперед на немного, стирает "хвост" и дорисовует переднюю часть. PI>Теперь нужно добавить оси координат и сетку. Если это делать в той же функции, что выполняется по таймеру, получается хреново: оси накладуются на "зад" отрисовуемой части очереди. PI>Надо в функции по таймеру сначала отобразить координатную сетку, потом наложить на нее отрисовуемую часть очереди.
Здравствуйте, Dmitry V. Romanovich, Вы писали:
DVR>Занимался схожей проблемой, только картинка не скроллировалась, а обновлялась целиком 25 раз/сек.
Спасибо за идею. У меня ушло три дня, чтобы правильно запрограммировать послойный вывод в DC (ну, я там еще кой-чего отлаживал и унаследовал класс от CWnd — раньше на общем контексте рисовал, что неудобно). Вот участки кода.
Обратите внимание:
1. В OnPaint нету рисования, за исключением битового копирования. Только при инициализации зарисовуется координатная сетка. Тут, наверное, будет еще вызов функции, заполняющей слой с надписями; до нее я пока не дошел. Вывод на основной слой происходит в другой отфонарной функции-члене (вместе с продвижением указателей на данные в очереди).
2. Первоначально Bitmap'ы заполняются белым цветом. Первый контекст в памяти выкопирывается в режиме SRCCOPY — это фон, остальные по очереди в режиме SRCAND: здесь и работает белый цвет. Потом части нефоновых битмапов должны затираться белым брашем. (О, я тут баг навскидку вычислил: у меня не белый браш в функциях а тот же, что и фоновый; понял почему в приложении этого не видно, на да неважно).
3. Что касается предыдущего пункта, я пробовал пользоваться не BitBlt'ом, а TransparentBlt'ом (битмапы-то вначале черные), но получалось почему-то мерцание, хотя у меня в OnPaint'е всего один BitBlt на скрин-контекст.
4. Легко переделать решение для вектора из слоев. Необязательно сначала копировать все слои на контекст в памяти, можно в OnPaint'е все сразу на скрин-контекст. Насколько я понимаю, реально отображение на экран произойдет при выходе из области видимости при уничтожении CPaintDC.
5. Регистрация винда как CS_OWNDC в коде выиграша дает понт, а графиков на экране может быть несколько (десятков). Я читал, что контексты быстро заканчиваются.
6. Кроме убиения и воскрешения битмапов надо еще и контексты освобождать и обратно требовать, иначе х@йня получается (даже если CS_OWNDC).
Помогите внести ясность в вопросы:
1. Почему у меня TransparentBlt (для контекста в памяти) давал flicker? См. выше п. 2 и 3.
2. Где и когда заканчиваются device context'ы?
3. Как убрать мерцание при ресайзе?
Здравствуйте, Serguei666, Вы писали:
PI>>Это два слоя, которые надо хранить в объекте график, причем нужно перерисовывать их по необходимости (например, юзер мышкой передвигает начало координат по графику). S>"Слои" — это термины из логической структуры вашей программы. В DC никаких слоев нету. И вообще, мне непонятно, в чем проблема. Надо рисовать сетку — рисуйте. Надо рисовать координатные оси — рисуйте. S>Когда юзер мышкой чего-то двигает, вы просто перерисовываете изменившуюся область
PI>>Как это сделать? Device context'ы, кажется живут недолго, а на битмапе нельзя рисовать... S>На битмапе рисовать можно, но вы этого сделать не захотите, птому что это будет медленно и вам враяд ли поможет.
Поразительная придирчивость к словам. Неужели Вы подумали, что я так недальновиден? Конечно, я имел в виду логическую структуру моей програмы. А Вы чем оперируете? Уж не побитовыми ли сдвигами?
А что касается "просто" перерисовать, то пока прога ВСЕ пересчитает, много воды утечет...
Прочтите лучше мой ответ на месаж коллеги (я там почему-то анонимом подписан).
Здравствуйте, PhantomIvan, Вы писали:
PI>Здравствуйте, Serguei666, Вы писали:
PI>>>Это два слоя, которые надо хранить в объекте график, причем нужно перерисовывать их по необходимости (например, юзер мышкой передвигает начало координат по графику). S>>"Слои" — это термины из логической структуры вашей программы. В DC никаких слоев нету. И вообще, мне непонятно, в чем проблема. Надо рисовать сетку — рисуйте. Надо рисовать координатные оси — рисуйте. S>>Когда юзер мышкой чего-то двигает, вы просто перерисовываете изменившуюся область
PI>>>Как это сделать? Device context'ы, кажется живут недолго, а на битмапе нельзя рисовать... S>>На битмапе рисовать можно, но вы этого сделать не захотите, птому что это будет медленно и вам враяд ли поможет. PI>Поразительная придирчивость к словам.
Это не придирчивость, а уточнение. Может, вы комп первый раз вчера увидели.
PI>Неужели Вы подумали, что я так недальновиден?
Я вас лично не знаю. вполне может оказаться, что вы недальновидны
PI>Конечно, я имел в виду логическую структуру моей програмы. А Вы чем оперируете? Уж не побитовыми ли сдвигами?
Зависит от ситуации
PI>А что касается "просто" перерисовать, то пока прога ВСЕ пересчитает, много воды утечет...
Тогда надо оптимизировать и перерисовывать только то, что необходимо. Из DC можно достать область, которую надо перерисовать, разрезать ее на прямоугольники (для этого есть функции) и отрисовывать только эти прямоугольники.
PI>Прочтите лучше мой ответ на месаж коллеги (я там почему-то анонимом подписан).
Обязательно.
Сасибо за пример. Как раз пришлось решать подобную проблему.
Проблема проявилась вот какая: при размерах, сравнимых с размерами экрана по высоте (1024), все жутко тормозит. Поскольку там кроие двух BitBlt ничего нет — вопрос такой — эта проблема не решаема в принципе или есть на что BitBlt заменить?
Здравствуйте, Dareka, Вы писали:
D>Проблема проявилась вот какая: при размерах, сравнимых с размерами экрана по высоте (1024), все жутко тормозит. Поскольку там кроие двух BitBlt ничего нет — вопрос такой — эта проблема не решаема в принципе или есть на что BitBlt заменить?
А что именно тормозит? Рисование в буфере или вывод его на экран с помощью BitBlt? В первом случае я бы попробовал пооптимизировать рисование (дорисовывать только изменения, возможно, скроллинг сделать вместо перерисовки всего, возможно, буфер бОльшего размера), во втором — посмотрел бы в сторону DirectX, ну или, если это не поможет, опять же можно попробовать поиграться со скроллингом или частичной дорисовкой, только нужно делать очень аккуратно, чтобы не было артефактов при перемещении или перекрытии окна.
Re[3]: Медленный BitBlt
От:
Аноним
Дата:
02.10.03 17:07
Оценка:
BitBlt одна из самых быстрых функций в GDI.
Покажи код с этими BitBlt.
George.
Здравствуйте, Dareka, Вы писали:
D>Проблема проявилась вот какая: при размерах, сравнимых с размерами экрана по высоте (1024), все жутко тормозит. Поскольку там кроие двух BitBlt ничего нет — вопрос такой — эта проблема не решаема в принципе или есть на что BitBlt заменить?
Здравствуйте, Vadim B, Вы писали:
VB>Здравствуйте, Dareka, Вы писали:
D>>Проблема проявилась вот какая: при размерах, сравнимых с размерами экрана по высоте (1024), все жутко тормозит. Поскольку там кроие двух BitBlt ничего нет — вопрос такой — эта проблема не решаема в принципе или есть на что BitBlt заменить?
VB>А что именно тормозит? Рисование в буфере или вывод его на экран с помощью BitBlt? В первом случае я бы попробовал пооптимизировать рисование (дорисовывать только изменения, возможно, скроллинг сделать вместо перерисовки всего, возможно, буфер бОльшего размера), во втором — посмотрел бы в сторону DirectX, ну или, если это не поможет, опять же можно попробовать поиграться со скроллингом или частичной дорисовкой, только нужно делать очень аккуратно, чтобы не было артефактов при перемещении или перекрытии окна.
Ну, судя по исключению кусков кода, тормозит именно битблт. Я раньше слышал, что он очень быстр, поэтому меня сомнение и взяло. По сути в коде 4 раза вызывается BitBlt (для скроллинга графика, для отрисовки фона, для отрисовки графика и для отправки этого всего на экран). Каждый раз перерисовывается только освободившееся место.
Немного соптимизировав исходный код за счет меньшего количества созданий всяких битмапов и вызовов конструкторов, удалось выиграть немного скорости. Но по-прежнему разница между 400 точек в высоту и 800 видна невооруженным взглядом.
Буферы создаются размером в клиентскую область (в OnSize() размеры меняются).
Имеет ли смысл копать директикс, если тормозит битблт.
Здравствуйте, Аноним, Вы писали:
А>BitBlt одна из самых быстрых функций в GDI.
А>Покажи код с этими BitBlt.
А>George.
Собственно код:
по таймеру, каждые 10 миллисекунд зовется
double CGraphWnd::AppendPoint(double dNewPoint)
{
// append a data point to the plot
// return the previous pointdouble dPrevious ;
dPrevious = m_dCurrentPosition ;
m_dCurrentPosition = dNewPoint ;
DrawPoint() ;
Invalidate() ;
return dPrevious ;
} // AppendPointvoid CGraphWnd::DrawPoint()
{
// this does the work of "scrolling" the plot to the left
// and appending a new data point all of the plotting is
// directed to the memory based bitmap associated with m_dcPlot
// the will subsequently be BitBlt'd to the client in OnPaintint currX, prevX, currY, prevY ;
CPen *oldPen ;
CRect rectCleanUp ;
if (m_dcPlot.GetSafeHdc() != NULL)
{
// shift the plot by BitBlt'ing it to itself
// note: the m_dcPlot covers the entire client
// but we only shift bitmap that is the size
// of the plot rectangle
// grab the right side of the plot (exluding m_nShiftPixels on the left)
// move this grabbed bitmap to the left by m_nShiftPixels
m_dcPlot.BitBlt(m_rectPlot.left, m_rectPlot.top+1,
m_nPlotWidth, m_nPlotHeight, &m_dcPlot,
m_rectPlot.left+m_nShiftPixels, m_rectPlot.top+1,
SRCCOPY) ;
// establish a rectangle over the right side of plot
// which now needs to be cleaned up proir to adding the new point
rectCleanUp = m_rectPlot ;
rectCleanUp.left = rectCleanUp.right - m_nShiftPixels ;
// fill the cleanup area with the background
m_dcPlot.FillRect(rectCleanUp, &m_brushBack) ;
// draw the next line segement
// grab the plotting pen
oldPen = m_dcPlot.SelectObject(&m_penPlot) ;
// move to the previous point
prevX = m_rectPlot.right-m_nPlotShiftPixels ;
prevY = m_rectPlot.bottom -
(long)((m_dPreviousPosition - m_dLowerLimit) * m_dVerticalFactor) ;
m_dcPlot.MoveTo (prevX, prevY) ;
// draw to the current point
currX = m_rectPlot.right-m_nHalfShiftPixels ;
currY = m_rectPlot.bottom -
(long)((m_dCurrentPosition - m_dLowerLimit) * m_dVerticalFactor) ;
m_dcPlot.LineTo (currX, currY) ;
// restore the pen
m_dcPlot.SelectObject(oldPen) ;
// if the data leaks over the upper or lower plot boundaries
// fill the upper and lower leakage with the background
// this will facilitate clipping on an as needed basis
// as opposed to always calling IntersectClipRectif ((prevY <= m_rectPlot.top) || (currY <= m_rectPlot.top))
m_dcPlot.FillRect(CRect(prevX, m_rectClient.top, currX+1, m_rectPlot.top+1), &m_brushBack) ;
if ((prevY >= m_rectPlot.bottom) || (currY >= m_rectPlot.bottom))
m_dcPlot.FillRect(CRect(prevX, m_rectPlot.bottom+1, currX+1, m_rectClient.bottom+1), &m_brushBack) ;
// store the current point for connection to the next point
m_dPreviousPosition = m_dCurrentPosition ;
}
} // end DrawPointvoid CGraphWnd::OnPaint()
{
CPaintDC dc(this) ; // device context for paintingif (m_dcMem.GetSafeHdc() != NULL)
{
CRect rectUpdate(dc.m_ps.rcPaint);
// first drop the grid on the memory dc
m_dcMem.BitBlt(rectUpdate.left, rectUpdate.top, rectUpdate.Width(), rectUpdate.Height(),
&m_dcGrid, rectUpdate.left, rectUpdate.top, SRCCOPY) ;
// now add the plot on top as a "pattern" via SRCPAINT.
// works well with dark background and a light plot
m_dcMem.BitBlt(rectUpdate.left, rectUpdate.top, rectUpdate.Width(), rectUpdate.Height(),
&m_dcPlot, rectUpdate.left, rectUpdate.top, SRCAND) ; //SRCPAINT
// finally send the result to the display
dc.BitBlt(rectUpdate.left, rectUpdate.top, rectUpdate.Width(), rectUpdate.Height(),
&m_dcMem, rectUpdate.left, rectUpdate.top, SRCCOPY) ;
}
} // OnPaint
Здравствуйте, Dareka, Вы писали:
D>Ну, судя по исключению кусков кода, тормозит именно битблт. Я раньше слышал, что он очень быстр, поэтому меня сомнение и взяло. По сути в коде 4 раза вызывается BitBlt (для скроллинга графика, для отрисовки фона, для отрисовки графика и для отправки этого всего на экран). Каждый раз перерисовывается только освободившееся место. D>Имеет ли смысл копать директикс, если тормозит битблт.
Профайлером пройтись не пробовал? Возможно, что тормозит BitBlt, который ты используешь для скроллинга в memory DC. Насколько я понимаю, BitBlt из памяти на экран оптимизирован и, когда можно, выполняется непосредственно видеокартой, а вот внутри битмапа, т.е. из одного места памяти в другое — возможно, что и нет.
Если тормозит именно этот кусок, то я бы сделал так: битмап в памяти делал бы не по размеру окна, а больше (скажем, в два раза). Тогда на каждую дорисовку тебе не нужно будет скроллировать, достаточно будет дорисовать в конец и вывести на экран часть битмапа (координаты части будут каждый раз сдвигаться), и только один раз на N точек нужно будет перерисовать весь буфер (битмап) заново.
...
VB>Если тормозит именно этот кусок, то я бы сделал так: битмап в памяти делал бы не по размеру окна, а больше (скажем, в два раза). Тогда на каждую дорисовку тебе не нужно будет скроллировать, достаточно будет дорисовать в конец и вывести на экран часть битмапа (координаты части будут каждый раз сдвигаться), и только один раз на N точек нужно будет перерисовать весь буфер (битмап) заново.