CommandExecutorGPU.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. /*
  2. * Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibAccelGfx/GlyphAtlas.h>
  7. #include <LibWeb/Painting/BorderRadiusCornerClipper.h>
  8. #include <LibWeb/Painting/CommandExecutorGPU.h>
  9. namespace Web::Painting {
  10. CommandExecutorGPU::CommandExecutorGPU(AccelGfx::Context& context, Gfx::Bitmap& bitmap)
  11. : m_target_bitmap(bitmap)
  12. , m_context(context)
  13. {
  14. m_context.activate();
  15. auto canvas = AccelGfx::Canvas::create(bitmap.size());
  16. auto painter = AccelGfx::Painter::create(m_context, canvas);
  17. m_stacking_contexts.append({ .canvas = canvas,
  18. .painter = move(painter),
  19. .opacity = 1.0f,
  20. .destination = {},
  21. .transform = {} });
  22. }
  23. CommandExecutorGPU::~CommandExecutorGPU()
  24. {
  25. m_context.activate();
  26. VERIFY(m_stacking_contexts.size() == 1);
  27. painter().flush(m_target_bitmap);
  28. }
  29. CommandResult CommandExecutorGPU::draw_glyph_run(Vector<Gfx::DrawGlyphOrEmoji> const& glyph_run, Color const& color, Gfx::FloatPoint translation, double scale)
  30. {
  31. Vector<Gfx::DrawGlyphOrEmoji> transformed_glyph_run;
  32. transformed_glyph_run.ensure_capacity(glyph_run.size());
  33. for (auto& glyph : glyph_run) {
  34. auto transformed_glyph = glyph;
  35. transformed_glyph.visit([&](auto& glyph) {
  36. glyph.position = glyph.position.scaled(scale).translated(translation);
  37. glyph.font = *glyph.font->with_size(glyph.font->point_size() * static_cast<float>(scale));
  38. });
  39. transformed_glyph_run.append(transformed_glyph);
  40. }
  41. painter().draw_glyph_run(transformed_glyph_run, color);
  42. return CommandResult::Continue;
  43. }
  44. CommandResult CommandExecutorGPU::draw_text(Gfx::IntRect const&, String const&, Gfx::TextAlignment, Color const&, Gfx::TextElision, Gfx::TextWrapping, Optional<NonnullRefPtr<Gfx::Font>> const&)
  45. {
  46. // FIXME
  47. return CommandResult::Continue;
  48. }
  49. CommandResult CommandExecutorGPU::fill_rect(Gfx::IntRect const& rect, Color const& color, Vector<Gfx::Path> const&)
  50. {
  51. // FIXME: Support clip paths
  52. painter().fill_rect(rect, color);
  53. return CommandResult::Continue;
  54. }
  55. static AccelGfx::Painter::ScalingMode to_accelgfx_scaling_mode(Gfx::Painter::ScalingMode scaling_mode)
  56. {
  57. switch (scaling_mode) {
  58. case Gfx::Painter::ScalingMode::NearestNeighbor:
  59. case Gfx::Painter::ScalingMode::BoxSampling:
  60. case Gfx::Painter::ScalingMode::SmoothPixels:
  61. case Gfx::Painter::ScalingMode::None:
  62. return AccelGfx::Painter::ScalingMode::NearestNeighbor;
  63. case Gfx::Painter::ScalingMode::BilinearBlend:
  64. return AccelGfx::Painter::ScalingMode::Bilinear;
  65. default:
  66. VERIFY_NOT_REACHED();
  67. }
  68. }
  69. CommandResult CommandExecutorGPU::draw_scaled_bitmap(Gfx::IntRect const& dst_rect, Gfx::Bitmap const& bitmap, Gfx::IntRect const& src_rect, Gfx::Painter::ScalingMode scaling_mode)
  70. {
  71. painter().draw_scaled_bitmap(dst_rect, bitmap, src_rect, to_accelgfx_scaling_mode(scaling_mode));
  72. return CommandResult::Continue;
  73. }
  74. CommandResult CommandExecutorGPU::draw_scaled_immutable_bitmap(Gfx::IntRect const& dst_rect, Gfx::ImmutableBitmap const& immutable_bitmap, Gfx::IntRect const& src_rect, Gfx::Painter::ScalingMode scaling_mode, Vector<Gfx::Path> const&)
  75. {
  76. // TODO: Support clip paths
  77. painter().draw_scaled_immutable_bitmap(dst_rect, immutable_bitmap, src_rect, to_accelgfx_scaling_mode(scaling_mode));
  78. return CommandResult::Continue;
  79. }
  80. CommandResult CommandExecutorGPU::set_clip_rect(Gfx::IntRect const& rect)
  81. {
  82. painter().set_clip_rect(rect);
  83. return CommandResult::Continue;
  84. }
  85. CommandResult CommandExecutorGPU::clear_clip_rect()
  86. {
  87. painter().clear_clip_rect();
  88. return CommandResult::Continue;
  89. }
  90. CommandResult CommandExecutorGPU::push_stacking_context(float opacity, bool is_fixed_position, Gfx::IntRect const& source_paintable_rect, Gfx::IntPoint post_transform_translation, CSS::ImageRendering, StackingContextTransform transform, Optional<StackingContextMask>)
  91. {
  92. if (source_paintable_rect.is_empty())
  93. return CommandResult::SkipStackingContext;
  94. m_stacking_contexts.last().stacking_context_depth++;
  95. painter().save();
  96. if (is_fixed_position) {
  97. auto const& translation = painter().transform().translation();
  98. painter().translate(-translation);
  99. }
  100. auto stacking_context_transform = Gfx::extract_2d_affine_transform(transform.matrix);
  101. Gfx::AffineTransform inverse_origin_translation;
  102. inverse_origin_translation.translate(-transform.origin);
  103. Gfx::AffineTransform origin_translation;
  104. origin_translation.translate(transform.origin);
  105. Gfx::AffineTransform final_transform = origin_translation;
  106. final_transform.multiply(stacking_context_transform);
  107. final_transform.multiply(inverse_origin_translation);
  108. if (opacity < 1 || !stacking_context_transform.is_identity_or_translation()) {
  109. // If, due to layout mistakes, we encounter an excessively large rectangle here, it must be skipped to prevent
  110. // framebuffer allocation failure.
  111. if (source_paintable_rect.width() > 10000 || source_paintable_rect.height() > 10000) {
  112. dbgln("FIXME: Skipping stacking context with excessively large paintable rect: {}", source_paintable_rect);
  113. return CommandResult::SkipStackingContext;
  114. }
  115. auto canvas = AccelGfx::Canvas::create(source_paintable_rect.size());
  116. auto painter = AccelGfx::Painter::create(m_context, canvas);
  117. painter->translate(-source_paintable_rect.location().to_type<float>());
  118. painter->clear(Color::Transparent);
  119. m_stacking_contexts.append({ .canvas = canvas,
  120. .painter = move(painter),
  121. .opacity = opacity,
  122. .destination = source_paintable_rect,
  123. .transform = final_transform });
  124. } else {
  125. painter().translate(stacking_context_transform.translation() + post_transform_translation.to_type<float>());
  126. m_stacking_contexts.append({ .canvas = {},
  127. .painter = MaybeOwned(painter()),
  128. .opacity = opacity,
  129. .destination = {},
  130. .transform = final_transform });
  131. }
  132. return CommandResult::Continue;
  133. }
  134. CommandResult CommandExecutorGPU::pop_stacking_context()
  135. {
  136. auto stacking_context = m_stacking_contexts.take_last();
  137. VERIFY(stacking_context.stacking_context_depth == 0);
  138. if (stacking_context.painter.is_owned()) {
  139. painter().blit_canvas(stacking_context.destination, *stacking_context.canvas, stacking_context.opacity, stacking_context.transform);
  140. }
  141. painter().restore();
  142. m_stacking_contexts.last().stacking_context_depth--;
  143. return CommandResult::Continue;
  144. }
  145. CommandResult CommandExecutorGPU::paint_linear_gradient(Gfx::IntRect const& rect, Web::Painting::LinearGradientData const& data, Vector<Gfx::Path> const&)
  146. {
  147. // FIXME: Support clip paths
  148. painter().fill_rect_with_linear_gradient(rect, data.color_stops.list, data.gradient_angle, data.color_stops.repeat_length);
  149. return CommandResult::Continue;
  150. }
  151. CommandResult CommandExecutorGPU::paint_outer_box_shadow(PaintOuterBoxShadowParams const&)
  152. {
  153. // FIXME
  154. return CommandResult::Continue;
  155. }
  156. CommandResult CommandExecutorGPU::paint_inner_box_shadow(PaintOuterBoxShadowParams const&)
  157. {
  158. // FIXME
  159. return CommandResult::Continue;
  160. }
  161. CommandResult CommandExecutorGPU::paint_text_shadow(int blur_radius, Gfx::IntRect const& shadow_bounding_rect, Gfx::IntRect const& text_rect, Span<Gfx::DrawGlyphOrEmoji const> glyph_run, Color const& color, int fragment_baseline, Gfx::IntPoint const& draw_location)
  162. {
  163. auto text_shadow_canvas = AccelGfx::Canvas::create(shadow_bounding_rect.size());
  164. auto text_shadow_painter = AccelGfx::Painter::create(m_context, text_shadow_canvas);
  165. text_shadow_painter->clear(color.with_alpha(0));
  166. Gfx::FloatRect const shadow_location { draw_location, shadow_bounding_rect.size() };
  167. Gfx::IntPoint const baseline_start(text_rect.x(), text_rect.y() + fragment_baseline);
  168. text_shadow_painter->translate(baseline_start.to_type<float>());
  169. text_shadow_painter->draw_glyph_run(glyph_run, color);
  170. if (blur_radius == 0) {
  171. painter().blit_canvas(shadow_location, *text_shadow_canvas);
  172. return CommandResult::Continue;
  173. }
  174. auto horizontal_blur_canvas = AccelGfx::Canvas::create(shadow_bounding_rect.size());
  175. auto horizontal_blur_painter = AccelGfx::Painter::create(m_context, horizontal_blur_canvas);
  176. horizontal_blur_painter->clear(color.with_alpha(0));
  177. horizontal_blur_painter->blit_blurred_canvas(shadow_bounding_rect.to_type<float>(), *text_shadow_canvas, blur_radius, AccelGfx::Painter::BlurDirection::Horizontal);
  178. painter().blit_blurred_canvas(shadow_location, *horizontal_blur_canvas, blur_radius, AccelGfx::Painter::BlurDirection::Vertical);
  179. return CommandResult::Continue;
  180. }
  181. CommandResult CommandExecutorGPU::fill_rect_with_rounded_corners(Gfx::IntRect const& rect, Color const& color, Gfx::AntiAliasingPainter::CornerRadius const& top_left_radius, Gfx::AntiAliasingPainter::CornerRadius const& top_right_radius, Gfx::AntiAliasingPainter::CornerRadius const& bottom_left_radius, Gfx::AntiAliasingPainter::CornerRadius const& bottom_right_radius, Vector<Gfx::Path> const&)
  182. {
  183. // FIXME: Support clip paths
  184. painter().fill_rect_with_rounded_corners(
  185. rect, color,
  186. { static_cast<float>(top_left_radius.horizontal_radius), static_cast<float>(top_left_radius.vertical_radius) },
  187. { static_cast<float>(top_right_radius.horizontal_radius), static_cast<float>(top_right_radius.vertical_radius) },
  188. { static_cast<float>(bottom_left_radius.horizontal_radius), static_cast<float>(bottom_left_radius.vertical_radius) },
  189. { static_cast<float>(bottom_right_radius.horizontal_radius), static_cast<float>(bottom_right_radius.vertical_radius) });
  190. return CommandResult::Continue;
  191. }
  192. CommandResult CommandExecutorGPU::fill_path_using_color(Gfx::Path const&, Color const&, Gfx::Painter::WindingRule, Gfx::FloatPoint const&)
  193. {
  194. // FIXME
  195. return CommandResult::Continue;
  196. }
  197. CommandResult CommandExecutorGPU::fill_path_using_paint_style(Gfx::Path const&, Gfx::PaintStyle const&, Gfx::Painter::WindingRule, float, Gfx::FloatPoint const&)
  198. {
  199. // FIXME
  200. return CommandResult::Continue;
  201. }
  202. CommandResult CommandExecutorGPU::stroke_path_using_color(Gfx::Path const&, Color const&, float, Gfx::FloatPoint const&)
  203. {
  204. // FIXME
  205. return CommandResult::Continue;
  206. }
  207. CommandResult CommandExecutorGPU::stroke_path_using_paint_style(Gfx::Path const&, Gfx::PaintStyle const&, float, float, Gfx::FloatPoint const&)
  208. {
  209. // FIXME
  210. return CommandResult::Continue;
  211. }
  212. CommandResult CommandExecutorGPU::draw_ellipse(Gfx::IntRect const&, Color const&, int)
  213. {
  214. // FIXME
  215. return CommandResult::Continue;
  216. }
  217. CommandResult CommandExecutorGPU::fill_ellipse(Gfx::IntRect const& rect, Color const& color, Gfx::AntiAliasingPainter::BlendMode)
  218. {
  219. auto horizontal_radius = static_cast<float>(rect.width() / 2);
  220. auto vertical_radius = static_cast<float>(rect.height() / 2);
  221. painter().fill_rect_with_rounded_corners(
  222. rect, color,
  223. { horizontal_radius, vertical_radius },
  224. { horizontal_radius, vertical_radius },
  225. { horizontal_radius, vertical_radius },
  226. { horizontal_radius, vertical_radius });
  227. return CommandResult::Continue;
  228. }
  229. CommandResult CommandExecutorGPU::draw_line(Color const& color, Gfx::IntPoint const& a, Gfx::IntPoint const& b, int thickness, Gfx::Painter::LineStyle, Color const&)
  230. {
  231. // FIXME: Pass line style and alternate color once AccelGfx::Painter supports it
  232. painter().draw_line(a, b, thickness, color);
  233. return CommandResult::Continue;
  234. }
  235. CommandResult CommandExecutorGPU::draw_signed_distance_field(Gfx::IntRect const&, Color const&, Gfx::GrayscaleBitmap const&, float)
  236. {
  237. // FIXME
  238. return CommandResult::Continue;
  239. }
  240. CommandResult CommandExecutorGPU::apply_backdrop_filter(Gfx::IntRect const&, Web::CSS::ResolvedBackdropFilter const&)
  241. {
  242. // FIXME
  243. return CommandResult::Continue;
  244. }
  245. CommandResult CommandExecutorGPU::draw_rect(Gfx::IntRect const&, Color const&, bool)
  246. {
  247. // FIXME
  248. return CommandResult::Continue;
  249. }
  250. CommandResult CommandExecutorGPU::paint_radial_gradient(Gfx::IntRect const&, Web::Painting::RadialGradientData const&, Gfx::IntPoint const&, Gfx::IntSize const&, Vector<Gfx::Path> const&)
  251. {
  252. // FIXME
  253. return CommandResult::Continue;
  254. }
  255. CommandResult CommandExecutorGPU::paint_conic_gradient(Gfx::IntRect const&, Web::Painting::ConicGradientData const&, Gfx::IntPoint const&, Vector<Gfx::Path> const&)
  256. {
  257. // FIXME
  258. return CommandResult::Continue;
  259. }
  260. CommandResult CommandExecutorGPU::draw_triangle_wave(Gfx::IntPoint const&, Gfx::IntPoint const&, Color const&, int, int)
  261. {
  262. // FIXME
  263. return CommandResult::Continue;
  264. }
  265. CommandResult CommandExecutorGPU::sample_under_corners(u32 id, CornerRadii const& corner_radii, Gfx::IntRect const& border_rect, CornerClip)
  266. {
  267. m_corner_clippers.resize(id + 1);
  268. m_corner_clippers[id] = make<BorderRadiusCornerClipper>();
  269. auto& corner_clipper = *m_corner_clippers[id];
  270. auto const& top_left = corner_radii.top_left;
  271. auto const& top_right = corner_radii.top_right;
  272. auto const& bottom_right = corner_radii.bottom_right;
  273. auto const& bottom_left = corner_radii.bottom_left;
  274. auto sampling_config = calculate_border_radius_sampling_config(corner_radii, border_rect);
  275. auto const& page_locations = sampling_config.page_locations;
  276. auto const& bitmap_locations = sampling_config.bitmap_locations;
  277. auto top_left_corner_size = Gfx::IntSize { top_left.horizontal_radius, top_left.vertical_radius };
  278. auto top_right_corner_size = Gfx::IntSize { top_right.horizontal_radius, top_right.vertical_radius };
  279. auto bottom_right_corner_size = Gfx::IntSize { bottom_right.horizontal_radius, bottom_right.vertical_radius };
  280. auto bottom_left_corner_size = Gfx::IntSize { bottom_left.horizontal_radius, bottom_left.vertical_radius };
  281. corner_clipper.page_top_left_rect = { page_locations.top_left, top_left_corner_size };
  282. corner_clipper.page_top_right_rect = { page_locations.top_right, top_right_corner_size };
  283. corner_clipper.page_bottom_right_rect = { page_locations.bottom_right, bottom_right_corner_size };
  284. corner_clipper.page_bottom_left_rect = { page_locations.bottom_left, bottom_left_corner_size };
  285. corner_clipper.sample_canvas_top_left_rect = { bitmap_locations.top_left, top_left_corner_size };
  286. corner_clipper.sample_canvas_top_right_rect = { bitmap_locations.top_right, top_right_corner_size };
  287. corner_clipper.sample_canvas_bottom_right_rect = { bitmap_locations.bottom_right, bottom_right_corner_size };
  288. corner_clipper.sample_canvas_bottom_left_rect = { bitmap_locations.bottom_left, bottom_left_corner_size };
  289. corner_clipper.corners_sample_canvas = AccelGfx::Canvas::create(sampling_config.corners_bitmap_size);
  290. auto corner_painter = AccelGfx::Painter::create(m_context, *corner_clipper.corners_sample_canvas);
  291. corner_painter->clear(Color::White);
  292. corner_painter->fill_rect_with_rounded_corners(
  293. Gfx::IntRect { { 0, 0 }, sampling_config.corners_bitmap_size },
  294. Color::Transparent,
  295. { static_cast<float>(top_left.horizontal_radius), static_cast<float>(top_left.vertical_radius) },
  296. { static_cast<float>(top_right.horizontal_radius), static_cast<float>(top_right.vertical_radius) },
  297. { static_cast<float>(bottom_right.horizontal_radius), static_cast<float>(bottom_right.vertical_radius) },
  298. { static_cast<float>(bottom_left.horizontal_radius), static_cast<float>(bottom_left.vertical_radius) },
  299. AccelGfx::Painter::BlendingMode::AlphaOverride);
  300. auto const& target_canvas = painter().canvas();
  301. if (!corner_clipper.sample_canvas_top_left_rect.is_empty())
  302. corner_painter->blit_canvas(corner_clipper.sample_canvas_top_left_rect, target_canvas, painter().transform().map(corner_clipper.page_top_left_rect), 1.0f, {}, AccelGfx::Painter::BlendingMode::AlphaPreserve);
  303. if (!corner_clipper.sample_canvas_top_right_rect.is_empty())
  304. corner_painter->blit_canvas(corner_clipper.sample_canvas_top_right_rect, target_canvas, painter().transform().map(corner_clipper.page_top_right_rect), 1.0f, {}, AccelGfx::Painter::BlendingMode::AlphaPreserve);
  305. if (!corner_clipper.sample_canvas_bottom_right_rect.is_empty())
  306. corner_painter->blit_canvas(corner_clipper.sample_canvas_bottom_right_rect, target_canvas, painter().transform().map(corner_clipper.page_bottom_right_rect), 1.0f, {}, AccelGfx::Painter::BlendingMode::AlphaPreserve);
  307. if (!corner_clipper.sample_canvas_bottom_left_rect.is_empty())
  308. corner_painter->blit_canvas(corner_clipper.sample_canvas_bottom_left_rect, target_canvas, painter().transform().map(corner_clipper.page_bottom_left_rect), 1.0f, {}, AccelGfx::Painter::BlendingMode::AlphaPreserve);
  309. return CommandResult::Continue;
  310. }
  311. CommandResult CommandExecutorGPU::blit_corner_clipping(u32 id)
  312. {
  313. auto const& corner_clipper = *m_corner_clippers[id];
  314. auto const& corner_sample_canvas = *corner_clipper.corners_sample_canvas;
  315. if (!corner_clipper.sample_canvas_top_left_rect.is_empty())
  316. painter().blit_canvas(corner_clipper.page_top_left_rect, corner_sample_canvas, corner_clipper.sample_canvas_top_left_rect);
  317. if (!corner_clipper.sample_canvas_top_right_rect.is_empty())
  318. painter().blit_canvas(corner_clipper.page_top_right_rect, corner_sample_canvas, corner_clipper.sample_canvas_top_right_rect);
  319. if (!corner_clipper.sample_canvas_bottom_right_rect.is_empty())
  320. painter().blit_canvas(corner_clipper.page_bottom_right_rect, corner_sample_canvas, corner_clipper.sample_canvas_bottom_right_rect);
  321. if (!corner_clipper.sample_canvas_bottom_left_rect.is_empty())
  322. painter().blit_canvas(corner_clipper.page_bottom_left_rect, corner_sample_canvas, corner_clipper.sample_canvas_bottom_left_rect);
  323. m_corner_clippers[id].clear();
  324. return CommandResult::Continue;
  325. }
  326. CommandResult CommandExecutorGPU::paint_borders(DevicePixelRect const& border_rect, CornerRadii const& corner_radii, BordersDataDevicePixels const& borders_data)
  327. {
  328. // FIXME: Add support for corner radiuses
  329. (void)corner_radii;
  330. Gfx::IntRect top_border_rect = {
  331. border_rect.x(),
  332. border_rect.y(),
  333. border_rect.width(),
  334. borders_data.top.width
  335. };
  336. Gfx::IntRect right_border_rect = {
  337. border_rect.x() + (border_rect.width() - borders_data.right.width),
  338. border_rect.y(),
  339. borders_data.right.width,
  340. border_rect.height()
  341. };
  342. Gfx::IntRect bottom_border_rect = {
  343. border_rect.x(),
  344. border_rect.y() + (border_rect.height() - borders_data.bottom.width),
  345. border_rect.width(),
  346. borders_data.bottom.width
  347. };
  348. Gfx::IntRect left_border_rect = {
  349. border_rect.x(),
  350. border_rect.y(),
  351. borders_data.left.width,
  352. border_rect.height()
  353. };
  354. if (borders_data.top.width > 0)
  355. painter().fill_rect(top_border_rect, borders_data.top.color);
  356. if (borders_data.right.width > 0)
  357. painter().fill_rect(right_border_rect, borders_data.right.color);
  358. if (borders_data.bottom.width > 0)
  359. painter().fill_rect(bottom_border_rect, borders_data.bottom.color);
  360. if (borders_data.left.width > 0)
  361. painter().fill_rect(left_border_rect, borders_data.left.color);
  362. return CommandResult::Continue;
  363. }
  364. bool CommandExecutorGPU::would_be_fully_clipped_by_painter(Gfx::IntRect rect) const
  365. {
  366. auto translation = painter().transform().translation().to_type<int>();
  367. return !painter().clip_rect().intersects(rect.translated(translation));
  368. }
  369. void CommandExecutorGPU::prepare_glyph_texture(HashMap<Gfx::Font const*, HashTable<u32>> const& unique_glyphs)
  370. {
  371. AccelGfx::GlyphAtlas::the().update(unique_glyphs);
  372. }
  373. void CommandExecutorGPU::prepare_to_execute()
  374. {
  375. m_context.activate();
  376. }
  377. void CommandExecutorGPU::update_immutable_bitmap_texture_cache(HashMap<u32, Gfx::ImmutableBitmap const*>& immutable_bitmaps)
  378. {
  379. painter().update_immutable_bitmap_texture_cache(immutable_bitmaps);
  380. }
  381. }