CINXE.COM
<!doctype html><html lang="en"><head><title data-rh="true">Getting started with Flutter GPU. Build custom renderers and render 3D… | by Brandon DeRosier | Flutter | Medium</title><meta data-rh="true" charset="utf-8"/><meta data-rh="true" name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1,maximum-scale=1"/><meta data-rh="true" name="theme-color" content="#000000"/><meta data-rh="true" name="twitter:app:name:iphone" content="Medium"/><meta data-rh="true" name="twitter:app:id:iphone" content="828256236"/><meta data-rh="true" property="al:ios:app_name" content="Medium"/><meta data-rh="true" property="al:ios:app_store_id" content="828256236"/><meta data-rh="true" property="al:android:package" content="com.medium.reader"/><meta data-rh="true" property="fb:app_id" content="542599432471018"/><meta data-rh="true" property="og:site_name" content="Medium"/><meta data-rh="true" property="og:type" content="article"/><meta data-rh="true" property="article:published_time" content="2024-10-21T19:08:56.281Z"/><meta data-rh="true" name="title" content="Getting started with Flutter GPU. Build custom renderers and render 3D… | by Brandon DeRosier | Flutter | Medium"/><meta data-rh="true" property="og:title" content="Getting started with Flutter GPU"/><meta data-rh="true" property="al:android:url" content="medium://p/f33d497b7c11"/><meta data-rh="true" property="al:ios:url" content="medium://p/f33d497b7c11"/><meta data-rh="true" property="al:android:app_name" content="Medium"/><meta data-rh="true" name="description" content="The Flutter 3.24 release introduces a new low-level graphics API called Flutter GPU. There is also a 3D rendering library powered by Flutter GPU called Flutter Scene (package: flutter_scene). Both…"/><meta data-rh="true" property="og:description" content="Build custom renderers and render 3D scenes in Flutter."/><meta data-rh="true" property="og:url" content="https://medium.com/flutter/getting-started-with-flutter-gpu-f33d497b7c11"/><meta data-rh="true" property="al:web:url" content="https://medium.com/flutter/getting-started-with-flutter-gpu-f33d497b7c11"/><meta data-rh="true" property="og:image" content="https://miro.medium.com/v2/da:true/resize:fit:506/1*jfeUgpEP9AgAz94yVxVW1g.gif"/><meta data-rh="true" property="article:author" content="https://medium.com/@algebrandon"/><meta data-rh="true" name="author" content="Brandon DeRosier"/><meta data-rh="true" name="robots" content="index,noarchive,follow,max-image-preview:large"/><meta data-rh="true" name="referrer" content="unsafe-url"/><meta data-rh="true" property="twitter:title" content="Getting started with Flutter GPU"/><meta data-rh="true" name="twitter:site" content="@flutterdev"/><meta data-rh="true" name="twitter:app:url:iphone" content="medium://p/f33d497b7c11"/><meta data-rh="true" property="twitter:description" content="Build custom renderers and render 3D scenes in Flutter."/><meta data-rh="true" name="twitter:image:src" content="https://miro.medium.com/v2/da:true/resize:fit:506/1*jfeUgpEP9AgAz94yVxVW1g.gif"/><meta data-rh="true" name="twitter:card" content="summary_large_image"/><meta data-rh="true" name="twitter:label1" content="Reading time"/><meta data-rh="true" name="twitter:data1" content="17 min read"/><link data-rh="true" rel="icon" href="https://miro.medium.com/v2/5d8de952517e8160e40ef9841c781cdc14a5db313057fa3c3de41c6f5b494b19"/><link data-rh="true" rel="search" type="application/opensearchdescription+xml" title="Medium" href="/osd.xml"/><link data-rh="true" rel="apple-touch-icon" sizes="152x152" href="https://miro.medium.com/v2/resize:fill:304:304/10fd5c419ac61637245384e7099e131627900034828f4f386bdaa47a74eae156"/><link data-rh="true" rel="apple-touch-icon" sizes="120x120" href="https://miro.medium.com/v2/resize:fill:240:240/10fd5c419ac61637245384e7099e131627900034828f4f386bdaa47a74eae156"/><link data-rh="true" rel="apple-touch-icon" sizes="76x76" href="https://miro.medium.com/v2/resize:fill:152:152/10fd5c419ac61637245384e7099e131627900034828f4f386bdaa47a74eae156"/><link data-rh="true" rel="apple-touch-icon" sizes="60x60" href="https://miro.medium.com/v2/resize:fill:120:120/10fd5c419ac61637245384e7099e131627900034828f4f386bdaa47a74eae156"/><link data-rh="true" rel="mask-icon" href="https://miro.medium.com/v2/resize:fill:1000:1000/7*GAOKVe--MXbEJmV9230oOQ.png" color="#171717"/><link data-rh="true" rel="preconnect" href="https://glyph.medium.com" crossOrigin=""/><link data-rh="true" id="glyph_preload_link" rel="preload" as="style" type="text/css" href="https://glyph.medium.com/css/unbound.css"/><link data-rh="true" id="glyph_link" rel="stylesheet" type="text/css" href="https://glyph.medium.com/css/unbound.css"/><link data-rh="true" rel="author" href="https://medium.com/@algebrandon"/><link data-rh="true" rel="canonical" href="https://medium.com/flutter/getting-started-with-flutter-gpu-f33d497b7c11"/><link data-rh="true" rel="alternate" href="android-app://com.medium.reader/https/medium.com/p/f33d497b7c11"/><script data-rh="true" type="application/ld+json">{"@context":"http:\u002F\u002Fschema.org","@type":"NewsArticle","image":["https:\u002F\u002Fmiro.medium.com\u002Fv2\u002Fda:true\u002Fresize:fit:1200\u002F1*jfeUgpEP9AgAz94yVxVW1g.gif"],"url":"https:\u002F\u002Fmedium.com\u002Fflutter\u002Fgetting-started-with-flutter-gpu-f33d497b7c11","dateCreated":"2024-08-06T18:02:39.237Z","datePublished":"2024-08-06T18:02:39.237Z","dateModified":"2024-11-19T04:29:34.439Z","headline":"Getting started with Flutter GPU - Flutter - Medium","name":"Getting started with Flutter GPU - Flutter - Medium","description":"The Flutter 3.24 release introduces a new low-level graphics API called Flutter GPU. There is also a 3D rendering library powered by Flutter GPU called Flutter Scene (package: flutter_scene). Both…","identifier":"f33d497b7c11","author":{"@type":"Person","name":"Brandon DeRosier","url":"https:\u002F\u002Fmedium.com\u002F@algebrandon"},"creator":["Brandon DeRosier"],"publisher":{"@type":"Organization","name":"Flutter","url":"https:\u002F\u002Fmedium.com\u002Fflutter","logo":{"@type":"ImageObject","width":228,"height":60,"url":"https:\u002F\u002Fmiro.medium.com\u002Fv2\u002Fresize:fit:456\u002F1*KvnfbD1F5CzEsU9wSmRZyA.png"}},"mainEntityOfPage":"https:\u002F\u002Fmedium.com\u002Fflutter\u002Fgetting-started-with-flutter-gpu-f33d497b7c11"}</script><style type="text/css" data-fela-rehydration="587" data-fela-type="STATIC">html{box-sizing:border-box;-webkit-text-size-adjust:100%}*, *:before, *:after{box-sizing:inherit}body{margin:0;padding:0;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;color:rgba(0,0,0,0.8);position:relative;min-height:100vh}h1, h2, h3, h4, h5, h6, dl, dd, ol, ul, menu, figure, blockquote, p, pre, form{margin:0}menu, ol, ul{padding:0;list-style:none;list-style-image:none}main{display:block}a{color:inherit;text-decoration:none}a, button, input{-webkit-tap-highlight-color:transparent}img, svg{vertical-align:middle}button{background:transparent;overflow:visible}button, input, optgroup, select, textarea{margin:0}:root{--reach-tabs:1;--reach-menu-button:1}#speechify-root{font-family:Sohne, sans-serif}div[data-popper-reference-hidden="true"]{visibility:hidden;pointer-events:none}.grecaptcha-badge{visibility:hidden} /*XCode style (c) Angel Garcia <angelgarcia.mail@gmail.com>*/.hljs {background: #fff;color: black; }/* Gray DOCTYPE selectors like WebKit */ .xml .hljs-meta {color: #c0c0c0; }.hljs-comment, .hljs-quote {color: #007400; }.hljs-tag, .hljs-attribute, .hljs-keyword, .hljs-selector-tag, .hljs-literal, .hljs-name {color: #aa0d91; }.hljs-variable, .hljs-template-variable {color: #3F6E74; }.hljs-code, .hljs-string, .hljs-meta .hljs-string {color: #c41a16; }.hljs-regexp, .hljs-link {color: #0E0EFF; }.hljs-title, .hljs-symbol, .hljs-bullet, .hljs-number {color: #1c00cf; }.hljs-section, .hljs-meta {color: #643820; }.hljs-title.class_, .hljs-class .hljs-title, .hljs-type, .hljs-built_in, .hljs-params {color: #5c2699; }.hljs-attr {color: #836C28; }.hljs-subst {color: #000; }.hljs-formula {background-color: #eee;font-style: italic; }.hljs-addition {background-color: #baeeba; }.hljs-deletion {background-color: #ffc8bd; }.hljs-selector-id, .hljs-selector-class {color: #9b703f; }.hljs-doctag, .hljs-strong {font-weight: bold; }.hljs-emphasis {font-style: italic; } </style><style type="text/css" data-fela-rehydration="587" data-fela-type="KEYFRAME">@-webkit-keyframes k1{0%{opacity:0.8}50%{opacity:0.5}100%{opacity:0.8}}@-moz-keyframes k1{0%{opacity:0.8}50%{opacity:0.5}100%{opacity:0.8}}@keyframes k1{0%{opacity:0.8}50%{opacity:0.5}100%{opacity:0.8}}</style><style type="text/css" data-fela-rehydration="587" data-fela-type="RULE">.a{font-family:medium-content-sans-serif-font, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif}.b{font-weight:400}.c{background-color:rgba(255, 255, 255, 1)}.l{display:block}.m{position:sticky}.n{top:0}.o{z-index:500}.p{padding:0 24px}.q{align-items:center}.r{border-bottom:solid 1px #F2F2F2}.y{height:41px}.z{line-height:20px}.ab{display:flex}.ac{height:57px}.ae{flex:1 0 auto}.af{color:inherit}.ag{fill:inherit}.ah{font-size:inherit}.ai{border:inherit}.aj{font-family:inherit}.ak{letter-spacing:inherit}.al{font-weight:inherit}.am{padding:0}.an{margin:0}.ao{cursor:pointer}.ap:disabled{cursor:not-allowed}.aq:disabled{color:#6B6B6B}.ar:disabled{fill:#6B6B6B}.au{width:auto}.av path{fill:#242424}.aw{height:25px}.ax{margin-left:16px}.ay{border:none}.az{border-radius:20px}.ba{width:240px}.bb{background:#F9F9F9}.bc path{fill:#6B6B6B}.be{outline:none}.bf{font-family:sohne, "Helvetica Neue", Helvetica, Arial, sans-serif}.bg{font-size:14px}.bh{width:100%}.bi{padding:10px 20px 10px 0}.bj{background-color:transparent}.bk{color:#242424}.bl::placeholder{color:#6B6B6B}.bm{display:inline-block}.bn{margin-left:12px}.bo{margin-right:12px}.bp{border-radius:4px}.bq{margin-left:24px}.br{height:24px}.bx{background-color:#F9F9F9}.by{border-radius:50%}.bz{height:32px}.ca{width:32px}.cb{justify-content:center}.ch{max-width:680px}.ci{min-width:0}.cj{animation:k1 1.2s ease-in-out infinite}.ck{height:100vh}.cl{margin-bottom:16px}.cm{margin-top:48px}.cn{align-items:flex-start}.co{flex-direction:column}.cp{justify-content:space-between}.cq{margin-bottom:24px}.cw{width:80%}.cx{background-color:#F2F2F2}.dd{height:44px}.de{width:44px}.df{margin:auto 0}.dg{margin-bottom:4px}.dh{height:16px}.di{width:120px}.dj{width:80px}.dp{margin-bottom:8px}.dq{width:96%}.dr{width:98%}.ds{width:81%}.dt{margin-left:8px}.du{color:#6B6B6B}.dv{font-size:13px}.dw{height:100%}.ep{color:#FFFFFF}.eq{fill:#FFFFFF}.er{background:rgba(73, 139, 209, 1)}.es{border-color:rgba(73, 139, 209, 1)}.ew:disabled{cursor:inherit !important}.ex:disabled{opacity:0.3}.ey:disabled:hover{background:rgba(73, 139, 209, 1)}.ez:disabled:hover{border-color:rgba(73, 139, 209, 1)}.fa{border-radius:99em}.fb{border-width:1px}.fc{border-style:solid}.fd{box-sizing:border-box}.fe{text-decoration:none}.ff{text-align:center}.fi{margin-right:32px}.fj{position:relative}.fk{fill:#6B6B6B}.fn{background:transparent}.fo svg{margin-left:4px}.fp svg{fill:#6B6B6B}.fr{box-shadow:inset 0 0 0 1px rgba(0, 0, 0, 0.05)}.fs{position:absolute}.fz{margin:0 24px}.gd{background:rgba(255, 255, 255, 1)}.ge{border:1px solid #F2F2F2}.gf{box-shadow:0 1px 4px #F2F2F2}.gg{max-height:100vh}.gh{overflow-y:auto}.gi{left:0}.gj{top:calc(100vh + 100px)}.gk{bottom:calc(100vh + 100px)}.gl{width:10px}.gm{pointer-events:none}.gn{word-break:break-word}.go{word-wrap:break-word}.gp:after{display:block}.gq:after{content:""}.gr:after{clear:both}.gs{line-height:1.23}.gt{letter-spacing:0}.gu{font-style:normal}.gv{font-weight:700}.hq{margin-bottom:-0.27em}.hr{line-height:1.394}.im{align-items:baseline}.in{width:48px}.io{height:48px}.ip{border:2px solid rgba(255, 255, 255, 1)}.iq{z-index:0}.ir{box-shadow:none}.is{border:1px solid rgba(0, 0, 0, 0.05)}.it{margin-left:-12px}.iu{width:28px}.iv{height:28px}.iw{z-index:1}.ix{width:24px}.iy{margin-bottom:2px}.iz{flex-wrap:nowrap}.ja{font-size:16px}.jb{line-height:24px}.jd{margin:0 8px}.je{display:inline}.jf{color:rgba(73, 139, 209, 1)}.jg{fill:rgba(73, 139, 209, 1)}.jj{flex:0 0 auto}.jm{flex-wrap:wrap}.jn{white-space:pre-wrap}.jo{margin-right:4px}.jp{overflow:hidden}.jq{max-height:20px}.jr{text-overflow:ellipsis}.js{display:-webkit-box}.jt{-webkit-line-clamp:1}.ju{-webkit-box-orient:vertical}.jv{word-break:break-all}.jx{padding-left:8px}.jy{padding-right:8px}.kz> *{flex-shrink:0}.la{overflow-x:scroll}.lb::-webkit-scrollbar{display:none}.lc{scrollbar-width:none}.ld{-ms-overflow-style:none}.le{width:74px}.lf{flex-direction:row}.lg{z-index:2}.lj{-webkit-user-select:none}.lk{border:0}.ll{fill:rgba(117, 117, 117, 1)}.lo{outline:0}.lp{user-select:none}.lq> svg{pointer-events:none}.lz{cursor:progress}.ma{margin-left:4px}.mb{margin-top:0px}.mc{opacity:1}.md{padding:4px 0}.mg{width:16px}.mi{display:inline-flex}.mo{max-width:100%}.mp{padding:8px 2px}.mq svg{color:#6B6B6B}.nh{line-height:1.58}.ni{letter-spacing:-0.004em}.nj{font-family:source-serif-pro, Georgia, Cambria, "Times New Roman", Times, serif}.oc{margin-bottom:-0.46em}.od{text-decoration:underline}.oe{padding:2px 4px}.of{font-size:75%}.og> strong{font-family:inherit}.oh{font-family:source-code-pro, Menlo, Monaco, "Courier New", Courier, monospace}.oi{list-style-type:decimal}.oj{margin-left:30px}.ok{padding-left:0px}.oq{margin-top:32px}.or{margin-bottom:14px}.os{padding-top:24px}.ot{padding-bottom:10px}.ou{background-color:#000000}.ov{height:3px}.ow{width:3px}.ox{margin-right:20px}.oy{line-height:1.12}.oz{letter-spacing:-0.022em}.pa{font-weight:600}.pt{margin-bottom:-0.28em}.pz{margin-left:auto}.qa{margin-right:auto}.qb{max-width:900px}.qh{clear:both}.qj{cursor:zoom-in}.qk{z-index:auto}.qm{height:auto}.qn{margin-top:10px}.qo{max-width:728px}.qw{list-style-type:disc}.qx{max-width:1600px}.qy{overflow-x:auto}.qz{padding:32px}.ra{border:1px solid #E5E5E5}.rb{line-height:1.4}.rc{margin-top:-0.2em}.rd{margin-bottom:-0.2em}.re{white-space:pre}.rf{min-width:fit-content}.rg{max-width:1389px}.rh{max-width:912px}.ri{font-style:italic}.rj{max-width:796px}.rk{max-width:506px}.rl{margin-bottom:26px}.rm{margin-top:6px}.rn{margin-top:8px}.ro{margin-right:8px}.rp{padding:8px 16px}.rq{border-radius:100px}.rr{transition:background 300ms ease}.rt{white-space:nowrap}.ru{border-top:none}.rv{margin-bottom:50px}.rw{height:52px}.rx{max-height:52px}.ry{box-sizing:content-box}.rz{position:static}.sb{max-width:155px}.sh{margin-bottom:64px}.si{margin-bottom:48px}.sw{border-radius:2px}.sy{height:64px}.sz{width:64px}.ta{align-self:flex-end}.tb{flex:1 1 auto}.th{padding-right:4px}.ti{font-weight:500}.tv{margin-top:16px}.tw{color:rgba(255, 255, 255, 1)}.tx{fill:rgba(255, 255, 255, 1)}.ty{background:rgba(25, 25, 25, 1)}.tz{border-color:rgba(25, 25, 25, 1)}.uc:disabled{opacity:0.1}.ud:disabled:hover{background:rgba(25, 25, 25, 1)}.ue:disabled:hover{border-color:rgba(25, 25, 25, 1)}.uk{height:0px}.ul{gap:18px}.um{fill:rgba(61, 61, 61, 1)}.uo{padding-bottom:6px}.up{border-bottom:1px solid #F2F2F2}.uv{fill:#242424}.uw{background:0}.ux{border-color:#242424}.uy:disabled:hover{color:#242424}.uz:disabled:hover{fill:#242424}.va:disabled:hover{border-color:#242424}.vl{border-bottom:solid 1px #E5E5E5}.vm{margin-top:72px}.vn{padding:24px 0}.vo{margin-bottom:0px}.vp{margin-right:16px}.as:hover:not(:disabled){color:rgba(25, 25, 25, 1)}.at:hover:not(:disabled){fill:rgba(25, 25, 25, 1)}.et:hover{background:rgba(68, 119, 175, 1)}.eu:hover{border-color:rgba(68, 119, 175, 1)}.ev:hover{cursor:pointer}.fl:hover{color:#242424}.fm:hover{fill:#242424}.fq:hover svg{fill:#242424}.ft:hover{background-color:rgba(0, 0, 0, 0.1)}.jc:hover{text-decoration:underline}.jh:hover:not(:disabled){color:rgba(68, 119, 175, 1)}.ji:hover:not(:disabled){fill:rgba(68, 119, 175, 1)}.ln:hover{fill:rgba(8, 8, 8, 1)}.me:hover{fill:#000000}.mf:hover p{color:#000000}.mh:hover{color:#000000}.mr:hover svg{color:#000000}.rs:hover{background-color:#F2F2F2}.sx:hover{background-color:none}.ua:hover{background:#000000}.ub:hover{border-color:#242424}.un:hover{fill:rgba(25, 25, 25, 1)}.bd:focus-within path{fill:#242424}.lm:focus{fill:rgba(8, 8, 8, 1)}.ms:focus svg{color:#000000}.ql:focus{transform:scale(1.01)}.lr:active{border-style:none}</style><style type="text/css" data-fela-rehydration="587" data-fela-type="RULE" media="all and (min-width: 1080px)">.d{display:none}.bw{width:64px}.cg{margin:0 64px}.cv{height:48px}.dc{margin-bottom:52px}.do{margin-bottom:48px}.ef{font-size:14px}.eg{line-height:20px}.em{font-size:13px}.eo{padding:5px 12px}.fh{display:flex}.fy{margin-bottom:50px}.gc{max-width:680px}.hm{font-size:42px}.hn{margin-top:1.19em}.ho{line-height:52px}.hp{letter-spacing:-0.011em}.ie{font-size:22px}.if{margin-top:0.92em}.ig{line-height:28px}.il{align-items:center}.kl{border-top:solid 1px #F2F2F2}.km{border-bottom:solid 1px #F2F2F2}.kn{margin:32px 0 0}.ko{padding:3px 8px}.kx> *{margin-right:24px}.ky> :last-child{margin-right:0}.ly{margin-top:0px}.mn{margin:0}.ny{font-size:20px}.nz{margin-top:2.14em}.oa{line-height:32px}.ob{letter-spacing:-0.003em}.op{margin-top:1.14em}.pp{font-size:24px}.pq{margin-top:1.25em}.pr{line-height:30px}.ps{letter-spacing:-0.016em}.py{margin-top:0.94em}.qg{margin-top:56px}.qv{margin-top:1.95em}.sg{display:inline-block}.sj{flex-direction:row}.sm{margin-bottom:0}.sn{margin-right:20px}.tc{max-width:500px}.tt{line-height:24px}.tu{letter-spacing:0}.uj{margin-bottom:88px}.uu{margin:40px 0 16px}.vf{width:min-width}.vk{padding-top:72px}</style><style type="text/css" data-fela-rehydration="587" data-fela-type="RULE" media="all and (max-width: 1079.98px)">.e{display:none}.lx{margin-top:0px}.qp{margin-left:auto}.qq{text-align:center}.sf{display:inline-block}</style><style type="text/css" data-fela-rehydration="587" data-fela-type="RULE" media="all and (max-width: 903.98px)">.f{display:none}.lw{margin-top:0px}.se{display:inline-block}</style><style type="text/css" data-fela-rehydration="587" data-fela-type="RULE" media="all and (max-width: 727.98px)">.g{display:none}.lu{margin-top:0px}.lv{margin-right:0px}.sd{display:inline-block}</style><style type="text/css" data-fela-rehydration="587" data-fela-type="RULE" media="all and (max-width: 551.98px)">.h{display:none}.s{display:flex}.t{justify-content:space-between}.bs{width:24px}.cc{margin:0 24px}.cr{height:40px}.cy{margin-bottom:44px}.dk{margin-bottom:32px}.dx{font-size:13px}.dy{line-height:20px}.eh{padding:0px 8px 1px}.fu{margin-bottom:2px}.gw{font-size:32px}.gx{margin-top:1.01em}.gy{line-height:38px}.gz{letter-spacing:-0.014em}.hs{font-size:18px}.ht{margin-top:0.79em}.hu{line-height:24px}.ih{align-items:flex-start}.jk{flex-direction:column}.jz{margin:24px -24px 0}.ka{padding:0}.kp> *{margin-right:8px}.kq> :last-child{margin-right:24px}.lh{margin-left:0px}.ls{margin-top:0px}.lt{margin-right:0px}.mj{margin:0}.mt{border:1px solid #F2F2F2}.mu{border-radius:99em}.mv{padding:0px 16px 0px 12px}.mw{height:38px}.mx{align-items:center}.mz svg{margin-right:8px}.nk{margin-top:1.56em}.nl{line-height:28px}.nm{letter-spacing:-0.003em}.ol{margin-top:1.34em}.pb{font-size:20px}.pc{margin-top:0.93em}.pd{letter-spacing:0}.pu{margin-top:0.67em}.qc{margin-top:40px}.qr{margin-top:1.2em}.sc{display:inline-block}.su{margin-bottom:20px}.sv{margin-right:0}.tg{max-width:100%}.tj{font-size:24px}.tk{line-height:30px}.tl{letter-spacing:-0.016em}.uf{margin-bottom:64px}.uq{margin:32px 0 16px}.vb{width:100%}.vg{padding-top:48px}.my:hover{border-color:#E5E5E5}</style><style type="text/css" data-fela-rehydration="587" data-fela-type="RULE" media="all and (min-width: 904px) and (max-width: 1079.98px)">.i{display:none}.bv{width:64px}.cf{margin:0 64px}.cu{height:48px}.db{margin-bottom:52px}.dn{margin-bottom:48px}.ed{font-size:14px}.ee{line-height:20px}.ek{font-size:13px}.el{padding:5px 12px}.fg{display:flex}.fx{margin-bottom:50px}.gb{max-width:680px}.hi{font-size:42px}.hj{margin-top:1.19em}.hk{line-height:52px}.hl{letter-spacing:-0.011em}.ib{font-size:22px}.ic{margin-top:0.92em}.id{line-height:28px}.ik{align-items:center}.kh{border-top:solid 1px #F2F2F2}.ki{border-bottom:solid 1px #F2F2F2}.kj{margin:32px 0 0}.kk{padding:3px 8px}.kv> *{margin-right:24px}.kw> :last-child{margin-right:0}.mm{margin:0}.nu{font-size:20px}.nv{margin-top:2.14em}.nw{line-height:32px}.nx{letter-spacing:-0.003em}.oo{margin-top:1.14em}.pl{font-size:24px}.pm{margin-top:1.25em}.pn{line-height:30px}.po{letter-spacing:-0.016em}.px{margin-top:0.94em}.qf{margin-top:56px}.qu{margin-top:1.95em}.sk{flex-direction:row}.so{margin-bottom:0}.sp{margin-right:20px}.td{max-width:500px}.tr{line-height:24px}.ts{letter-spacing:0}.ui{margin-bottom:88px}.ut{margin:40px 0 16px}.ve{width:min-width}.vj{padding-top:72px}</style><style type="text/css" data-fela-rehydration="587" data-fela-type="RULE" media="all and (min-width: 728px) and (max-width: 903.98px)">.j{display:none}.w{display:flex}.x{justify-content:space-between}.bu{width:64px}.ce{margin:0 48px}.ct{height:48px}.da{margin-bottom:52px}.dm{margin-bottom:48px}.eb{font-size:13px}.ec{line-height:20px}.ej{padding:0px 8px 1px}.fw{margin-bottom:50px}.ga{max-width:680px}.he{font-size:42px}.hf{margin-top:1.19em}.hg{line-height:52px}.hh{letter-spacing:-0.011em}.hy{font-size:22px}.hz{margin-top:0.92em}.ia{line-height:28px}.ij{align-items:center}.kd{border-top:solid 1px #F2F2F2}.ke{border-bottom:solid 1px #F2F2F2}.kf{margin:32px 0 0}.kg{padding:3px 8px}.kt> *{margin-right:24px}.ku> :last-child{margin-right:0}.ml{margin:0}.nq{font-size:20px}.nr{margin-top:2.14em}.ns{line-height:32px}.nt{letter-spacing:-0.003em}.on{margin-top:1.14em}.ph{font-size:24px}.pi{margin-top:1.25em}.pj{line-height:30px}.pk{letter-spacing:-0.016em}.pw{margin-top:0.94em}.qe{margin-top:56px}.qt{margin-top:1.95em}.sl{flex-direction:row}.sq{margin-bottom:0}.sr{margin-right:20px}.te{max-width:500px}.tp{line-height:24px}.tq{letter-spacing:0}.uh{margin-bottom:88px}.us{margin:40px 0 16px}.vd{width:min-width}.vi{padding-top:72px}</style><style type="text/css" data-fela-rehydration="587" data-fela-type="RULE" media="all and (min-width: 552px) and (max-width: 727.98px)">.k{display:none}.u{display:flex}.v{justify-content:space-between}.bt{width:24px}.cd{margin:0 24px}.cs{height:40px}.cz{margin-bottom:44px}.dl{margin-bottom:32px}.dz{font-size:13px}.ea{line-height:20px}.ei{padding:0px 8px 1px}.fv{margin-bottom:2px}.ha{font-size:32px}.hb{margin-top:1.01em}.hc{line-height:38px}.hd{letter-spacing:-0.014em}.hv{font-size:18px}.hw{margin-top:0.79em}.hx{line-height:24px}.ii{align-items:flex-start}.jl{flex-direction:column}.kb{margin:24px 0 0}.kc{padding:0}.kr> *{margin-right:8px}.ks> :last-child{margin-right:8px}.li{margin-left:0px}.mk{margin:0}.na{border:1px solid #F2F2F2}.nb{border-radius:99em}.nc{padding:0px 16px 0px 12px}.nd{height:38px}.ne{align-items:center}.ng svg{margin-right:8px}.nn{margin-top:1.56em}.no{line-height:28px}.np{letter-spacing:-0.003em}.om{margin-top:1.34em}.pe{font-size:20px}.pf{margin-top:0.93em}.pg{letter-spacing:0}.pv{margin-top:0.67em}.qd{margin-top:40px}.qs{margin-top:1.2em}.ss{margin-bottom:20px}.st{margin-right:0}.tf{max-width:100%}.tm{font-size:24px}.tn{line-height:30px}.to{letter-spacing:-0.016em}.ug{margin-bottom:64px}.ur{margin:32px 0 16px}.vc{width:100%}.vh{padding-top:48px}.nf:hover{border-color:#E5E5E5}</style><style type="text/css" data-fela-rehydration="587" data-fela-type="RULE" media="print">.sa{display:none}</style><style type="text/css" data-fela-rehydration="587" data-fela-type="RULE" media="(orientation: landscape) and (max-width: 903.98px)">.jw{max-height:none}</style><style type="text/css" data-fela-rehydration="587" data-fela-type="RULE" media="(prefers-reduced-motion: no-preference)">.qi{transition:transform 300ms cubic-bezier(0.2, 0, 0.2, 1)}</style></head><body><div id="root"><div class="a b c"><div class="d e f g h i j k"></div><script>document.domain = document.domain;</script><div class="l c"><div class="l m n o c"><div class="p q r s t u v w x i d y z"><a class="du ag dv bf ak b am an ao ap aq ar as at s u w i d q dw z" href="https://rsci.app.link/?%24canonical_url=https%3A%2F%2Fmedium.com%2Fp%2Ff33d497b7c11&%7Efeature=LoOpenInAppButton&%7Echannel=ShowPostUnderCollection&source=---top_nav_layout_nav-----------------------------------------" rel="noopener follow">Open in app<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" fill="none" viewBox="0 0 10 10" class="dt"><path fill="currentColor" d="M.985 8.485a.375.375 0 1 0 .53.53zM8.75 1.25h.375A.375.375 0 0 0 8.75.875zM8.375 6.5a.375.375 0 1 0 .75 0zM3.5.875a.375.375 0 1 0 0 .75zm-1.985 8.14 7.5-7.5-.53-.53-7.5 7.5zm6.86-7.765V6.5h.75V1.25zM3.5 1.625h5.25v-.75H3.5z"></path></svg></a><div class="ab q"><p class="bf b dx dy dz ea eb ec ed ee ef eg du"><span><button class="bf b dx dy eh dz ea ei eb ec ej ek ee el em eg eo ep eq er es et eu ev ew ex ey ez fa fb fc fd bm fe ff" data-testid="headerSignUpButton">Sign up</button></span></p><div class="ax l"><p class="bf b dx dy dz ea eb ec ed ee ef eg du"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerSignInButton" rel="noopener follow" href="/m/signin?operation=login&redirect=https%3A%2F%2Fmedium.com%2Fflutter%2Fgetting-started-with-flutter-gpu-f33d497b7c11&source=post_page---top_nav_layout_nav-----------------------global_nav------------------">Sign in</a></span></p></div></div></div><div class="p q r ab ac"><div class="ab q ae"><a class="af ag ah ai aj ak al am an ao ap aq ar as at ab" aria-label="Homepage" data-testid="headerMediumLogo" rel="noopener follow" href="/?source=---top_nav_layout_nav-----------------------------------------"><svg xmlns="http://www.w3.org/2000/svg" width="719" height="160" fill="none" viewBox="0 0 719 160" class="au av aw"><path fill="#242424" d="m174.104 9.734.215-.047V8.02H130.39L89.6 103.89 48.81 8.021H1.472v1.666l.212.047c8.018 1.81 12.09 4.509 12.09 14.242V137.93c0 9.734-4.087 12.433-12.106 14.243l-.212.047v1.671h32.118v-1.665l-.213-.048c-8.018-1.809-12.089-4.509-12.089-14.242V30.586l52.399 123.305h2.972l53.925-126.743V140.75c-.687 7.688-4.721 10.062-11.982 11.701l-.215.05v1.652h55.948v-1.652l-.215-.05c-7.269-1.639-11.4-4.013-12.087-11.701l-.037-116.774h.037c0-9.733 4.071-12.432 12.087-14.242m25.555 75.488c.915-20.474 8.268-35.252 20.606-35.507 3.806.063 6.998 1.312 9.479 3.714 5.272 5.118 7.751 15.812 7.368 31.793zm-.553 5.77h65.573v-.275c-.186-15.656-4.721-27.834-13.466-36.196-7.559-7.227-18.751-11.203-30.507-11.203h-.263c-6.101 0-13.584 1.48-18.909 4.16-6.061 2.807-11.407 7.003-15.855 12.511-7.161 8.874-11.499 20.866-12.554 34.343q-.05.606-.092 1.212a50 50 0 0 0-.065 1.151 85.807 85.807 0 0 0-.094 5.689c.71 30.524 17.198 54.917 46.483 54.917 25.705 0 40.675-18.791 44.407-44.013l-1.886-.664c-6.557 13.556-18.334 21.771-31.738 20.769-18.297-1.369-32.314-19.922-31.042-42.395m139.722 41.359c-2.151 5.101-6.639 7.908-12.653 7.908s-11.513-4.129-15.418-11.63c-4.197-8.053-6.405-19.436-6.405-32.92 0-28.067 8.729-46.22 22.24-46.22 5.657 0 10.111 2.807 12.236 7.704zm43.499 20.008c-8.019-1.897-12.089-4.722-12.089-14.951V1.309l-48.716 14.353v1.757l.299-.024c6.72-.543 11.278.386 13.925 2.83 2.072 1.915 3.082 4.853 3.082 8.987v18.66c-4.803-3.067-10.516-4.56-17.448-4.56-14.059 0-26.909 5.92-36.176 16.672-9.66 11.205-14.767 26.518-14.767 44.278-.003 31.72 15.612 53.039 38.851 53.039 13.595 0 24.533-7.449 29.54-20.013v16.865h43.711v-1.746zM424.1 19.819c0-9.904-7.468-17.374-17.375-17.374-9.859 0-17.573 7.632-17.573 17.374s7.721 17.374 17.573 17.374c9.907 0 17.375-7.47 17.375-17.374m11.499 132.546c-8.019-1.897-12.089-4.722-12.089-14.951h-.035V43.635l-43.714 12.551v1.705l.263.024c9.458.842 12.047 4.1 12.047 15.152v81.086h43.751v-1.746zm112.013 0c-8.018-1.897-12.089-4.722-12.089-14.951V43.635l-41.621 12.137v1.71l.246.026c7.733.813 9.967 4.257 9.967 15.36v59.279c-2.578 5.102-7.415 8.131-13.274 8.336-9.503 0-14.736-6.419-14.736-18.073V43.638l-43.714 12.55v1.703l.262.024c9.459.84 12.05 4.097 12.05 15.152v50.17a56.3 56.3 0 0 0 .91 10.444l.787 3.423c3.701 13.262 13.398 20.197 28.59 20.197 12.868 0 24.147-7.966 29.115-20.43v17.311h43.714v-1.747zm169.818 1.788v-1.749l-.213-.05c-8.7-2.006-12.089-5.789-12.089-13.49v-63.79c0-19.89-11.171-31.761-29.883-31.761-13.64 0-25.141 7.882-29.569 20.16-3.517-13.01-13.639-20.16-28.606-20.16-13.146 0-23.449 6.938-27.869 18.657V43.643L545.487 55.68v1.715l.263.024c9.345.829 12.047 4.181 12.047 14.95v81.784h40.787v-1.746l-.215-.053c-6.941-1.631-9.181-4.606-9.181-12.239V66.998c1.836-4.289 5.537-9.37 12.853-9.37 9.086 0 13.692 6.296 13.692 18.697v77.828h40.797v-1.746l-.215-.053c-6.94-1.631-9.18-4.606-9.18-12.239V75.066a42 42 0 0 0-.578-7.26c1.947-4.661 5.86-10.177 13.475-10.177 9.214 0 13.691 6.114 13.691 18.696v77.828z"></path></svg></a><div class="ax h"><div class="ab ay az ba bb q bc bd"><div class="bm" aria-hidden="false" aria-describedby="searchResults" aria-labelledby="searchResults"></div><div class="bn bo ab"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M4.092 11.06a6.95 6.95 0 1 1 13.9 0 6.95 6.95 0 0 1-13.9 0m6.95-8.05a8.05 8.05 0 1 0 5.13 14.26l3.75 3.75a.56.56 0 1 0 .79-.79l-3.73-3.73A8.05 8.05 0 0 0 11.042 3z" clip-rule="evenodd"></path></svg></div><input role="combobox" aria-controls="searchResults" aria-expanded="false" aria-label="search" data-testid="headerSearchInput" tabindex="0" class="ay be bf bg z bh bi bj bk bl" placeholder="Search" value=""/></div></div></div><div class="h k w fg fh"><div class="fi ab"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerWriteButton" rel="noopener follow" href="/m/signin?operation=register&redirect=https%3A%2F%2Fmedium.com%2Fnew-story&source=---top_nav_layout_nav-----------------------new_post_topnav------------------"><div class="bf b bg z du fj fk ab q fl fm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" aria-label="Write"><path fill="currentColor" d="M14 4a.5.5 0 0 0 0-1zm7 6a.5.5 0 0 0-1 0zm-7-7H4v1h10zM3 4v16h1V4zm1 17h16v-1H4zm17-1V10h-1v10zm-1 1a1 1 0 0 0 1-1h-1zM3 20a1 1 0 0 0 1 1v-1zM4 3a1 1 0 0 0-1 1h1z"></path><path stroke="currentColor" d="m17.5 4.5-8.458 8.458a.25.25 0 0 0-.06.098l-.824 2.47a.25.25 0 0 0 .316.316l2.47-.823a.25.25 0 0 0 .098-.06L19.5 6.5m-2-2 2.323-2.323a.25.25 0 0 1 .354 0l1.646 1.646a.25.25 0 0 1 0 .354L19.5 6.5m-2-2 2 2"></path></svg><div class="dt l">Write</div></div></a></span></div></div><div class="k j i d"><div class="fi ab"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerSearchButton" rel="noopener follow" href="/search?source=---top_nav_layout_nav-----------------------------------------"><div class="bf b bg z du fj fk ab q fl fm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" aria-label="Search"><path fill="currentColor" fill-rule="evenodd" d="M4.092 11.06a6.95 6.95 0 1 1 13.9 0 6.95 6.95 0 0 1-13.9 0m6.95-8.05a8.05 8.05 0 1 0 5.13 14.26l3.75 3.75a.56.56 0 1 0 .79-.79l-3.73-3.73A8.05 8.05 0 0 0 11.042 3z" clip-rule="evenodd"></path></svg></div></a></div></div><div class="fi h k j"><div class="ab q"><p class="bf b dx dy dz ea eb ec ed ee ef eg du"><span><button class="bf b dx dy eh dz ea ei eb ec ej ek ee el em eg eo ep eq er es et eu ev ew ex ey ez fa fb fc fd bm fe ff" data-testid="headerSignUpButton">Sign up</button></span></p><div class="ax l"><p class="bf b dx dy dz ea eb ec ed ee ef eg du"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerSignInButton" rel="noopener follow" href="/m/signin?operation=login&redirect=https%3A%2F%2Fmedium.com%2Fflutter%2Fgetting-started-with-flutter-gpu-f33d497b7c11&source=post_page---top_nav_layout_nav-----------------------global_nav------------------">Sign in</a></span></p></div></div></div><div class="l" aria-hidden="false"><button class="ay fn am ab q ao fo fp fq" aria-label="user options menu" data-testid="headerUserIcon"><div class="l fj"><img alt="" class="l fd by bz ca cx" src="https://miro.medium.com/v2/resize:fill:64:64/1*dmbNkD5D-u45r44go_cf0g.png" width="32" height="32" loading="lazy" role="presentation"/><div class="fr by l bz ca fs n ay ft"></div></div></button></div></div></div><div class="l"><div class="fu fv fw fx fy l"><div class="ab cb"><div class="ci bh fz ga gb gc"></div></div><article><div class="l"><div class="l"><span class="l"></span><section><div><div class="fs gi gj gk gl gm"></div><div class="gn go gp gq gr"><div class="ab cb"><div class="ci bh fz ga gb gc"><div><h1 id="1dd2" class="pw-post-title gs gt gu bf gv gw gx gy gz ha hb hc hd he hf hg hh hi hj hk hl hm hn ho hp hq bk" data-testid="storyTitle">Getting started with Flutter GPU</h1></div><div><h2 id="2308" class="pw-subtitle-paragraph hr gt gu bf b hs ht hu hv hw hx hy hz ia ib ic id ie if ig cq du">Build custom renderers and render 3D scenes in Flutter.</h2><div><div class="speechify-ignore ab cp"><div class="speechify-ignore bh l"><div class="ih ii ij ik il ab"><div><div class="ab im"><div><div class="bm" aria-hidden="false"><a rel="noopener follow" href="/@algebrandon?source=post_page---byline--f33d497b7c11---------------------------------------"><div class="l in io by ip iq"><div class="l fj"><img alt="Brandon DeRosier" class="l fd by dd de cx" src="https://miro.medium.com/v2/resize:fill:88:88/0*Jzj4ct7CmcvOiR3g." width="44" height="44" loading="lazy" data-testid="authorPhoto"/><div class="ir by l dd de fs n is ft"></div></div></div></a></div></div><div class="it ab fj"><div><div class="bm" aria-hidden="false"><a href="https://medium.com/flutter?source=post_page---byline--f33d497b7c11---------------------------------------" rel="noopener follow"><div class="l iu iv by ip iw"><div class="l fj"><img alt="Flutter" class="l fd by br ix cx" src="https://miro.medium.com/v2/resize:fill:48:48/1*5-aoK8IBmXve5whBQM90GA.png" width="24" height="24" loading="lazy" data-testid="publicationPhoto"/><div class="ir by l br ix fs n is ft"></div></div></div></a></div></div></div></div></div><div class="bn bh l"><div class="ab"><div style="flex:1"><span class="bf b bg z bk"><div class="iy ab q"><div class="ab q iz"><div class="ab q"><div><div class="bm" aria-hidden="false"><p class="bf b ja jb bk"><a class="af ag ah ai aj ak al am an ao ap aq ar jc" data-testid="authorName" rel="noopener follow" href="/@algebrandon?source=post_page---byline--f33d497b7c11---------------------------------------">Brandon DeRosier</a></p></div></div></div><span class="jd je" aria-hidden="true"><span class="bf b bg z du">·</span></span><p class="bf b ja jb du"><span><a class="jf jg ah ai aj ak al am an ao ap aq ar ex jh ji" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fsubscribe%2Fuser%2F9a1282e0b0ee&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fflutter%2Fgetting-started-with-flutter-gpu-f33d497b7c11&user=Brandon+DeRosier&userId=9a1282e0b0ee&source=post_page-9a1282e0b0ee--byline--f33d497b7c11---------------------post_header------------------">Follow</a></span></p></div></div></span></div></div><div class="l jj"><span class="bf b bg z du"><div class="ab cn jk jl jm"><div class="fu fv ab"><div class="bf b bg z du ab jn"><span class="jo l jj">Published in</span><div><div class="l" aria-hidden="false"><a class="af ag ah ai aj ak al am an ao ap aq ar jc ab q" data-testid="publicationName" href="https://medium.com/flutter?source=post_page---byline--f33d497b7c11---------------------------------------" rel="noopener follow"><p class="bf b bg z jp jq jr js jt ju jv jw bk">Flutter</p></a></div></div></div><div class="h k"><span class="jd je" aria-hidden="true"><span class="bf b bg z du">·</span></span></div></div><span class="bf b bg z du"><div class="ab ae"><span data-testid="storyReadTime">17 min read</span><div class="jx jy l" aria-hidden="true"><span class="l" aria-hidden="true"><span class="bf b bg z du">·</span></span></div><span data-testid="storyPublishDate">Aug 6, 2024</span></div></span></div></span></div></div></div><div class="ab cp jz ka kb kc kd ke kf kg kh ki kj kk kl km kn ko"><div class="h k w fg fh q"><div class="le l"><div class="ab q lf lg"><div class="pw-multi-vote-icon fj jo lh li lj"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerClapButton" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fvote%2Fflutter%2Ff33d497b7c11&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fflutter%2Fgetting-started-with-flutter-gpu-f33d497b7c11&user=Brandon+DeRosier&userId=9a1282e0b0ee&source=---header_actions--f33d497b7c11---------------------clap_footer------------------"><div><div class="bm" aria-hidden="false"><div class="lk ao ll lm ln lo am lp lq lr lj"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-label="clap"><path fill-rule="evenodd" d="M11.37.828 12 3.282l.63-2.454zM13.916 3.953l1.523-2.112-1.184-.39zM8.589 1.84l1.522 2.112-.337-2.501zM18.523 18.92c-.86.86-1.75 1.246-2.62 1.33a6 6 0 0 0 .407-.372c2.388-2.389 2.86-4.951 1.399-7.623l-.912-1.603-.79-1.672c-.26-.56-.194-.98.203-1.288a.7.7 0 0 1 .546-.132c.283.046.546.231.728.5l2.363 4.157c.976 1.624 1.141 4.237-1.324 6.702m-10.999-.438L3.37 14.328a.828.828 0 0 1 .585-1.408.83.83 0 0 1 .585.242l2.158 2.157a.365.365 0 0 0 .516-.516l-2.157-2.158-1.449-1.449a.826.826 0 0 1 1.167-1.17l3.438 3.44a.363.363 0 0 0 .516 0 .364.364 0 0 0 0-.516L5.293 9.513l-.97-.97a.826.826 0 0 1 0-1.166.84.84 0 0 1 1.167 0l.97.968 3.437 3.436a.36.36 0 0 0 .517 0 .366.366 0 0 0 0-.516L6.977 7.83a.82.82 0 0 1-.241-.584.82.82 0 0 1 .824-.826c.219 0 .43.087.584.242l5.787 5.787a.366.366 0 0 0 .587-.415l-1.117-2.363c-.26-.56-.194-.98.204-1.289a.7.7 0 0 1 .546-.132c.283.046.545.232.727.501l2.193 3.86c1.302 2.38.883 4.59-1.277 6.75-1.156 1.156-2.602 1.627-4.19 1.367-1.418-.236-2.866-1.033-4.079-2.246M10.75 5.971l2.12 2.12c-.41.502-.465 1.17-.128 1.89l.22.465-3.523-3.523a.8.8 0 0 1-.097-.368c0-.22.086-.428.241-.584a.847.847 0 0 1 1.167 0m7.355 1.705c-.31-.461-.746-.758-1.23-.837a1.44 1.44 0 0 0-1.11.275c-.312.24-.505.543-.59.881a1.74 1.74 0 0 0-.906-.465 1.47 1.47 0 0 0-.82.106l-2.182-2.182a1.56 1.56 0 0 0-2.2 0 1.54 1.54 0 0 0-.396.701 1.56 1.56 0 0 0-2.21-.01 1.55 1.55 0 0 0-.416.753c-.624-.624-1.649-.624-2.237-.037a1.557 1.557 0 0 0 0 2.2c-.239.1-.501.238-.715.453a1.56 1.56 0 0 0 0 2.2l.516.515a1.556 1.556 0 0 0-.753 2.615L7.01 19c1.32 1.319 2.909 2.189 4.475 2.449q.482.08.971.08c.85 0 1.653-.198 2.393-.579.231.033.46.054.686.054 1.266 0 2.457-.52 3.505-1.567 2.763-2.763 2.552-5.734 1.439-7.586z" clip-rule="evenodd"></path></svg></div></div></div></a></span></div><div class="pw-multi-vote-count l ls lt lu lv lw lx ly"><p class="bf b dv z du"><span class="lz">--</span></p></div></div></div><div><div class="bm" aria-hidden="false"><button class="ao lk mc md ab q fk me mf" aria-label="responses"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="mb"><path d="M18.006 16.803c1.533-1.456 2.234-3.325 2.234-5.321C20.24 7.357 16.709 4 12.191 4S4 7.357 4 11.482c0 4.126 3.674 7.482 8.191 7.482.817 0 1.622-.111 2.393-.327.231.2.48.391.744.559 1.06.693 2.203 1.044 3.399 1.044.224-.008.4-.112.486-.287a.49.49 0 0 0-.042-.518c-.495-.67-.845-1.364-1.04-2.057a4 4 0 0 1-.125-.598zm-3.122 1.055-.067-.223-.315.096a8 8 0 0 1-2.311.338c-4.023 0-7.292-2.955-7.292-6.587 0-3.633 3.269-6.588 7.292-6.588 4.014 0 7.112 2.958 7.112 6.593 0 1.794-.608 3.469-2.027 4.72l-.195.168v.255c0 .056 0 .151.016.295.025.231.081.478.154.733.154.558.398 1.117.722 1.659a5.3 5.3 0 0 1-2.165-.845c-.276-.176-.714-.383-.941-.59z"></path></svg><p class="bf b dv z du"><span class="pw-responses-count ma mb">11</span></p></button></div></div></div><div class="ab q kp kq kr ks kt ku kv kw kx ky kz la lb lc ld"><div class="mg k j i d"></div><div class="h k"><div><div class="bm" aria-hidden="false"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerBookmarkButton" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2Ff33d497b7c11&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fflutter%2Fgetting-started-with-flutter-gpu-f33d497b7c11&source=---header_actions--f33d497b7c11---------------------bookmark_footer------------------"><svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" fill="none" viewBox="0 0 25 25" class="du mh" aria-label="Add to list bookmark button"><path fill="currentColor" d="M18 2.5a.5.5 0 0 1 1 0V5h2.5a.5.5 0 0 1 0 1H19v2.5a.5.5 0 1 1-1 0V6h-2.5a.5.5 0 0 1 0-1H18zM7 7a1 1 0 0 1 1-1h3.5a.5.5 0 0 0 0-1H8a2 2 0 0 0-2 2v14a.5.5 0 0 0 .805.396L12.5 17l5.695 4.396A.5.5 0 0 0 19 21v-8.5a.5.5 0 0 0-1 0v7.485l-5.195-4.012a.5.5 0 0 0-.61 0L7 19.985z"></path></svg></a></span></div></div></div><div class="fd mi cn"><div class="l ae"><div class="ab cb"><div class="mj mk ml mm mn mo ci bh"><div class="ab"><div class="bm" aria-hidden="false"><div><div class="bm" aria-hidden="false"><button aria-label="Listen" data-testid="audioPlayButton" class="af fk ah ai aj ak al mp an ao ap ex mq mr mf ms mt mu mv mw s mx my mz na nb nc nd u ne nf ng"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M3 12a9 9 0 1 1 18 0 9 9 0 0 1-18 0m9-10C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2m3.376 10.416-4.599 3.066a.5.5 0 0 1-.777-.416V8.934a.5.5 0 0 1 .777-.416l4.599 3.066a.5.5 0 0 1 0 .832" clip-rule="evenodd"></path></svg><div class="j i d"><p class="bf b bg z du">Listen</p></div></button></div></div></div></div></div></div></div></div><div class="bm" aria-hidden="false" aria-describedby="postFooterSocialMenu" aria-labelledby="postFooterSocialMenu"><div><div class="bm" aria-hidden="false"><button aria-controls="postFooterSocialMenu" aria-expanded="false" aria-label="Share Post" data-testid="headerSocialShareButton" class="af fk ah ai aj ak al mp an ao ap ex mq mr mf ms mt mu mv mw s mx my mz na nb nc nd u ne nf ng"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M15.218 4.931a.4.4 0 0 1-.118.132l.012.006a.45.45 0 0 1-.292.074.5.5 0 0 1-.3-.13l-2.02-2.02v7.07c0 .28-.23.5-.5.5s-.5-.22-.5-.5v-7.04l-2 2a.45.45 0 0 1-.57.04h-.02a.4.4 0 0 1-.16-.3.4.4 0 0 1 .1-.32l2.8-2.8a.5.5 0 0 1 .7 0l2.8 2.79a.42.42 0 0 1 .068.498m-.106.138.008.004v-.01zM16 7.063h1.5a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-11c-1.1 0-2-.9-2-2v-10a2 2 0 0 1 2-2H8a.5.5 0 0 1 .35.15.5.5 0 0 1 .15.35.5.5 0 0 1-.15.35.5.5 0 0 1-.35.15H6.4c-.5 0-.9.4-.9.9v10.2a.9.9 0 0 0 .9.9h11.2c.5 0 .9-.4.9-.9v-10.2c0-.5-.4-.9-.9-.9H16a.5.5 0 0 1 0-1" clip-rule="evenodd"></path></svg><div class="j i d"><p class="bf b bg z du">Share</p></div></button></div></div></div></div></div></div></div></div></div><p id="a6f6" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">The Flutter 3.24 release introduces a new low-level graphics API called <a class="af od" href="https://github.com/flutter/engine/blob/main/docs/impeller/Flutter-GPU.md" rel="noopener ugc nofollow" target="_blank">Flutter GPU</a>. There is also a 3D rendering library powered by Flutter GPU called <a class="af od" href="https://pub.dev/packages/flutter_scene" rel="noopener ugc nofollow" target="_blank">Flutter Scene</a> (package: <code class="cx oe of og oh b">flutter_scene</code>). Both Flutter GPU and Flutter Scene are currently in preview, only available on Flutter’s <a class="af od" href="https://docs.flutter.dev/release/upgrade#other-channels" rel="noopener ugc nofollow" target="_blank">main channel</a> (due to reliance on experimental features), require <a class="af od" href="https://docs.flutter.dev/perf/impeller#availability" rel="noopener ugc nofollow" target="_blank">Impeller to be enabled</a>, and might occasionally introduce breaking changes.</p><p id="952a" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">This article contains two “getting started” guides for these packages:</p><ol class=""><li id="4ce4" class="nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc oi oj ok bk">🔺 <strong class="nj gv">Advanced:</strong> <a class="af od" href="#d558" rel="noopener ugc nofollow">Getting started with Flutter GPU</a><br/>If you’re an experienced graphics programmer or you’re interested in low level graphics and want to build renderers from scratch in Flutter, then this guide will get you set up to start tinkering with Flutter GPU. You’ll draw your first triangle from scratch… in Flutter!</li><li id="fbac" class="nh ni gu nj b hs ol nl nm hv om no np nq on ns nt nu oo nw nx ny op oa ob oc oi oj ok bk">💚 <strong class="nj gv">Intermediate:</strong> <a class="af od" href="#6b35" rel="noopener ugc nofollow">3D rendering with Flutter Scene</a><strong class="nj gv"><br/></strong>If you’re a Flutter developer that wants to add 3D functionality to your apps, or you want to create 3D games using Dart and Flutter, then this is a guide for you! You’ll set up a project that imports and renders 3D assets in Flutter.</li></ol></div></div></div><div class="ab cb oq or os ot" role="separator"><span class="ou by bm ov ow ox"></span><span class="ou by bm ov ow ox"></span><span class="ou by bm ov ow"></span></div><div class="gn go gp gq gr"><div class="ab cb"><div class="ci bh fz ga gb gc"><h1 id="d558" class="oy oz gu bf pa pb pc hu pd pe pf hx pg ph pi pj pk pl pm pn po pp pq pr ps pt bk">Getting started with Flutter GPU</h1><p id="2164" class="pw-post-body-paragraph nh ni gu nj b hs pu nl nm hv pv no np nq pw ns nt nu px nw nx ny py oa ob oc gn bk">⚠️ Warning! ⚠️ Flutter GPU is ultimately a low-level API. It’s overwhelmingly likely that the vast majority of Flutter devs who will benefit from Flutter GPU’s existence will do so by consuming higher level rendering libraries published on pub.dev, such as the Flutter Scene rendering package. If you’re not interested in the Flutter GPU API itself and you’re just interested in 3D rendering, skip ahead to <a class="af od" href="#6b35" rel="noopener ugc nofollow">3D rendering with Flutter Scene</a>.</p><figure class="qc qd qe qf qg qh pz qa paragraph-image"><div role="button" tabindex="0" class="qi qj fj qk bh ql"><div class="pz qa qb"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/0*hAqIOVkaI1IWnOHE 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*hAqIOVkaI1IWnOHE 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*hAqIOVkaI1IWnOHE 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*hAqIOVkaI1IWnOHE 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*hAqIOVkaI1IWnOHE 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*hAqIOVkaI1IWnOHE 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/0*hAqIOVkaI1IWnOHE 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/0*hAqIOVkaI1IWnOHE 640w, https://miro.medium.com/v2/resize:fit:720/0*hAqIOVkaI1IWnOHE 720w, https://miro.medium.com/v2/resize:fit:750/0*hAqIOVkaI1IWnOHE 750w, https://miro.medium.com/v2/resize:fit:786/0*hAqIOVkaI1IWnOHE 786w, https://miro.medium.com/v2/resize:fit:828/0*hAqIOVkaI1IWnOHE 828w, https://miro.medium.com/v2/resize:fit:1100/0*hAqIOVkaI1IWnOHE 1100w, https://miro.medium.com/v2/resize:fit:1400/0*hAqIOVkaI1IWnOHE 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh mo qm c" width="700" height="524" loading="eager" role="presentation"/></picture></div></div><figcaption class="qn ff qo pz qa qp qq bf b bg z du">Ooh shiny. This is a ray-marched signed distance field. You could render this using Flutter GPU, but it’s perfectly possible to do so with a <a class="af od" href="https://docs.flutter.dev/ui/design/graphics/fragment-shaders" rel="noopener ugc nofollow" target="_blank">custom fragment shader</a> as well.</figcaption></figure><h1 id="0097" class="oy oz gu bf pa pb qr hu pd pe qs hx pg ph qt pj pk pl qu pn po pp qv pr ps pt bk">Getting started with Flutter GPU</h1><p id="d305" class="pw-post-body-paragraph nh ni gu nj b hs pu nl nm hv pv no np nq pw ns nt nu px nw nx ny py oa ob oc gn bk">Flutter GPU is Flutter’s built-in low-level graphics API. It allows you to build and integrate custom renderers in Flutter by writing Dart code and GLSL shaders. No native platform code required.</p><p id="8cce" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Currently, Flutter GPU is in early preview and offers a basic rasterization API, but more functionality will continue to be added and refined, as the API approaches stable.</p><p id="36c8" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Flutter GPU also requires <a class="af od" href="https://docs.flutter.dev/perf/impeller#availability" rel="noopener ugc nofollow" target="_blank">Impeller to be enabled</a>. This means it can only be used when targeting platforms that are supported by Impeller. At the time of writing, Impeller supports:</p><ul class=""><li id="4586" class="nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc qw oj ok bk">iOS (on by default)</li><li id="9965" class="nh ni gu nj b hs ol nl nm hv om no np nq on ns nt nu oo nw nx ny op oa ob oc qw oj ok bk">macOS (opt-in preview)</li><li id="9ac0" class="nh ni gu nj b hs ol nl nm hv om no np nq on ns nt nu oo nw nx ny op oa ob oc qw oj ok bk">Android (opt-in preview)</li></ul><p id="53ab" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Our aim with Flutter GPU is to eventually support all of Flutter’s platform targets. The ultimate goal is to foster an ecosystem of cross-platform rendering solutions in Flutter that are easy to maintain for package authors and easy to install for users.</p><p id="b74e" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">3D rendering is just one possible use case. Flutter GPU can also be used to build specialized 2D renderers, or to do something more unorthodox, like render 3D slices of a 4D space, or project non-euclidean spaces.</p><p id="cadf" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">One example of a great use case for a custom 2D renderer powered by Flutter GPU would be 2D character animation formats that rely on skeletal mesh deformation. Spine 2D is a good example of this. Such skeletal mesh solutions usually have animation clips that manipulate translation, rotation, and scale properties of bones in a hierarchy, and each vertex has a few associated “bone weights” that determine what bones should influence the vertex and by how much.’</p><p id="edc4" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">With a Canvas solution like <code class="cx oe of og oh b">drawVertices</code>, bone weight transforms would need to be applied for each vertex on the CPU. With Flutter GPU, the bone transforms could be fed to a vertex shader in the form of a uniform array or even a texture sampler, allowing the final position for each vertex to be calculated in parallel on the GPU based on the state of the skeleton and the per-vertex bone weights.</p><p id="a4b5" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">With that out of the way, let’s get started with Flutter GPU by way of a gentle introduction: Drawing your first triangle!</p><figure class="qc qd qe qf qg qh pz qa paragraph-image"><div role="button" tabindex="0" class="qi qj fj qk bh ql"><div class="pz qa qx"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/0*JEI3fLDGcRHWKruT 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*JEI3fLDGcRHWKruT 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*JEI3fLDGcRHWKruT 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*JEI3fLDGcRHWKruT 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*JEI3fLDGcRHWKruT 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*JEI3fLDGcRHWKruT 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/0*JEI3fLDGcRHWKruT 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/0*JEI3fLDGcRHWKruT 640w, https://miro.medium.com/v2/resize:fit:720/0*JEI3fLDGcRHWKruT 720w, https://miro.medium.com/v2/resize:fit:750/0*JEI3fLDGcRHWKruT 750w, https://miro.medium.com/v2/resize:fit:786/0*JEI3fLDGcRHWKruT 786w, https://miro.medium.com/v2/resize:fit:828/0*JEI3fLDGcRHWKruT 828w, https://miro.medium.com/v2/resize:fit:1100/0*JEI3fLDGcRHWKruT 1100w, https://miro.medium.com/v2/resize:fit:1400/0*JEI3fLDGcRHWKruT 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh mo qm c" width="700" height="568" loading="lazy" role="presentation"/></picture></div></div></figure><h1 id="0496" class="oy oz gu bf pa pb qr hu pd pe qs hx pg ph qt pj pk pl qu pn po pp qv pr ps pt bk">Add Flutter GPU to your project</h1><p id="f487" class="pw-post-body-paragraph nh ni gu nj b hs pu nl nm hv pv no np nq pw ns nt nu px nw nx ny py oa ob oc gn bk">First, note that Flutter GPU is currently in an early preview state and might be prone to API breakages. Quite a lot is already possible with the current API, but experienced graphics engineers might notice some missing common functionality. Much is planned for Flutter GPU over the coming months.</p><p id="1ed5" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">For these reasons, it’s strongly recommended that, for now, you operate against the tip of the <a class="af od" href="https://docs.flutter.dev/release/upgrade#other-channels" rel="noopener ugc nofollow" target="_blank">main channel</a> when developing packages against Flutter GPU. If you happen to run into any unexpected behavior, bugs, or have feature requests, please file issues using the standard <a class="af od" href="https://github.com/flutter/flutter/issues/new/choose" rel="noopener ugc nofollow" target="_blank">Flutter issue templates</a> on GitHub. All tracked issues related to Flutter GPU are given the <a class="af od" href="https://github.com/flutter/flutter/labels/flutter-gpu" rel="noopener ugc nofollow" target="_blank">flutter-gpu label</a>.</p><p id="f206" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">So before experimenting with Flutter GPU, switch Flutter over to the main channel by running the following commands.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="bd7c" class="rb oz gu oh b bg rc rd l re rf">flutter channel main<br/>flutter upgrade</span></pre><p id="4538" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Now create a fresh Flutter project.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="3bec" class="rb oz gu oh b bg rc rd l re rf">flutter create my_cool_renderer<br/>cd my_cool_renderer</span></pre><p id="9e34" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Next, add the flutter_gpu SDK package to your pubspec.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="d559" class="rb oz gu oh b bg rc rd l re rf">flutter pub add flutter_gpu --sdk=flutter</span></pre><h1 id="502b" class="oy oz gu bf pa pb qr hu pd pe qs hx pg ph qt pj pk pl qu pn po pp qv pr ps pt bk">Build and import shader bundles.</h1><p id="3492" class="pw-post-body-paragraph nh ni gu nj b hs pu nl nm hv pv no np nq pw ns nt nu px nw nx ny py oa ob oc gn bk">In order to render anything with Flutter GPU, you’ll need to author some GLSL shaders. Flutter GPU’s shaders have different semantics than those consumed by Flutter’s <a class="af od" href="https://docs.flutter.dev/ui/design/graphics/fragment-shaders" rel="noopener ugc nofollow" target="_blank">fragment shader</a> feature, particularly when it comes to uniform bindings. You’ll also define a vertex shader to go alongside the fragment shader.</p><p id="0002" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Start with defining the simplest possible shaders. You can place shaders anywhere in your project, but for this example, create a <code class="cx oe of og oh b">shaders</code> directory and populate it with two shaders: <code class="cx oe of og oh b">simple.vert</code> and <code class="cx oe of og oh b">simple.frag</code> .</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="09d6" class="rb oz gu oh b bg rc rd l re rf">// Copy into: shaders/simple.vert<br/><br/>in vec2 position;<br/><br/>void main() {<br/> gl_Position = vec4(position, 0.0, 1.0);<br/>}</span></pre><p id="7049" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">When drawing the triangle, you’ll have a list of data that defines each vertex. In this case, it merely lists 2D positions. For each of these vertices, a simple vertex shader assigns these 2D positions to the clip space output intrinsic <code class="cx oe of og oh b">gl_Position</code>.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="c11d" class="rb oz gu oh b bg rc rd l re rf">// Copy into: shaders/simple.frag<br/><br/>out vec4 frag_color;<br/><br/>void main() {<br/> frag_color = vec4(0, 1, 0, 1);<br/>}</span></pre><p id="23c7" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">The fragment shader is even simpler; it outputs an RGBA color with a range of <code class="cx oe of og oh b">(0, 0, 0, 0)</code> to <code class="cx oe of og oh b">(1, 1, 1, 1)</code>. So everything will be shaded as green.</p><p id="cbf9" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Okay, so now that you have your shaders, compile them using Flutter's ahead of time (AOT) shader compiler . To set up automated builds for shader bundles, we recommend using the <a class="af od" href="https://pub.dev/packages/flutter_gpu_shaders" rel="noopener ugc nofollow" target="_blank">flutter_gpu_shaders</a> package.</p><p id="de7c" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Use pub to add <code class="cx oe of og oh b">flutter_gpu_shaders</code> as a regular dependency in your project.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="d18a" class="rb oz gu oh b bg rc rd l re rf">flutter pub add flutter_gpu_shaders</span></pre><p id="b5bc" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Flutter GPU shaders are bundled into <code class="cx oe of og oh b">.shaderbundle</code> files, which can be added to your project's asset bundle as regular assets. Shader bundles contain the compiled shader sources for the platform target(s).</p><p id="4387" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Next, create a shader bundle manifest file that describes the contents of the shader bundle. Add the following to <code class="cx oe of og oh b">my_renderer.shaderbundle.json</code> in the root directory of your project.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="3793" class="rb oz gu oh b bg rc rd l re rf">{<br/> "SimpleVertex": {<br/> "type": "vertex",<br/> "file": "shaders/simple.vert"<br/> },<br/> "SimpleFragment": {<br/> "type": "fragment",<br/> "file": "shaders/simple.frag"<br/> }<br/>}</span></pre><p id="8fc3" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Each entry in the shader bundle can have an arbitrary name. In this case, the names are "SimpleVertex" and "SimpleFragment". These names are used to look up the shaders in your app.</p><p id="4dd6" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Next, use the <code class="cx oe of og oh b">flutter_gpu_shaders</code> package to build the shaderbundle. You can add a hook that automatically triggers a build by enabling the experimental "native assets" feature. Use the following commands to enable native assets and install the <code class="cx oe of og oh b">native_assets_cli</code> package.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="eea1" class="rb oz gu oh b bg rc rd l re rf">flutter config --enable-native-assets<br/>flutter pub add native_assets_cli</span></pre><p id="f2d0" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">With the native assets feature enabled, add a <code class="cx oe of og oh b">build.dart</code> script under the <code class="cx oe of og oh b">hook</code> directory that will trigger building the shader bundle automatically.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="dc30" class="rb oz gu oh b bg rc rd l re rf">// Copy into: hook/build.dart<br/><br/>import 'package:native_assets_cli/native_assets_cli.dart';<br/>import 'package:flutter_gpu_shaders/build.dart';<br/><br/>void main(List<String> args) async {<br/> await build(args, (config, output) async {<br/> await buildShaderBundleJson(<br/> buildConfig: config,<br/> buildOutput: output,<br/> manifestFileName: 'my_renderer.shaderbundle.json');<br/> });<br/>}</span></pre><p id="8a03" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">After this change, when the Flutter tool is building the project, <code class="cx oe of og oh b">buildShaderBundleJson</code> builds the shader bundle and outputs the result to <code class="cx oe of og oh b">build/shaderbundles/my_renderer.shaderbundle</code> under the package root.</p><p id="2f76" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">The shader bundle format itself is tied to the specific version of Flutter you're using and might change over time. If you're authoring a package that builds shader bundles, do <strong class="nj gv">not</strong> check the generated <code class="cx oe of og oh b">.shaderbundle</code> files into your source tree. Instead, use a build hook to automate the build process (as previously explained).</p><p id="290e" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">This way, developers that use your library will always build fresh shader bundles with the correct format!</p><p id="95a9" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Now that you're automatically building your shader bundle, import it like a regular asset. Add an asset entry to your project's <code class="cx oe of og oh b">pubspec.yaml</code>:</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="5ebd" class="rb oz gu oh b bg rc rd l re rf">flutter:<br/> assets:<br/> - build/shaderbundles/</span></pre><p id="f509" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">In the future, the native assets feature will allow build hooks to append data assets to the bundle. Once this happens, it will no longer be necessary to add an asset import rule alongside the build hook.</p><p id="4a3d" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Next, add some code to load up the shaders at runtime. Create <code class="cx oe of og oh b">lib/shaders.dart</code> and add the following code.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="6a34" class="rb oz gu oh b bg rc rd l re rf">// Copy into: lib/shaders.dart<br/><br/>import 'package:flutter_gpu/gpu.dart' as gpu;<br/><br/>const String _kShaderBundlePath =<br/> 'build/shaderbundles/my_renderer.shaderbundle';<br/>// NOTE: If you're building a library, the path must be prefixed<br/>// with a package name. For example:<br/>// 'packages/my_cool_renderer/build/shaderbundles/my_renderer.shaderbundle'<br/><br/>gpu.ShaderLibrary? _shaderLibrary;<br/>gpu.ShaderLibrary get shaderLibrary {<br/> if (_shaderLibrary != null) {<br/> return _shaderLibrary!;<br/> }<br/> _shaderLibrary = gpu.ShaderLibrary.fromAsset(_kShaderBundlePath);<br/> if (_shaderLibrary != null) {<br/> return _shaderLibrary!;<br/> }<br/><br/> throw Exception("Failed to load shader bundle! ($_kShaderBundlePath)");<br/>}</span></pre><p id="9129" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">This code creates a singleton getter for the Flutter GPU shader runtime library. The first time <code class="cx oe of og oh b">shaderLibrary</code> is accessed, the runtime shader library is initialized using the built asset bundle with <code class="cx oe of og oh b">gpu.ShaderLibrary.fromAsset(shader_bundle_path)</code>.</p><p id="a0b5" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">The project is now set up to use Flutter GPU shaders. It’s time to render that triangle!</p><h1 id="eead" class="oy oz gu bf pa pb qr hu pd pe qs hx pg ph qt pj pk pl qu pn po pp qv pr ps pt bk">Drawing your first triangle</h1><p id="85fc" class="pw-post-body-paragraph nh ni gu nj b hs pu nl nm hv pv no np nq pw ns nt nu px nw nx ny py oa ob oc gn bk">For this guide, you’ll create an RGBA Flutter GPU <code class="cx oe of og oh b">Texture</code> and a <code class="cx oe of og oh b">RenderPass</code> that attaches the Texture as a color output. Then, you’ll render the <code class="cx oe of og oh b">Texture</code> in a widget using <code class="cx oe of og oh b"><a class="af od" href="https://api.flutter.dev/flutter/dart-ui/Canvas/drawImage.html" rel="noopener ugc nofollow" target="_blank">Canvas.drawImage</a></code>.</p><p id="9833" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">For the sake of brevity, you’ll forego best practice and just rebuild all of the resources for each frame.</p><p id="0bf8" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">As long as you mark your <code class="cx oe of og oh b">Texture</code> as "shader readable" when allocating it, you’ll be able to convert it to a <code class="cx oe of og oh b">dart:ui.Image</code>. To display the rendered results in the widget tree, draw it to a <code class="cx oe of og oh b">dart:ui.Canvas</code>!</p><p id="bf47" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">You can access a <code class="cx oe of og oh b">Canvas</code> by scaffolding the widget tree with a custom painter. Replace the contents of <code class="cx oe of og oh b">lib/main.dart</code> with the following:</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="4b2c" class="rb oz gu oh b bg rc rd l re rf">import 'dart:typed_data';<br/><br/>import 'package:flutter/material.dart';<br/>import 'package:flutter_gpu/gpu.dart' as gpu;<br/><br/>// NOTE: We made this earlier while setting up shader bundle imports!<br/>import 'shaders.dart';<br/><br/>void main() {<br/> runApp(const MyApp());<br/>}<br/><br/>class MyApp extends StatelessWidget {<br/> const MyApp({super.key});<br/><br/> @override<br/> Widget build(BuildContext context) {<br/> return MaterialApp(<br/> title: 'Flutter GPU Triangle Example',<br/> home: CustomPaint(<br/> painter: TrianglePainter(),<br/> ),<br/> );<br/> }<br/>}<br/><br/>class TrianglePainter extends CustomPainter {<br/> @override<br/> void paint(Canvas canvas, Size size) {<br/> // Attempt to access `gpu.gpuContext`.<br/> // If Flutter GPU isn't supported, an exception will be thrown.<br/> print('Default color format: ' +<br/> gpu.gpuContext.defaultColorFormat.toString());<br/> }<br/><br/> @override<br/> bool shouldRepaint(covariant CustomPainter oldDelegate) => true;<br/>}</span></pre><p id="0947" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Now, run the app. As a reminder, Flutter GPU currently requires <a class="af od" href="https://docs.flutter.dev/perf/impeller#availability" rel="noopener ugc nofollow" target="_blank">Impeller to be enabled</a>. So you must use an Impeller-supported platform. For this guide, I'll be targeting macOS.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="5ac5" class="rb oz gu oh b bg rc rd l re rf">flutter run -d macos --enable-impeller</span></pre><figure class="qc qd qe qf qg qh pz qa paragraph-image"><div role="button" tabindex="0" class="qi qj fj qk bh ql"><div class="pz qa qx"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/0*lKTtaX2ih6dFpSMQ 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*lKTtaX2ih6dFpSMQ 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*lKTtaX2ih6dFpSMQ 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*lKTtaX2ih6dFpSMQ 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*lKTtaX2ih6dFpSMQ 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*lKTtaX2ih6dFpSMQ 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/0*lKTtaX2ih6dFpSMQ 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/0*lKTtaX2ih6dFpSMQ 640w, https://miro.medium.com/v2/resize:fit:720/0*lKTtaX2ih6dFpSMQ 720w, https://miro.medium.com/v2/resize:fit:750/0*lKTtaX2ih6dFpSMQ 750w, https://miro.medium.com/v2/resize:fit:786/0*lKTtaX2ih6dFpSMQ 786w, https://miro.medium.com/v2/resize:fit:828/0*lKTtaX2ih6dFpSMQ 828w, https://miro.medium.com/v2/resize:fit:1100/0*lKTtaX2ih6dFpSMQ 1100w, https://miro.medium.com/v2/resize:fit:1400/0*lKTtaX2ih6dFpSMQ 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh mo qm c" width="700" height="568" loading="lazy" role="presentation"/></picture></div></div></figure><p id="a809" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">If Flutter GPU is working, then you should see the default color format printed to the console.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="f1ac" class="rb oz gu oh b bg rc rd l re rf">flutter: Default color format: PixelFormat.b8g8r8a8UNormInt</span></pre><p id="4fa9" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">If Impeller isn't enabled, an exception is thrown when attempting to access <code class="cx oe of og oh b">gpu.gpuContext</code>.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="7480" class="rb oz gu oh b bg rc rd l re rf">Exception: Flutter GPU requires the Impeller rendering backend to be enabled.<br/><br/>The relevant error-causing widget was:<br/> CustomPaint</span></pre><p id="3d52" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">For the sake of simplicity, you’ll only modify the <code class="cx oe of og oh b">paint</code> method from here onwards.</p><p id="92f8" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">To start off, create a Flutter GPU <code class="cx oe of og oh b">Texture</code>, clear it, and then display it by drawing it to the <code class="cx oe of og oh b">Canvas</code>.</p><p id="d7a2" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Create a <code class="cx oe of og oh b">Texture</code> the size of the <code class="cx oe of og oh b">Canvas</code>. A <code class="cx oe of og oh b">StorageMode</code> must be chosen. In this case, you’ll mark the <code class="cx oe of og oh b">Texture</code> as <code class="cx oe of og oh b">devicePrivate</code>, because you'll only be using instructions that accesses the texture's memory from the device (GPU).</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="8070" class="rb oz gu oh b bg rc rd l re rf">final texture = gpu.gpuContext.createTexture(gpu.StorageMode.devicePrivate,<br/> size.width.toInt(), size.height.toInt())!;</span></pre><p id="dc7e" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">If overwriting the texture's data by uploading it from the host (CPU), then use <code class="cx oe of og oh b">StorageMode.hostVisible</code> instead.</p><p id="cdca" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">The third available option is <code class="cx oe of og oh b">StorageMode.deviceTransient</code>, which is useful for attachments that don't need to exceed the lifetime of a single <code class="cx oe of og oh b">RenderPass</code> (so they can just exist in tile memory and don't need to be backed by a VRAM allocation). Oftentimes, depth/stencil textures fit this criteria.</p><p id="6764" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Next, define a <code class="cx oe of og oh b">RenderTarget</code>. Render targets contain a collection of "attachments" that describe a per-fragment memory layout and its setup/teardown behavior at the beginning and end of a <code class="cx oe of og oh b">RenderPass</code>.</p><p id="5d3f" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Essentially, a <code class="cx oe of og oh b">RenderTarget</code> is a reusable descriptor for a <code class="cx oe of og oh b">RenderPass</code>.</p><p id="4a64" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">For now, define a very simple <code class="cx oe of og oh b">RenderTarget</code> consisting of only one color attachment.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="71a7" class="rb oz gu oh b bg rc rd l re rf">final renderTarget = gpu.RenderTarget.singleColor(<br/>gpu.ColorAttachment(texture: texture, clearValue: Colors.lightBlue));</span></pre><p id="4923" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Notice that this code sets the <code class="cx oe of og oh b">clearValue</code> to light blue. Each attachment has a <code class="cx oe of og oh b">LoadAction</code> and a <code class="cx oe of og oh b">StoreAction</code> that determines what should happen to the attachment's ephemeral tile memory at the beginning and end of the pass, respectively.</p><p id="a7a7" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">By default, color attachments are set to <code class="cx oe of og oh b">LoadAction.clear</code> (which initializes the tile memory to a given color), and <code class="cx oe of og oh b">StoreAction.store</code> (which saves the results to the attached texture's VRAM allocation).</p><p id="ca5f" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Now, create a <code class="cx oe of og oh b">CommandBuffer</code>, spawn a <code class="cx oe of og oh b">RenderPass</code> from it using the <code class="cx oe of og oh b">RenderTarget</code> from earlier, and then immediately submit the <code class="cx oe of og oh b">CommandBuffer</code> to clear the texture.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="102e" class="rb oz gu oh b bg rc rd l re rf">final commandBuffer = gpu.gpuContext.createCommandBuffer();<br/>final renderPass = commandBuffer.createRenderPass(renderTarget);<br/>// ... draw calls will go here!<br/>commandBuffer.submit();</span></pre><p id="53ac" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">All that's left is to draw the initialized texture to the Canvas!</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="2c91" class="rb oz gu oh b bg rc rd l re rf">final image = texture.asImage();<br/>canvas.drawImage(image, Offset.zero, Paint());</span></pre><figure class="qc qd qe qf qg qh pz qa paragraph-image"><div role="button" tabindex="0" class="qi qj fj qk bh ql"><div class="pz qa qx"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/0*ebUDtzQOuIGmdlop 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*ebUDtzQOuIGmdlop 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*ebUDtzQOuIGmdlop 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*ebUDtzQOuIGmdlop 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*ebUDtzQOuIGmdlop 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*ebUDtzQOuIGmdlop 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/0*ebUDtzQOuIGmdlop 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/0*ebUDtzQOuIGmdlop 640w, https://miro.medium.com/v2/resize:fit:720/0*ebUDtzQOuIGmdlop 720w, https://miro.medium.com/v2/resize:fit:750/0*ebUDtzQOuIGmdlop 750w, https://miro.medium.com/v2/resize:fit:786/0*ebUDtzQOuIGmdlop 786w, https://miro.medium.com/v2/resize:fit:828/0*ebUDtzQOuIGmdlop 828w, https://miro.medium.com/v2/resize:fit:1100/0*ebUDtzQOuIGmdlop 1100w, https://miro.medium.com/v2/resize:fit:1400/0*ebUDtzQOuIGmdlop 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh mo qm c" width="700" height="568" loading="lazy" role="presentation"/></picture></div></div></figure><p id="add2" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Now that you have a <code class="cx oe of og oh b">RenderPass</code> hooked up with results displayed to the screen, you’re ready to start drawing the triangle. To do this, set up the following:</p><ol class=""><li id="3f3a" class="nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc oi oj ok bk">A <code class="cx oe of og oh b">RenderPipeline</code> created from our shaders, and</li><li id="db82" class="nh ni gu nj b hs ol nl nm hv om no np nq on ns nt nu oo nw nx ny op oa ob oc oi oj ok bk">A GPU-accessible buffer containing our geometry (three vertex positions).</li></ol><p id="bbca" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Creating the <code class="cx oe of og oh b">RenderPipeline</code> is easy. You just need to combine a vertex and fragment shader from your library.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="3fbb" class="rb oz gu oh b bg rc rd l re rf">final vert = shaderLibrary['SimpleVertex']!;<br/>final frag = shaderLibrary['SimpleFragment']!;<br/>final pipeline = gpu.gpuContext.createRenderPipeline(vert, frag);</span></pre><p id="6a7d" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Now for the geometry. Recall that the "SimpleVertex" shader only has one input: <code class="cx oe of og oh b">in vec2 position</code>. So, to draw the three vertices, you need three sets of two floating point numbers.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="9ce7" class="rb oz gu oh b bg rc rd l re rf">final vertices = Float32List.fromList([<br/> -0.5, -0.5, // First vertex<br/> 0.5, -0.5, // Second vertex<br/> 0.0, 0.5, // Third vertex<br/>]);<br/>final verticesDeviceBuffer = gpu.gpuContext<br/> .createDeviceBufferWithCopy(ByteData.sublistView(vertices))!;</span></pre><p id="37da" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">All that's remaining is to bind the new resources and call <code class="cx oe of og oh b">renderPass.draw()</code> to finish recording the draw call.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="6431" class="rb oz gu oh b bg rc rd l re rf">renderPass.bindPipeline(pipeline);<br/><br/>final verticesView = gpu.BufferView(<br/> verticesDeviceBuffer,<br/> offsetInBytes: 0,<br/> lengthInBytes: verticesDeviceBuffer.sizeInBytes,<br/>);<br/>renderPass.bindVertexBuffer(verticesView, 3);<br/><br/>renderPass.draw();</span></pre><p id="1d81" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">If you launch your app, you should now see a green triangle!</p><figure class="qc qd qe qf qg qh pz qa paragraph-image"><div role="button" tabindex="0" class="qi qj fj qk bh ql"><div class="pz qa qx"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/0*LWnGU5WPT_Eom0wJ 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*LWnGU5WPT_Eom0wJ 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*LWnGU5WPT_Eom0wJ 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*LWnGU5WPT_Eom0wJ 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*LWnGU5WPT_Eom0wJ 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*LWnGU5WPT_Eom0wJ 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/0*LWnGU5WPT_Eom0wJ 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/0*LWnGU5WPT_Eom0wJ 640w, https://miro.medium.com/v2/resize:fit:720/0*LWnGU5WPT_Eom0wJ 720w, https://miro.medium.com/v2/resize:fit:750/0*LWnGU5WPT_Eom0wJ 750w, https://miro.medium.com/v2/resize:fit:786/0*LWnGU5WPT_Eom0wJ 786w, https://miro.medium.com/v2/resize:fit:828/0*LWnGU5WPT_Eom0wJ 828w, https://miro.medium.com/v2/resize:fit:1100/0*LWnGU5WPT_Eom0wJ 1100w, https://miro.medium.com/v2/resize:fit:1400/0*LWnGU5WPT_Eom0wJ 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh mo qm c" width="700" height="568" loading="lazy" role="presentation"/></picture></div></div></figure><p id="0027" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Yay, you built a renderer from scratch with Flutter, Dart, and a little bit of GLSL!</p><p id="4b4a" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Whether this is your first time rendering a triangle or you're a seasoned graphics veteran, I hope you'll continue playing with Flutter GPU and check out the packages that we're working on, like Flutter Scene.</p><p id="1a02" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">In the future, we hope to publish beginner-friendly codelabs that dive deeper into Flutter GPU's default behavior and best practices. We still haven't talked about the vertex attribute layout, texture binding, uniforms and alignment requirements, pipeline blending, depth and stencil attachments, perspective correction, and so much more!</p><p id="09ef" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Until then, I'd recommend exploring <a class="af od" href="https://github.com/bdero/flutter_scene" rel="noopener ugc nofollow" target="_blank">Flutter Scene</a> as a more comprehensive example of how to use Flutter GPU.</p></div></div></div><div class="ab cb oq or os ot" role="separator"><span class="ou by bm ov ow ox"></span><span class="ou by bm ov ow ox"></span><span class="ou by bm ov ow"></span></div><div class="gn go gp gq gr"><div class="ab cb"><div class="ci bh fz ga gb gc"><h1 id="6b35" class="oy oz gu bf pa pb pc hu pd pe pf hx pg ph pi pj pk pl pm pn po pp pq pr ps pt bk">3D rendering with Flutter Scene</h1><p id="604b" class="pw-post-body-paragraph nh ni gu nj b hs pu nl nm hv pv no np nq pw ns nt nu px nw nx ny py oa ob oc gn bk">Flutter Scene (package <code class="cx oe of og oh b">flutter_scene</code>) is a new 3D scene graph package powered by Flutter GPU that enables Flutter developers to import animated glTF models and render realtime 3D scenes.</p><p id="0ff9" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">The intent is to provide a package that makes building interactive 3D apps and games easy in Flutter.</p><figure class="qc qd qe qf qg qh pz qa paragraph-image"><div role="button" tabindex="0" class="qi qj fj qk bh ql"><div class="pz qa rg"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/0*tC68CbPLef2rJp1e 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*tC68CbPLef2rJp1e 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*tC68CbPLef2rJp1e 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*tC68CbPLef2rJp1e 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*tC68CbPLef2rJp1e 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*tC68CbPLef2rJp1e 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/0*tC68CbPLef2rJp1e 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/0*tC68CbPLef2rJp1e 640w, https://miro.medium.com/v2/resize:fit:720/0*tC68CbPLef2rJp1e 720w, https://miro.medium.com/v2/resize:fit:750/0*tC68CbPLef2rJp1e 750w, https://miro.medium.com/v2/resize:fit:786/0*tC68CbPLef2rJp1e 786w, https://miro.medium.com/v2/resize:fit:828/0*tC68CbPLef2rJp1e 828w, https://miro.medium.com/v2/resize:fit:1100/0*tC68CbPLef2rJp1e 1100w, https://miro.medium.com/v2/resize:fit:1400/0*tC68CbPLef2rJp1e 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh mo qm c" width="700" height="807" loading="eager" role="presentation"/></picture></div></div></figure><p id="9283" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">This package started life as a <code class="cx oe of og oh b">dart:ui</code> extension for a 3D renderer written in C++ and built directly into Flutter's native runtime, but it's been rewritten against Flutter GPU with a more flexible interface.</p><p id="4a63" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">As with the Flutter GPU API itself, Flutter Scene is currently in an early preview state and requires <a class="af od" href="https://docs.flutter.dev/perf/impeller#availability" rel="noopener ugc nofollow" target="_blank">Impeller to be enabled</a>. Flutter Scene generally keeps up to date with breaking changes to Flutter GPU's API, and so it's strongly recommended to use the <a class="af od" href="https://docs.flutter.dev/release/upgrade#other-channels" rel="noopener ugc nofollow" target="_blank">main channel</a> when experimenting with Flutter Scene.</p><p id="6656" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Next, make an app with Flutter Scene!</p><h1 id="146f" class="oy oz gu bf pa pb qr hu pd pe qs hx pg ph qt pj pk pl qu pn po pp qv pr ps pt bk">Set up a Flutter Scene project</h1><p id="f18d" class="pw-post-body-paragraph nh ni gu nj b hs pu nl nm hv pv no np nq pw ns nt nu px nw nx ny py oa ob oc gn bk">Since it's strongly recommended to use Flutter Scene against the <a class="af od" href="https://docs.flutter.dev/release/upgrade#other-channels" rel="noopener ugc nofollow" target="_blank">main channel</a>, start off by switching to it.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="700d" class="rb oz gu oh b bg rc rd l re rf">flutter channel main<br/>flutter upgrade</span></pre><p id="e994" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Next, create a fresh Flutter project.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="3722" class="rb oz gu oh b bg rc rd l re rf">flutter create my_3d_app<br/>cd my_3d_app</span></pre><p id="6e34" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Flutter Scene relies on the experimental "native assets" feature for automatically building shaders. You’ll use native assets in a moment to set up automatic importing of 3D models for Flutter Scene.</p><p id="378c" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Enable native assets with the following command.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="7f85" class="rb oz gu oh b bg rc rd l re rf">flutter config --enable-native-assets</span></pre><p id="61a3" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">And finally, add Flutter Scene as a project dependency.</p><p id="8aae" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">You’ll also need to use several <code class="cx oe of og oh b">vector_math</code> constructs while interacting with Flutter Scene's API, so add the <code class="cx oe of og oh b">vector_math</code> package as well.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="e0b3" class="rb oz gu oh b bg rc rd l re rf">flutter pub add flutter_scene vector_math</span></pre><p id="a6a6" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Next, import a 3D model!</p><h1 id="c0de" class="oy oz gu bf pa pb qr hu pd pe qs hx pg ph qt pj pk pl qu pn po pp qv pr ps pt bk">Import a 3D model</h1><p id="c0f3" class="pw-post-body-paragraph nh ni gu nj b hs pu nl nm hv pv no np nq pw ns nt nu px nw nx ny py oa ob oc gn bk">First, you need a 3D model to render. For this guide, you’ll use a common <a class="af od" href="https://en.wikipedia.org/wiki/GlTF" rel="noopener ugc nofollow" target="_blank">glTF</a> sample asset: <a class="af od" href="https://github.com/KhronosGroup/glTF-Sample-Assets/tree/main/Models/DamagedHelmet" rel="noopener ugc nofollow" target="_blank">DamagedHelmet.glb</a>. Here's what it looks like.</p><figure class="qc qd qe qf qg qh pz qa paragraph-image"><div role="button" tabindex="0" class="qi qj fj qk bh ql"><div class="pz qa rh"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/0*vVWRLxJ348tCxv7T 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*vVWRLxJ348tCxv7T 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*vVWRLxJ348tCxv7T 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*vVWRLxJ348tCxv7T 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*vVWRLxJ348tCxv7T 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*vVWRLxJ348tCxv7T 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/0*vVWRLxJ348tCxv7T 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/0*vVWRLxJ348tCxv7T 640w, https://miro.medium.com/v2/resize:fit:720/0*vVWRLxJ348tCxv7T 720w, https://miro.medium.com/v2/resize:fit:750/0*vVWRLxJ348tCxv7T 750w, https://miro.medium.com/v2/resize:fit:786/0*vVWRLxJ348tCxv7T 786w, https://miro.medium.com/v2/resize:fit:828/0*vVWRLxJ348tCxv7T 828w, https://miro.medium.com/v2/resize:fit:1100/0*vVWRLxJ348tCxv7T 1100w, https://miro.medium.com/v2/resize:fit:1400/0*vVWRLxJ348tCxv7T 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh mo qm c" width="700" height="568" loading="lazy" role="presentation"/></picture></div></div><figcaption class="qn ff qo pz qa qp qq bf b bg z du">The original Damaged Helmet model was created by theblueturtle_ in 2016 (license: <a class="af od" href="https://creativecommons.org/licenses/by-nc/4.0/legalcode" rel="noopener ugc nofollow" target="_blank">CC BY-NC 4.0 International</a>). The converted glTF version was created by ctxwing in 2018 (license: <a class="af od" href="https://creativecommons.org/licenses/by/4.0/legalcode" rel="noopener ugc nofollow" target="_blank">CC BY 4.0 International</a>)</figcaption></figure><p id="ae63" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">You can grab it from the <a class="af od" href="https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/DamagedHelmet/glTF-Binary/DamagedHelmet.glb" rel="noopener ugc nofollow" target="_blank">glTF sample assets repository</a> hosted on GitHub. Place DamagedHelmet.glb in your project root.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="3181" class="rb oz gu oh b bg rc rd l re rf">curl -O https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/main/2.0/DamagedHelmet/glTF-Binary/DamagedHelmet.glb</span></pre><p id="312d" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Like most real-time 3D renderers, Flutter Scene uses a specialized 3D model format internally. You can convert standard glTF binaries (.glb files) to this format using Flutter Scene's offline importer tool.</p><p id="4bbe" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Add the <code class="cx oe of og oh b">flutter_scene_importer</code> package to the project as a regular dependency.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="a477" class="rb oz gu oh b bg rc rd l re rf">flutter pub add flutter_scene_importer</span></pre><p id="43f9" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk"><strong class="nj gv">Note:</strong> The importer will automatically build itself using CMake when invoked. So be sure to <a class="af od" href="https://cmake.org/download/" rel="noopener ugc nofollow" target="_blank">install CMake</a>. Otherwise, you will run into “<strong class="nj gv"><em class="ri">ProcessException: No such file or directory”</em></strong> errors.</p><p id="6bc3" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Adding this package makes it possible to invoke the importer manually using <code class="cx oe of og oh b">dart run</code>.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="536d" class="rb oz gu oh b bg rc rd l re rf">dart --enable-experiment=native-assets \<br/> run flutter_scene_importer:import \<br/> --input "path/to/my/source_model.glb" \<br/> --output "path/to/my/imported_model.model"</span></pre><p id="0bf5" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">You can automatically run the importer by using a native assets build hook. To do this, first install <code class="cx oe of og oh b">native_assets_cli</code> as a regular project dependency.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="cf58" class="rb oz gu oh b bg rc rd l re rf">flutter pub add native_assets_cli</span></pre><p id="7b18" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">You can now write the build hook. Create <code class="cx oe of og oh b">hook/build.dart</code> with the following contents.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="9b31" class="rb oz gu oh b bg rc rd l re rf">import 'package:native_assets_cli/native_assets_cli.dart';<br/>import 'package:flutter_scene_importer/build_hooks.dart';<br/><br/>void main(List<String> args) {<br/> build(args, (config, output) async {<br/> buildModels(buildConfig: config, inputFilePaths: [<br/> 'DamagedHelmet.glb',<br/> ]);<br/> });<br/>}</span></pre><p id="d480" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Using the <code class="cx oe of og oh b">buildModels</code> utility from <code class="cx oe of og oh b">flutter_scene_importer</code>, supply a list of models to build. The paths are relative to the build root of the project.</p><p id="f390" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">When the Flutter tool is building the project, <code class="cx oe of og oh b">buildModels</code> now builds the shader bundle and outputs the result to <code class="cx oe of og oh b">build/models/DamagedModel.model</code> under the package root.</p><p id="684a" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">The imported model format itself is tied to the specific version of Flutter Scene you're using and will change over time. When authoring an app or library that uses Flutter Scene, do not check the generated <code class="cx oe of og oh b">.model</code> files into your source tree. Instead, use a build hook to generate them from your source models (as previously explained).</p><p id="1f03" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">This way, you'll always build fresh .model files with the correct format as you upgrade Flutter Scene over time!</p><p id="4bad" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Next, import the model like a regular asset. Add an asset entry to your project's <code class="cx oe of og oh b">pubspec.yaml</code>.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="2a6e" class="rb oz gu oh b bg rc rd l re rf">flutter:<br/> assets:<br/> - build/models/</span></pre><p id="f421" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">In the future, the native assets feature will allow build hooks to append data assets to the bundle. Once this happens, it will no longer be necessary to add an asset import rule alongside the build hook.</p><h1 id="df48" class="oy oz gu bf pa pb qr hu pd pe qs hx pg ph qt pj pk pl qu pn po pp qv pr ps pt bk">Rendering a 3D scene</h1><p id="44ba" class="pw-post-body-paragraph nh ni gu nj b hs pu nl nm hv pv no np nq pw ns nt nu px nw nx ny py oa ob oc gn bk">Now for the app's code.</p><p id="192b" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">First, create a stateful widget to persist a <code class="cx oe of og oh b">Scene</code> across frames.</p><p id="4b75" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">You’ll be animating based on time, so add the <code class="cx oe of og oh b">SingleTickerProviderStateMixin</code> to the state along with an <code class="cx oe of og oh b">elapsedSeconds</code> member.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="0d76" class="rb oz gu oh b bg rc rd l re rf">import 'dart:math';<br/><br/>import 'package:flutter/material.dart';<br/>import 'package:flutter/scheduler.dart';<br/>import 'package:flutter_scene/camera.dart';<br/>import 'package:flutter_scene/node.dart';<br/>import 'package:flutter_scene/scene.dart';<br/>import 'package:vector_math/vector_math.dart';<br/><br/>void main() {<br/> runApp(const MyApp());<br/>}<br/><br/>class MyApp extends StatefulWidget{<br/> const MyApp({super.key});<br/><br/> @override<br/> MyAppState createState() => MyAppState();<br/>}<br/><br/>class MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {<br/> double elapsedSeconds = 0;<br/> Scene scene = Scene();<br/><br/> @override<br/> Widget build(BuildContext context) {<br/> return MaterialApp(<br/> title: 'My 3D app',<br/> home: Placeholder(),<br/> );<br/> }<br/>}</span></pre><p id="3379" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Run the app as a smoke test to ensure there are no errors. And remember to <a class="af od" href="https://docs.flutter.dev/perf/impeller#availability" rel="noopener ugc nofollow" target="_blank">enable Impeller</a>!</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="ad46" class="rb oz gu oh b bg rc rd l re rf">flutter run -d macos --enable-impeller</span></pre><figure class="qc qd qe qf qg qh pz qa paragraph-image"><div role="button" tabindex="0" class="qi qj fj qk bh ql"><div class="pz qa rh"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/0*74qs6ytcTjyVHwML 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*74qs6ytcTjyVHwML 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*74qs6ytcTjyVHwML 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*74qs6ytcTjyVHwML 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*74qs6ytcTjyVHwML 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*74qs6ytcTjyVHwML 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/0*74qs6ytcTjyVHwML 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/0*74qs6ytcTjyVHwML 640w, https://miro.medium.com/v2/resize:fit:720/0*74qs6ytcTjyVHwML 720w, https://miro.medium.com/v2/resize:fit:750/0*74qs6ytcTjyVHwML 750w, https://miro.medium.com/v2/resize:fit:786/0*74qs6ytcTjyVHwML 786w, https://miro.medium.com/v2/resize:fit:828/0*74qs6ytcTjyVHwML 828w, https://miro.medium.com/v2/resize:fit:1100/0*74qs6ytcTjyVHwML 1100w, https://miro.medium.com/v2/resize:fit:1400/0*74qs6ytcTjyVHwML 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh mo qm c" width="700" height="568" loading="lazy" role="presentation"/></picture></div></div></figure><p id="4a4d" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Before continuing, set up the ticker for the animation. Override <code class="cx oe of og oh b">initState</code> in <code class="cx oe of og oh b">MyAppState</code> to call <code class="cx oe of og oh b">createTicker</code>.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="3227" class="rb oz gu oh b bg rc rd l re rf"> @override<br/> void initState() {<br/> createTicker((elapsed) {<br/> setState(() {<br/> elapsedSeconds = elapsed.inMilliseconds.toDouble() / 1000;<br/> });<br/> }).start();<br/><br/> super.initState();<br/> }</span></pre><p id="21ae" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">As long as the widget is visible, the ticker callback is invoked for every frame. Calling <code class="cx oe of og oh b">setState</code> triggers this widget to rebuild every frame.</p><p id="ba41" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Next, load up the 3D model that you placed in the project earlier and add it to the Scene.</p><p id="0530" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Use <code class="cx oe of og oh b">Node.fromAsset</code> to load the model from the asset bundle. Place the following code in <code class="cx oe of og oh b">initState</code>.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="65c4" class="rb oz gu oh b bg rc rd l re rf"> Node.fromAsset('build/models/DamagedHelmet.model').then((model) {<br/> model.name = 'Helmet';<br/> scene.add(model);<br/> });</span></pre><p id="c41f" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk"><code class="cx oe of og oh b">Node.fromAsset</code> asynchronously deserializes the model from the asset bundle and resolves the returned <code class="cx oe of og oh b">Future<Node></code> once it's ready to be added to the scene.</p><p id="3b5d" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">The <code class="cx oe of og oh b">MyAppState.initState</code> should now look as follows:</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="e452" class="rb oz gu oh b bg rc rd l re rf"> @override<br/> void initState() {<br/> createTicker((elapsed) {<br/> setState(() {<br/> elapsedSeconds = elapsed.inMilliseconds.toDouble() / 1000;<br/> });<br/> }).start();<br/><br/> Node.fromAsset('build/models/DamagedHelmet.model').then((model) {<br/> model.name = 'Helmet';<br/> scene.add(model);<br/> });<br/><br/> super.initState();<br/> }</span></pre><p id="a941" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">However, you’re still not actually rendering the 3D Scene yet! To do this, use <code class="cx oe of og oh b">Scene.render</code>, which takes a UI <code class="cx oe of og oh b">Canvas</code>, a Flutter Scene <code class="cx oe of og oh b">Camera</code>, and a size.</p><p id="8888" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">One way to access a Canvas is to create a <code class="cx oe of og oh b">CustomPainter</code>:</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="d229" class="rb oz gu oh b bg rc rd l re rf">class ScenePainter extends CustomPainter {<br/> ScenePainter({required this.scene, required this.camera});<br/> Scene scene;<br/> Camera camera;<br/><br/> @override<br/> void paint(Canvas canvas, Size size) {<br/> scene.render(camera, canvas, viewport: Offset.zero & size);<br/> }<br/><br/> @override<br/> bool shouldRepaint(covariant CustomPainter oldDelegate) => true;<br/>}</span></pre><p id="4d7d" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Don't forget to set the <code class="cx oe of og oh b">shouldRepaint</code> override to return true so that the custom painter will repaint whenever a rebuild occurs.</p><p id="ac59" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Lastly, add the <code class="cx oe of og oh b">CustomPainter</code> to the source tree.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="a59e" class="rb oz gu oh b bg rc rd l re rf"> @override<br/> Widget build(BuildContext context) {<br/> final painter = ScenePainter(<br/> scene: scene,<br/> camera: PerspectiveCamera(<br/> position: Vector3(sin(elapsedSeconds) * 3, 2, cos(elapsedSeconds) * 3),<br/> target: Vector3(0, 0, 0),<br/> ),<br/> );<br/><br/> return MaterialApp(<br/> title: 'My 3D app',<br/> home: CustomPaint(painter: painter),<br/> );<br/> }</span></pre><p id="52d1" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">This code instructs the camera to move in a continuous circle, but always facing towards the origin.</p><p id="de22" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Finally, start the app!</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="afba" class="rb oz gu oh b bg rc rd l re rf">flutter run -d macos --enable-impeller</span></pre><figure class="qc qd qe qf qg qh pz qa paragraph-image"><div role="button" tabindex="0" class="qi qj fj qk bh ql"><div class="pz qa rj"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/0*_-OFc0vhBHAhrPrO 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*_-OFc0vhBHAhrPrO 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*_-OFc0vhBHAhrPrO 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*_-OFc0vhBHAhrPrO 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*_-OFc0vhBHAhrPrO 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*_-OFc0vhBHAhrPrO 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/0*_-OFc0vhBHAhrPrO 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/0*_-OFc0vhBHAhrPrO 640w, https://miro.medium.com/v2/resize:fit:720/0*_-OFc0vhBHAhrPrO 720w, https://miro.medium.com/v2/resize:fit:750/0*_-OFc0vhBHAhrPrO 750w, https://miro.medium.com/v2/resize:fit:786/0*_-OFc0vhBHAhrPrO 786w, https://miro.medium.com/v2/resize:fit:828/0*_-OFc0vhBHAhrPrO 828w, https://miro.medium.com/v2/resize:fit:1100/0*_-OFc0vhBHAhrPrO 1100w, https://miro.medium.com/v2/resize:fit:1400/0*_-OFc0vhBHAhrPrO 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh mo qm c" width="700" height="551" loading="lazy" role="presentation"/></picture></div></div></figure><p id="a1b3" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Here's the full source we put together.</p><pre class="qc qd qe qf qg qy oh qz bp ra bb bk"><span id="216a" class="rb oz gu oh b bg rc rd l re rf">import 'dart:math';<br/><br/>import 'package:flutter/material.dart';<br/>import 'package:flutter_scene/camera.dart';<br/>import 'package:flutter_scene/node.dart';<br/>import 'package:flutter_scene/scene.dart';<br/>import 'package:vector_math/vector_math.dart';<br/><br/>void main() {<br/> runApp(const MyApp());<br/>}<br/><br/>class MyApp extends StatefulWidget {<br/> const MyApp({super.key});<br/><br/> @override<br/> MyAppState createState() => MyAppState();<br/>}<br/><br/>class MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {<br/> double elapsedSeconds = 0;<br/> Scene scene = Scene();<br/><br/> @override<br/> void initState() {<br/> createTicker((elapsed) {<br/> setState(() {<br/> elapsedSeconds = elapsed.inMilliseconds.toDouble() / 1000;<br/> });<br/> }).start();<br/><br/> Node.fromAsset('build/models/DamagedHelmet.model').then((model) {<br/> model.name = 'Helmet';<br/> scene.add(model);<br/> });<br/><br/> super.initState();<br/> }<br/><br/> @override<br/> Widget build(BuildContext context) {<br/> final painter = ScenePainter(<br/> scene: scene,<br/> camera: PerspectiveCamera(<br/> position: Vector3(sin(elapsedSeconds) * 3, 2, cos(elapsedSeconds) * 3),<br/> target: Vector3(0, 0, 0),<br/> ),<br/> );<br/><br/> return MaterialApp(<br/> title: 'My 3D app',<br/> home: CustomPaint(painter: painter),<br/> );<br/> }<br/>}<br/><br/>class ScenePainter extends CustomPainter {<br/> ScenePainter({required this.scene, required this.camera});<br/> Scene scene;<br/> Camera camera;<br/><br/> @override<br/> void paint(Canvas canvas, Size size) {<br/> scene.render(camera, canvas, viewport: Offset.zero & size);<br/> }<br/><br/> @override<br/> bool shouldRepaint(covariant CustomPainter oldDelegate) => true;<br/>}</span></pre><h1 id="08f8" class="oy oz gu bf pa pb qr hu pd pe qs hx pg ph qt pj pk pl qu pn po pp qv pr ps pt bk">Flutter’s great future ahead</h1><p id="cee8" class="pw-post-body-paragraph nh ni gu nj b hs pu nl nm hv pv no np nq pw ns nt nu px nw nx ny py oa ob oc gn bk">If you were able to successfully follow one of these guides and get something up and running: Woohoo, congrats!</p><p id="3e15" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Both Flutter GPU and Flutter Scene are very young efforts with limited platform support. But I think someday we'll look back fondly at these humble beginnings.</p><p id="6ac6" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">With the Impeller effort, the Flutter team took full ownership over the rendering stack because we needed to specialize the renderer towards Flutter's use case. And now we're starting a new chapter in Flutter's history. One where YOU collectively take control over the rendering!</p><p id="d646" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">Flutter Scene started as a C++ component in Impeller alongside the 2D Canvas renderer with a slim <code class="cx oe of og oh b">dart:ui</code> extension. By the time I was building it, I was already aware that Flutter Engine wouldn't be its final destination.</p><p id="0a90" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">The sea of architecture decisions for 3D renderers is vast, and no single generic 3D renderer can solve every use case well. "Generic" and "high performance" are generally antithetical goals.</p><p id="186d" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">At best, adequacy at everything all but guarantees excellence at nothing.</p><p id="0a2c" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">In the world of rendering performance, the situation is even worse. Specializing for one use case often means degrading the performance of another.</p><p id="34fe" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">In short, it's just not possible to ship a generic 3D renderer that can solve every use case for everyone. But, by surfacing the low level APIs necessary for you to build your own solutions (Flutter GPU), and building a useful generic 3D renderer on top of it that's easy for the Flutter community to inspect and modify (Flutter Scene), we’re carving out the space for Flutter developers to enjoy low-risk for obsolescence and a high reward.</p><figure class="qc qd qe qf qg qh pz qa paragraph-image"><div class="pz qa rk"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/1*jfeUgpEP9AgAz94yVxVW1g.gif 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*jfeUgpEP9AgAz94yVxVW1g.gif 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*jfeUgpEP9AgAz94yVxVW1g.gif 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*jfeUgpEP9AgAz94yVxVW1g.gif 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*jfeUgpEP9AgAz94yVxVW1g.gif 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*jfeUgpEP9AgAz94yVxVW1g.gif 1100w, https://miro.medium.com/v2/resize:fit:1012/format:webp/1*jfeUgpEP9AgAz94yVxVW1g.gif 1012w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 506px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/1*jfeUgpEP9AgAz94yVxVW1g.gif 640w, https://miro.medium.com/v2/resize:fit:720/1*jfeUgpEP9AgAz94yVxVW1g.gif 720w, https://miro.medium.com/v2/resize:fit:750/1*jfeUgpEP9AgAz94yVxVW1g.gif 750w, https://miro.medium.com/v2/resize:fit:786/1*jfeUgpEP9AgAz94yVxVW1g.gif 786w, https://miro.medium.com/v2/resize:fit:828/1*jfeUgpEP9AgAz94yVxVW1g.gif 828w, https://miro.medium.com/v2/resize:fit:1100/1*jfeUgpEP9AgAz94yVxVW1g.gif 1100w, https://miro.medium.com/v2/resize:fit:1012/1*jfeUgpEP9AgAz94yVxVW1g.gif 1012w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 506px"/><img alt="" class="bh mo qm c" width="506" height="360" loading="lazy" role="presentation"/></picture></div></figure><p id="da72" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">I can't wait to see what you'll make with these new capabilities. Stay tuned for future releases of Flutter Scene. There's a lot on the way.</p><p id="c0e3" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">In the meantime, I’m heading back to work.</p><p id="47af" class="pw-post-body-paragraph nh ni gu nj b hs nk nl nm hv nn no np nq nr ns nt nu nv nw nx ny nz oa ob oc gn bk">See you soon. :)</p></div></div></div></div></section></div></div></article></div><div class="ab cb"><div class="ci bh fz ga gb gc"><div class="rl rm ab jm"><div class="rn ab"><a class="ro ay am ao" rel="noopener follow" href="/tag/flutter?source=post_page-----f33d497b7c11---------------------------------------"><div class="rp fj cx rq ge rr rs bf b bg z bk rt">Flutter</div></a></div><div class="rn ab"><a class="ro ay am ao" rel="noopener follow" href="/tag/graphics-programming?source=post_page-----f33d497b7c11---------------------------------------"><div class="rp fj cx rq ge rr rs bf b bg z bk rt">Graphics Programming</div></a></div><div class="rn ab"><a class="ro ay am ao" rel="noopener follow" href="/tag/3d?source=post_page-----f33d497b7c11---------------------------------------"><div class="rp fj cx rq ge rr rs bf b bg z bk rt">3d</div></a></div><div class="rn ab"><a class="ro ay am ao" rel="noopener follow" href="/tag/guides-and-tutorials?source=post_page-----f33d497b7c11---------------------------------------"><div class="rp fj cx rq ge rr rs bf b bg z bk rt">Guides And Tutorials</div></a></div><div class="rn ab"><a class="ro ay am ao" rel="noopener follow" href="/tag/game-development?source=post_page-----f33d497b7c11---------------------------------------"><div class="rp fj cx rq ge rr rs bf b bg z bk rt">Game Development</div></a></div></div></div></div><div class="l"></div><footer class="ru rv rw rx ry ab q rz iw c"><div class="l ae"><div class="ab cb"><div class="ci bh fz ga gb gc"><div class="ab cp sa"><div class="ab q lf"><div class="sb l"><span class="l sc sd se e d"><div class="ab q lf lg"><div class="pw-multi-vote-icon fj jo lh li lj"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="footerClapButton" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fvote%2Fflutter%2Ff33d497b7c11&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fflutter%2Fgetting-started-with-flutter-gpu-f33d497b7c11&user=Brandon+DeRosier&userId=9a1282e0b0ee&source=---footer_actions--f33d497b7c11---------------------clap_footer------------------"><div><div class="bm" aria-hidden="false"><div class="lk ao ll lm ln lo am lp lq lr lj"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-label="clap"><path fill-rule="evenodd" d="M11.37.828 12 3.282l.63-2.454zM13.916 3.953l1.523-2.112-1.184-.39zM8.589 1.84l1.522 2.112-.337-2.501zM18.523 18.92c-.86.86-1.75 1.246-2.62 1.33a6 6 0 0 0 .407-.372c2.388-2.389 2.86-4.951 1.399-7.623l-.912-1.603-.79-1.672c-.26-.56-.194-.98.203-1.288a.7.7 0 0 1 .546-.132c.283.046.546.231.728.5l2.363 4.157c.976 1.624 1.141 4.237-1.324 6.702m-10.999-.438L3.37 14.328a.828.828 0 0 1 .585-1.408.83.83 0 0 1 .585.242l2.158 2.157a.365.365 0 0 0 .516-.516l-2.157-2.158-1.449-1.449a.826.826 0 0 1 1.167-1.17l3.438 3.44a.363.363 0 0 0 .516 0 .364.364 0 0 0 0-.516L5.293 9.513l-.97-.97a.826.826 0 0 1 0-1.166.84.84 0 0 1 1.167 0l.97.968 3.437 3.436a.36.36 0 0 0 .517 0 .366.366 0 0 0 0-.516L6.977 7.83a.82.82 0 0 1-.241-.584.82.82 0 0 1 .824-.826c.219 0 .43.087.584.242l5.787 5.787a.366.366 0 0 0 .587-.415l-1.117-2.363c-.26-.56-.194-.98.204-1.289a.7.7 0 0 1 .546-.132c.283.046.545.232.727.501l2.193 3.86c1.302 2.38.883 4.59-1.277 6.75-1.156 1.156-2.602 1.627-4.19 1.367-1.418-.236-2.866-1.033-4.079-2.246M10.75 5.971l2.12 2.12c-.41.502-.465 1.17-.128 1.89l.22.465-3.523-3.523a.8.8 0 0 1-.097-.368c0-.22.086-.428.241-.584a.847.847 0 0 1 1.167 0m7.355 1.705c-.31-.461-.746-.758-1.23-.837a1.44 1.44 0 0 0-1.11.275c-.312.24-.505.543-.59.881a1.74 1.74 0 0 0-.906-.465 1.47 1.47 0 0 0-.82.106l-2.182-2.182a1.56 1.56 0 0 0-2.2 0 1.54 1.54 0 0 0-.396.701 1.56 1.56 0 0 0-2.21-.01 1.55 1.55 0 0 0-.416.753c-.624-.624-1.649-.624-2.237-.037a1.557 1.557 0 0 0 0 2.2c-.239.1-.501.238-.715.453a1.56 1.56 0 0 0 0 2.2l.516.515a1.556 1.556 0 0 0-.753 2.615L7.01 19c1.32 1.319 2.909 2.189 4.475 2.449q.482.08.971.08c.85 0 1.653-.198 2.393-.579.231.033.46.054.686.054 1.266 0 2.457-.52 3.505-1.567 2.763-2.763 2.552-5.734 1.439-7.586z" clip-rule="evenodd"></path></svg></div></div></div></a></span></div><div class="pw-multi-vote-count l ls lt lu lv lw lx ly"><p class="bf b dv z du"><span class="lz">--</span></p></div></div></span><span class="l h g f sf sg"><div class="ab q lf lg"><div class="pw-multi-vote-icon fj jo lh li lj"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="footerClapButton" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fvote%2Fflutter%2Ff33d497b7c11&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fflutter%2Fgetting-started-with-flutter-gpu-f33d497b7c11&user=Brandon+DeRosier&userId=9a1282e0b0ee&source=---footer_actions--f33d497b7c11---------------------clap_footer------------------"><div><div class="bm" aria-hidden="false"><div class="lk ao ll lm ln lo am lp lq lr lj"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-label="clap"><path fill-rule="evenodd" d="M11.37.828 12 3.282l.63-2.454zM13.916 3.953l1.523-2.112-1.184-.39zM8.589 1.84l1.522 2.112-.337-2.501zM18.523 18.92c-.86.86-1.75 1.246-2.62 1.33a6 6 0 0 0 .407-.372c2.388-2.389 2.86-4.951 1.399-7.623l-.912-1.603-.79-1.672c-.26-.56-.194-.98.203-1.288a.7.7 0 0 1 .546-.132c.283.046.546.231.728.5l2.363 4.157c.976 1.624 1.141 4.237-1.324 6.702m-10.999-.438L3.37 14.328a.828.828 0 0 1 .585-1.408.83.83 0 0 1 .585.242l2.158 2.157a.365.365 0 0 0 .516-.516l-2.157-2.158-1.449-1.449a.826.826 0 0 1 1.167-1.17l3.438 3.44a.363.363 0 0 0 .516 0 .364.364 0 0 0 0-.516L5.293 9.513l-.97-.97a.826.826 0 0 1 0-1.166.84.84 0 0 1 1.167 0l.97.968 3.437 3.436a.36.36 0 0 0 .517 0 .366.366 0 0 0 0-.516L6.977 7.83a.82.82 0 0 1-.241-.584.82.82 0 0 1 .824-.826c.219 0 .43.087.584.242l5.787 5.787a.366.366 0 0 0 .587-.415l-1.117-2.363c-.26-.56-.194-.98.204-1.289a.7.7 0 0 1 .546-.132c.283.046.545.232.727.501l2.193 3.86c1.302 2.38.883 4.59-1.277 6.75-1.156 1.156-2.602 1.627-4.19 1.367-1.418-.236-2.866-1.033-4.079-2.246M10.75 5.971l2.12 2.12c-.41.502-.465 1.17-.128 1.89l.22.465-3.523-3.523a.8.8 0 0 1-.097-.368c0-.22.086-.428.241-.584a.847.847 0 0 1 1.167 0m7.355 1.705c-.31-.461-.746-.758-1.23-.837a1.44 1.44 0 0 0-1.11.275c-.312.24-.505.543-.59.881a1.74 1.74 0 0 0-.906-.465 1.47 1.47 0 0 0-.82.106l-2.182-2.182a1.56 1.56 0 0 0-2.2 0 1.54 1.54 0 0 0-.396.701 1.56 1.56 0 0 0-2.21-.01 1.55 1.55 0 0 0-.416.753c-.624-.624-1.649-.624-2.237-.037a1.557 1.557 0 0 0 0 2.2c-.239.1-.501.238-.715.453a1.56 1.56 0 0 0 0 2.2l.516.515a1.556 1.556 0 0 0-.753 2.615L7.01 19c1.32 1.319 2.909 2.189 4.475 2.449q.482.08.971.08c.85 0 1.653-.198 2.393-.579.231.033.46.054.686.054 1.266 0 2.457-.52 3.505-1.567 2.763-2.763 2.552-5.734 1.439-7.586z" clip-rule="evenodd"></path></svg></div></div></div></a></span></div><div class="pw-multi-vote-count l ls lt lu lv lw lx ly"><p class="bf b dv z du"><span class="lz">--</span></p></div></div></span></div><div class="bq ab"><div><div class="bm" aria-hidden="false"><button class="ao lk mc md ab q fk me mf" aria-label="responses"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="mb"><path d="M18.006 16.803c1.533-1.456 2.234-3.325 2.234-5.321C20.24 7.357 16.709 4 12.191 4S4 7.357 4 11.482c0 4.126 3.674 7.482 8.191 7.482.817 0 1.622-.111 2.393-.327.231.2.48.391.744.559 1.06.693 2.203 1.044 3.399 1.044.224-.008.4-.112.486-.287a.49.49 0 0 0-.042-.518c-.495-.67-.845-1.364-1.04-2.057a4 4 0 0 1-.125-.598zm-3.122 1.055-.067-.223-.315.096a8 8 0 0 1-2.311.338c-4.023 0-7.292-2.955-7.292-6.587 0-3.633 3.269-6.588 7.292-6.588 4.014 0 7.112 2.958 7.112 6.593 0 1.794-.608 3.469-2.027 4.72l-.195.168v.255c0 .056 0 .151.016.295.025.231.081.478.154.733.154.558.398 1.117.722 1.659a5.3 5.3 0 0 1-2.165-.845c-.276-.176-.714-.383-.941-.59z"></path></svg><p class="bf b bg z du"><span class="pw-responses-count ma mb">11</span></p></button></div></div></div></div><div class="ab q"><div class="ox l jj"><div><div class="bm" aria-hidden="false"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="footerBookmarkButton" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2Ff33d497b7c11&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fflutter%2Fgetting-started-with-flutter-gpu-f33d497b7c11&source=---footer_actions--f33d497b7c11---------------------bookmark_footer------------------"><svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" fill="none" viewBox="0 0 25 25" class="du mh" aria-label="Add to list bookmark button"><path fill="currentColor" d="M18 2.5a.5.5 0 0 1 1 0V5h2.5a.5.5 0 0 1 0 1H19v2.5a.5.5 0 1 1-1 0V6h-2.5a.5.5 0 0 1 0-1H18zM7 7a1 1 0 0 1 1-1h3.5a.5.5 0 0 0 0-1H8a2 2 0 0 0-2 2v14a.5.5 0 0 0 .805.396L12.5 17l5.695 4.396A.5.5 0 0 0 19 21v-8.5a.5.5 0 0 0-1 0v7.485l-5.195-4.012a.5.5 0 0 0-.61 0L7 19.985z"></path></svg></a></span></div></div></div><div class="ox l jj"><div class="bm" aria-hidden="false" aria-describedby="postFooterSocialMenu" aria-labelledby="postFooterSocialMenu"><div><div class="bm" aria-hidden="false"><button aria-controls="postFooterSocialMenu" aria-expanded="false" aria-label="Share Post" data-testid="footerSocialShareButton" class="af fk ah ai aj ak al mp an ao ap ex mq mr mf ms"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M15.218 4.931a.4.4 0 0 1-.118.132l.012.006a.45.45 0 0 1-.292.074.5.5 0 0 1-.3-.13l-2.02-2.02v7.07c0 .28-.23.5-.5.5s-.5-.22-.5-.5v-7.04l-2 2a.45.45 0 0 1-.57.04h-.02a.4.4 0 0 1-.16-.3.4.4 0 0 1 .1-.32l2.8-2.8a.5.5 0 0 1 .7 0l2.8 2.79a.42.42 0 0 1 .068.498m-.106.138.008.004v-.01zM16 7.063h1.5a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-11c-1.1 0-2-.9-2-2v-10a2 2 0 0 1 2-2H8a.5.5 0 0 1 .35.15.5.5 0 0 1 .15.35.5.5 0 0 1-.15.35.5.5 0 0 1-.35.15H6.4c-.5 0-.9.4-.9.9v10.2a.9.9 0 0 0 .9.9h11.2c.5 0 .9-.4.9-.9v-10.2c0-.5-.4-.9-.9-.9H16a.5.5 0 0 1 0-1" clip-rule="evenodd"></path></svg></button></div></div></div></div></div></div></div></div></div></footer><div class="sh l"><div><div class="ab cb"><div class="ci bh fz ga gb gc"><div class="si l"><div class="ab sj sk sl jl jk"><div class="sm sn so sp sq sr ss st su sv ab cp"><div class="h k"><a href="https://medium.com/flutter?source=post_page---post_publication_info--f33d497b7c11---------------------------------------" rel="noopener follow"><div class="fj ab"><img alt="Flutter" class="sw in io cx" src="https://miro.medium.com/v2/resize:fill:96:96/1*5-aoK8IBmXve5whBQM90GA.png" width="48" height="48" loading="lazy"/><div class="sw l io in fs n fr sx"></div></div></a></div><div class="j i d"><a href="https://medium.com/flutter?source=post_page---post_publication_info--f33d497b7c11---------------------------------------" rel="noopener follow"><div class="fj ab"><img alt="Flutter" class="sw sz sy cx" src="https://miro.medium.com/v2/resize:fill:128:128/1*5-aoK8IBmXve5whBQM90GA.png" width="64" height="64" loading="lazy"/><div class="sw l sy sz fs n fr sx"></div></div></a></div><div class="j i d ta jj"><div class="ab"></div></div></div><div class="ab co tb"><div class="tc td te tf tg l"><a class="af ag ah aj ak al am an ao ap aq ar as at ab q" href="https://medium.com/flutter?source=post_page---post_publication_info--f33d497b7c11---------------------------------------" rel="noopener follow"><h2 class="pw-author-name bf ti tj tk tl tm tn to nq tp tq nu tr ts ny tt tu bk"><span class="gn th">Published in <!-- -->Flutter</span></h2></a><div class="rn ab im"><div class="l jj"><span class="pw-follower-count bf b bg z du"><a class="af ag ah ai aj ak al am an ao ap aq ar jc" rel="noopener follow" href="/flutter/followers?source=post_page---post_publication_info--f33d497b7c11---------------------------------------">59K Followers</a></span></div><div class="bf b bg z du ab jn"><span class="jd l" aria-hidden="true"><span class="bf b bg z du">·</span></span><a class="af ag ah ai aj ak al am an ao ap aq ar jc" rel="noopener follow" href="/flutter/whats-new-in-flutter-3-29-f90c380c2317?source=post_page---post_publication_info--f33d497b7c11---------------------------------------">Last published <!-- -->4 days ago</a></div></div><div class="tv l"><p class="bf b bg z bk">Flutter is Google's UI framework for crafting high-quality native interfaces on iOS, Android, web, and desktop. Flutter works with existing code, is used by developers and organizations around the world, and is free and open source. Learn more at <a class="af ag ah ai aj ak al am an ao ap aq ar od go" href="https://flutter.dev" rel="noopener ugc nofollow">https://flutter.dev</a></p></div></div></div><div class="h k"><div class="ab"></div></div></div></div><div class="ab sj sk sl jl jk"><div class="sm sn so sp sq sr ss st su sv ab cp"><div class="h k"><a tabindex="0" rel="noopener follow" href="/@algebrandon?source=post_page---post_author_info--f33d497b7c11---------------------------------------"><div class="l fj"><img alt="Brandon DeRosier" class="l fd by io in cx" src="https://miro.medium.com/v2/resize:fill:96:96/0*Jzj4ct7CmcvOiR3g." width="48" height="48" loading="lazy"/><div class="fr by l io in fs n ay sx"></div></div></a></div><div class="j i d"><a tabindex="0" rel="noopener follow" href="/@algebrandon?source=post_page---post_author_info--f33d497b7c11---------------------------------------"><div class="l fj"><img alt="Brandon DeRosier" class="l fd by sy sz cx" src="https://miro.medium.com/v2/resize:fill:128:128/0*Jzj4ct7CmcvOiR3g." width="64" height="64" loading="lazy"/><div class="fr by l sy sz fs n ay sx"></div></div></a></div><div class="j i d ta jj"><div class="ab"><span><button class="bf b bg z tw rp tx ty tz ua ub ev ew uc ud ue fa fb fc fd bm fe ff">Follow</button></span></div></div></div><div class="ab co tb"><div class="tc td te tf tg l"><a class="af ag ah aj ak al am an ao ap aq ar as at ab q" rel="noopener follow" href="/@algebrandon?source=post_page---post_author_info--f33d497b7c11---------------------------------------"><h2 class="pw-author-name bf ti tj tk tl tm tn to nq tp tq nu tr ts ny tt tu bk"><span class="gn th">Written by <!-- -->Brandon DeRosier</span></h2></a><div class="rn ab im"><div class="l jj"><span class="pw-follower-count bf b bg z du"><a class="af ag ah ai aj ak al am an ao ap aq ar jc" rel="noopener follow" href="/@algebrandon/followers?source=post_page---post_author_info--f33d497b7c11---------------------------------------">125 Followers</a></span></div><div class="bf b bg z du ab jn"><span class="jd l" aria-hidden="true"><span class="bf b bg z du">·</span></span><a class="af ag ah ai aj ak al am an ao ap aq ar jc" rel="noopener follow" href="/@algebrandon/following?source=post_page---post_author_info--f33d497b7c11---------------------------------------">0 Following</a></div></div><div class="tv l"><p class="bf b bg z bk"><a class="af ag ah ai aj ak al am an ao ap aq ar od go" href="https://twitter.com/algebrandon" rel="noopener ugc nofollow">https://twitter.com/algebrandon</a> <a class="af ag ah ai aj ak al am an ao ap aq ar od go" href="https://github.com/bdero" rel="noopener ugc nofollow">https://github.com/bdero</a></p></div></div></div><div class="h k"><div class="ab"><span><button class="bf b bg z tw rp tx ty tz ua ub ev ew uc ud ue fa fb fc fd bm fe ff">Follow</button></span></div></div></div></div></div></div></div><div class="uf ug uh ui uj l"><div class="uk bh r sh"></div><div class="ab cb"><div class="ci bh fz ga gb gc"><div class="ab q cp"><h2 class="bf ti pb hu pd pe hx pg ph pj pk pl pn po pp pr ps bk">Responses (<!-- -->11<!-- -->)</h2><div class="ab ul"><div><div class="bm" aria-hidden="false"><a class="um un" href="https://policy.medium.com/medium-rules-30e5502c4eb4?source=post_page---post_responses--f33d497b7c11---------------------------------------" rel="noopener follow" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 25 25"><path fill-rule="evenodd" d="M11.987 5.036a.754.754 0 0 1 .914-.01c.972.721 1.767 1.218 2.6 1.543.828.322 1.719.485 2.887.505a.755.755 0 0 1 .741.757c-.018 3.623-.43 6.256-1.449 8.21-1.034 1.984-2.662 3.209-4.966 4.083a.75.75 0 0 1-.537-.003c-2.243-.874-3.858-2.095-4.897-4.074-1.024-1.951-1.457-4.583-1.476-8.216a.755.755 0 0 1 .741-.757c1.195-.02 2.1-.182 2.923-.503.827-.322 1.6-.815 2.519-1.535m.468.903c-.897.69-1.717 1.21-2.623 1.564-.898.35-1.856.527-3.026.565.037 3.45.469 5.817 1.36 7.515.884 1.684 2.25 2.762 4.284 3.571 2.092-.81 3.465-1.89 4.344-3.575.886-1.698 1.299-4.065 1.334-7.512-1.149-.039-2.091-.217-2.99-.567-.906-.353-1.745-.873-2.683-1.561m-.009 9.155a2.672 2.672 0 1 0 0-5.344 2.672 2.672 0 0 0 0 5.344m0 1a3.672 3.672 0 1 0 0-7.344 3.672 3.672 0 0 0 0 7.344m-1.813-3.777.525-.526.916.917 1.623-1.625.526.526-2.149 2.152z" clip-rule="evenodd"></path></svg></a></div></div></div></div><div class="uo up uq ur us ut uu l"></div><div class="oq l"><button class="bf b bg z bk rp uv uw ux mh me ub ev ew ex uy uz va fa vb vc vd ve vf fb fc fd bm fe ff">See all responses</button></div></div></div></div><div class="vg vh vi vj vk l bx"><div class="h k j"><div class="uk bh vl vm"></div><div class="ab cb"><div class="ci bh fz ga gb gc"><div class="vn ab lf jm"><div class="vo vp l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://help.medium.com/hc/en-us?source=post_page-----f33d497b7c11---------------------------------------" rel="noopener follow"><p class="bf b dv z du">Help</p></a></div><div class="vo vp l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://medium.statuspage.io/?source=post_page-----f33d497b7c11---------------------------------------" rel="noopener follow"><p class="bf b dv z du">Status</p></a></div><div class="vo vp l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" rel="noopener follow" href="/about?autoplay=1&source=post_page-----f33d497b7c11---------------------------------------"><p class="bf b dv z du">About</p></a></div><div class="vo vp l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" rel="noopener follow" href="/jobs-at-medium/work-at-medium-959d1a85284e?source=post_page-----f33d497b7c11---------------------------------------"><p class="bf b dv z du">Careers</p></a></div><div class="vo vp l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="mailto:pressinquiries@medium.com" rel="noopener follow"><p class="bf b dv z du">Press</p></a></div><div class="vo vp l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://blog.medium.com/?source=post_page-----f33d497b7c11---------------------------------------" rel="noopener follow"><p class="bf b dv z du">Blog</p></a></div><div class="vo vp l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://policy.medium.com/medium-privacy-policy-f03bf92035c9?source=post_page-----f33d497b7c11---------------------------------------" rel="noopener follow"><p class="bf b dv z du">Privacy</p></a></div><div class="vo vp l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://policy.medium.com/medium-terms-of-service-9db0094a1e0f?source=post_page-----f33d497b7c11---------------------------------------" rel="noopener follow"><p class="bf b dv z du">Terms</p></a></div><div class="vo vp l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://speechify.com/medium?source=post_page-----f33d497b7c11---------------------------------------" rel="noopener follow"><p class="bf b dv z du">Text to speech</p></a></div><div class="vo l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" rel="noopener follow" href="/business?source=post_page-----f33d497b7c11---------------------------------------"><p class="bf b dv z du">Teams</p></a></div></div></div></div></div></div></div></div></div></div><script>window.__BUILD_ID__="main-20250214-225023-b6ac233ec4"</script><script>window.__GRAPHQL_URI__ = "https://medium.com/_/graphql"</script><script>window.__PRELOADED_STATE__ = {"algolia":{"queries":{}},"cache":{"experimentGroupSet":true,"reason":"","group":"enabled","tags":["group-edgeCachePosts","post-f33d497b7c11","user-9a1282e0b0ee","collection-4da7dfd21a33"],"serverVariantState":"44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","middlewareEnabled":true,"cacheStatus":"DYNAMIC","shouldUseCache":true,"vary":[],"pubFeaturingPostPageLabelEnabled":false},"client":{"hydrated":false,"isUs":false,"isNativeMedium":false,"isSafariMobile":false,"isSafari":false,"isFirefox":false,"routingEntity":{"type":"DEFAULT","explicit":false},"viewerIsBot":false},"debug":{"requestId":"4de7b246-397c-9fbb-8d22-d40cbba62121","requestTag":"","hybridDevServices":[],"originalSpanCarrier":{"traceparent":"00-a87465a0699428ff0b8f49c2e79da308-9bebb62b286a0860-01"}},"multiVote":{"clapsPerPost":{}},"navigation":{"branch":{"show":null,"hasRendered":null,"blockedByCTA":false},"hideGoogleOneTap":false,"hasRenderedAlternateUserBanner":null,"currentLocation":"https:\u002F\u002Fmedium.com\u002Fflutter\u002Fgetting-started-with-flutter-gpu-f33d497b7c11","host":"medium.com","hostname":"medium.com","referrer":"","hasSetReferrer":false,"susiModal":{"step":null,"operation":"register"},"postRead":false,"partnerProgram":{"selectedCountryCode":null},"queryString":"","currentHash":""},"config":{"nodeEnv":"production","version":"main-20250214-225023-b6ac233ec4","target":"production","productName":"Medium","publicUrl":"https:\u002F\u002Fcdn-client.medium.com\u002Flite","authDomain":"medium.com","authGoogleClientId":"216296035834-k1k6qe060s2tp2a2jam4ljdcms00sttg.apps.googleusercontent.com","favicon":"production","glyphUrl":"https:\u002F\u002Fglyph.medium.com","branchKey":"key_live_ofxXr2qTrrU9NqURK8ZwEhknBxiI6KBm","algolia":{"appId":"MQ57UUUQZ2","apiKeySearch":"394474ced050e3911ae2249ecc774921","indexPrefix":"medium_","host":"-dsn.algolia.net"},"recaptchaKey":"6Lfc37IUAAAAAKGGtC6rLS13R1Hrw_BqADfS1LRk","recaptcha3Key":"6Lf8R9wUAAAAABMI_85Wb8melS7Zj6ziuf99Yot5","recaptchaEnterpriseKeyId":"6Le-uGgpAAAAAPprRaokM8AKthQ9KNGdoxaGUvVp","datadog":{"applicationId":"6702d87d-a7e0-42fe-bbcb-95b469547ea0","clientToken":"pub853ea8d17ad6821d9f8f11861d23dfed","rumToken":"pubf9cc52896502b9413b68ba36fc0c7162","context":{"deployment":{"target":"production","tag":"main-20250214-225023-b6ac233ec4","commit":"b6ac233ec4e33c48e304b373b7df8c338a410ef2"}},"datacenter":"us"},"googleAnalyticsCode":"G-7JY7T788PK","googlePay":{"apiVersion":"2","apiVersionMinor":"0","merchantId":"BCR2DN6TV7EMTGBM","merchantName":"Medium","instanceMerchantId":"13685562959212738550"},"applePay":{"version":3},"signInWallCustomDomainCollectionIds":["3a8144eabfe3","336d898217ee","61061eb0c96b","138adf9c44c","819cc2aaeee0"],"mediumMastodonDomainName":"me.dm","mediumOwnedAndOperatedCollectionIds":["8a9336e5bb4","b7e45b22fec3","193b68bd4fba","8d6b8a439e32","54c98c43354d","3f6ecf56618","d944778ce714","92d2092dc598","ae2a65f35510","1285ba81cada","544c7006046e","fc8964313712","40187e704f1c","88d9857e584e","7b6769f2748b","bcc38c8f6edf","cef6983b292","cb8577c9149e","444d13b52878","713d7dbc99b0","ef8e90590e66","191186aaafa0","55760f21cdc5","9dc80918cc93","bdc4052bbdba","8ccfed20cbb2"],"tierOneDomains":["medium.com","thebolditalic.com","arcdigital.media","towardsdatascience.com","uxdesign.cc","codeburst.io","psiloveyou.xyz","writingcooperative.com","entrepreneurshandbook.co","prototypr.io","betterhumans.coach.me","theascent.pub"],"topicsToFollow":["d61cf867d93f","8a146bc21b28","1eca0103fff3","4d562ee63426","aef1078a3ef5","e15e46793f8d","6158eb913466","55f1c20aba7a","3d18b94f6858","4861fee224fd","63c6f1f93ee","1d98b3a9a871","decb52b64abf","ae5d4995e225","830cded25262"],"topicToTagMappings":{"accessibility":"accessibility","addiction":"addiction","android-development":"android-development","art":"art","artificial-intelligence":"artificial-intelligence","astrology":"astrology","basic-income":"basic-income","beauty":"beauty","biotech":"biotech","blockchain":"blockchain","books":"books","business":"business","cannabis":"cannabis","cities":"cities","climate-change":"climate-change","comics":"comics","coronavirus":"coronavirus","creativity":"creativity","cryptocurrency":"cryptocurrency","culture":"culture","cybersecurity":"cybersecurity","data-science":"data-science","design":"design","digital-life":"digital-life","disability":"disability","economy":"economy","education":"education","equality":"equality","family":"family","feminism":"feminism","fiction":"fiction","film":"film","fitness":"fitness","food":"food","freelancing":"freelancing","future":"future","gadgets":"gadgets","gaming":"gaming","gun-control":"gun-control","health":"health","history":"history","humor":"humor","immigration":"immigration","ios-development":"ios-development","javascript":"javascript","justice":"justice","language":"language","leadership":"leadership","lgbtqia":"lgbtqia","lifestyle":"lifestyle","machine-learning":"machine-learning","makers":"makers","marketing":"marketing","math":"math","media":"media","mental-health":"mental-health","mindfulness":"mindfulness","money":"money","music":"music","neuroscience":"neuroscience","nonfiction":"nonfiction","outdoors":"outdoors","parenting":"parenting","pets":"pets","philosophy":"philosophy","photography":"photography","podcasts":"podcast","poetry":"poetry","politics":"politics","privacy":"privacy","product-management":"product-management","productivity":"productivity","programming":"programming","psychedelics":"psychedelics","psychology":"psychology","race":"race","relationships":"relationships","religion":"religion","remote-work":"remote-work","san-francisco":"san-francisco","science":"science","self":"self","self-driving-cars":"self-driving-cars","sexuality":"sexuality","social-media":"social-media","society":"society","software-engineering":"software-engineering","space":"space","spirituality":"spirituality","sports":"sports","startups":"startup","style":"style","technology":"technology","transportation":"transportation","travel":"travel","true-crime":"true-crime","tv":"tv","ux":"ux","venture-capital":"venture-capital","visual-design":"visual-design","work":"work","world":"world","writing":"writing"},"defaultImages":{"avatar":{"imageId":"1*dmbNkD5D-u45r44go_cf0g.png","height":150,"width":150},"orgLogo":{"imageId":"7*V1_7XP4snlmqrc_0Njontw.png","height":110,"width":500},"postLogo":{"imageId":"bd978bb536350a710e8efb012513429cabdc4c28700604261aeda246d0f980b7","height":810,"width":1440},"postPreviewImage":{"imageId":"1*hn4v1tCaJy7cWMyb0bpNpQ.png","height":386,"width":579}},"collectionStructuredData":{"8d6b8a439e32":{"name":"Elemental","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F980\u002F1*9ygdqoKprhwuTVKUM0DLPA@2x.png","width":980,"height":159}}},"3f6ecf56618":{"name":"Forge","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F596\u002F1*uULpIlImcO5TDuBZ6lm7Lg@2x.png","width":596,"height":183}}},"ae2a65f35510":{"name":"GEN","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fmiro.medium.com\u002Fmax\u002F264\u002F1*RdVZMdvfV3YiZTw6mX7yWA.png","width":264,"height":140}}},"88d9857e584e":{"name":"LEVEL","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fmiro.medium.com\u002Fmax\u002F540\u002F1*JqYMhNX6KNNb2UlqGqO2WQ.png","width":540,"height":108}}},"7b6769f2748b":{"name":"Marker","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F383\u002F1*haCUs0wF6TgOOvfoY-jEoQ@2x.png","width":383,"height":92}}},"444d13b52878":{"name":"OneZero","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fmiro.medium.com\u002Fmax\u002F540\u002F1*cw32fIqCbRWzwJaoQw6BUg.png","width":540,"height":123}}},"8ccfed20cbb2":{"name":"Zora","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fmiro.medium.com\u002Fmax\u002F540\u002F1*tZUQqRcCCZDXjjiZ4bDvgQ.png","width":540,"height":106}}}},"embeddedPostIds":{"coronavirus":"cd3010f9d81f"},"sharedCdcMessaging":{"COVID_APPLICABLE_TAG_SLUGS":[],"COVID_APPLICABLE_TOPIC_NAMES":[],"COVID_APPLICABLE_TOPIC_NAMES_FOR_TOPIC_PAGE":[],"COVID_MESSAGES":{"tierA":{"text":"For more information on the novel coronavirus and Covid-19, visit cdc.gov.","markups":[{"start":66,"end":73,"href":"https:\u002F\u002Fwww.cdc.gov\u002Fcoronavirus\u002F2019-nCoV"}]},"tierB":{"text":"Anyone can publish on Medium per our Policies, but we don’t fact-check every story. For more info about the coronavirus, see cdc.gov.","markups":[{"start":37,"end":45,"href":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Fcategories\u002F201931128-Policies-Safety"},{"start":125,"end":132,"href":"https:\u002F\u002Fwww.cdc.gov\u002Fcoronavirus\u002F2019-nCoV"}]},"paywall":{"text":"This article has been made free for everyone, thanks to Medium Members. For more information on the novel coronavirus and Covid-19, visit cdc.gov.","markups":[{"start":56,"end":70,"href":"https:\u002F\u002Fmedium.com\u002Fmembership"},{"start":138,"end":145,"href":"https:\u002F\u002Fwww.cdc.gov\u002Fcoronavirus\u002F2019-nCoV"}]},"unbound":{"text":"This article is free for everyone, thanks to Medium Members. For more information on the novel coronavirus and Covid-19, visit cdc.gov.","markups":[{"start":45,"end":59,"href":"https:\u002F\u002Fmedium.com\u002Fmembership"},{"start":127,"end":134,"href":"https:\u002F\u002Fwww.cdc.gov\u002Fcoronavirus\u002F2019-nCoV"}]}},"COVID_BANNER_POST_ID_OVERRIDE_WHITELIST":["3b31a67bff4a"]},"sharedVoteMessaging":{"TAGS":["politics","election-2020","government","us-politics","election","2020-presidential-race","trump","donald-trump","democrats","republicans","congress","republican-party","democratic-party","biden","joe-biden","maga"],"TOPICS":["politics","election"],"MESSAGE":{"text":"Find out more about the U.S. election results here.","markups":[{"start":46,"end":50,"href":"https:\u002F\u002Fcookpolitical.com\u002F2020-national-popular-vote-tracker"}]},"EXCLUDE_POSTS":["397ef29e3ca5"]},"embedPostRules":[],"recircOptions":{"v1":{"limit":3},"v2":{"limit":8}},"braintreeClientKey":"production_zjkj96jm_m56f8fqpf7ngnrd4","braintree":{"enabled":true,"merchantId":"m56f8fqpf7ngnrd4","merchantAccountId":{"usd":"AMediumCorporation_instant","eur":"amediumcorporation_EUR","cad":"amediumcorporation_CAD"},"publicKey":"ds2nn34bg2z7j5gd","braintreeEnvironment":"production","dashboardUrl":"https:\u002F\u002Fwww.braintreegateway.com\u002Fmerchants","gracePeriodDurationInDays":14,"mediumMembershipPlanId":{"monthly":"ce105f8c57a3","monthlyV2":"e8a5e126-792b-4ee6-8fba-d574c1b02fc5","monthlyWithTrial":"d5ee3dbe3db8","monthlyPremium":"fa741a9b47a2","yearly":"a40ad4a43185","yearlyV2":"3815d7d6-b8ca-4224-9b8c-182f9047866e","yearlyStaff":"d74fb811198a","yearlyWithTrial":"b3bc7350e5c7","yearlyPremium":"e21bd2c12166","monthlyOneYearFree":"e6c0637a-2bad-4171-ab4f-3c268633d83c","monthly25PercentOffFirstYear":"235ecc62-0cdb-49ae-9378-726cd21c504b","monthly20PercentOffFirstYear":"ba518864-9c13-4a99-91ca-411bf0cac756","monthly15PercentOffFirstYear":"594c029b-9f89-43d5-88f8-8173af4e070e","monthly10PercentOffFirstYear":"c6c7bc9a-40f2-4b51-8126-e28511d5bdb0","monthlyForStudents":"629ebe51-da7d-41fd-8293-34cd2f2030a8","yearlyOneYearFree":"78ba7be9-0d9f-4ece-aa3e-b54b826f2bf1","yearly25PercentOffFirstYear":"2dbb010d-bb8f-4eeb-ad5c-a08509f42d34","yearly20PercentOffFirstYear":"47565488-435b-47f8-bf93-40d5fbe0ebc8","yearly15PercentOffFirstYear":"8259809b-0881-47d9-acf7-6c001c7f720f","yearly10PercentOffFirstYear":"9dd694fb-96e1-472c-8d9e-3c868d5c1506","yearlyForStudents":"e29345ef-ab1c-4234-95c5-70e50fe6bc23","monthlyCad":"p52orjkaceei","yearlyCad":"h4q9g2up9ktt"},"braintreeDiscountId":{"oneMonthFree":"MONTHS_FREE_01","threeMonthsFree":"MONTHS_FREE_03","sixMonthsFree":"MONTHS_FREE_06","fiftyPercentOffOneYear":"FIFTY_PERCENT_OFF_ONE_YEAR"},"3DSecureVersion":"2","defaultCurrency":"usd","providerPlanIdCurrency":{"4ycw":"usd","rz3b":"usd","3kqm":"usd","jzw6":"usd","c2q2":"usd","nnsw":"usd","q8qw":"usd","d9y6":"usd","fx7w":"cad","nwf2":"cad"}},"paypalClientId":"AXj1G4fotC2GE8KzWX9mSxCH1wmPE3nJglf4Z2ig_amnhvlMVX87otaq58niAg9iuLktVNF_1WCMnN7v","paypal":{"host":"https:\u002F\u002Fapi.paypal.com:443","clientMode":"production","serverMode":"live","webhookId":"4G466076A0294510S","monthlyPlan":{"planId":"P-9WR0658853113943TMU5FDQA","name":"Medium Membership (Monthly) with setup fee","description":"Unlimited access to the best and brightest stories on Medium. Membership billed monthly."},"yearlyPlan":{"planId":"P-7N8963881P8875835MU5JOPQ","name":"Medium Membership (Annual) with setup fee","description":"Unlimited access to the best and brightest stories on Medium. Membership billed annually."},"oneYearGift":{"name":"Medium Membership (1 Year, Digital Gift Code)","description":"Unlimited access to the best and brightest stories on Medium. Gift codes can be redeemed at medium.com\u002Fredeem.","price":"50.00","currency":"USD","sku":"membership-gift-1-yr"},"oldMonthlyPlan":{"planId":"P-96U02458LM656772MJZUVH2Y","name":"Medium Membership (Monthly)","description":"Unlimited access to the best and brightest stories on Medium. Membership billed monthly."},"oldYearlyPlan":{"planId":"P-59P80963JF186412JJZU3SMI","name":"Medium Membership (Annual)","description":"Unlimited access to the best and brightest stories on Medium. Membership billed annually."},"monthlyPlanWithTrial":{"planId":"P-66C21969LR178604GJPVKUKY","name":"Medium Membership (Monthly) with setup fee","description":"Unlimited access to the best and brightest stories on Medium. Membership billed monthly."},"yearlyPlanWithTrial":{"planId":"P-6XW32684EX226940VKCT2MFA","name":"Medium Membership (Annual) with setup fee","description":"Unlimited access to the best and brightest stories on Medium. Membership billed annually."},"oldMonthlyPlanNoSetupFee":{"planId":"P-4N046520HR188054PCJC7LJI","name":"Medium Membership (Monthly)","description":"Unlimited access to the best and brightest stories on Medium. Membership billed monthly."},"oldYearlyPlanNoSetupFee":{"planId":"P-7A4913502Y5181304CJEJMXQ","name":"Medium Membership (Annual)","description":"Unlimited access to the best and brightest stories on Medium. Membership billed annually."},"sdkUrl":"https:\u002F\u002Fwww.paypal.com\u002Fsdk\u002Fjs"},"stripePublishableKey":"pk_live_7FReX44VnNIInZwrIIx6ghjl","log":{"json":true,"level":"info"},"imageUploadMaxSizeMb":25,"staffPicks":{"title":"Staff Picks","catalogId":"c7bc6e1ee00f"}},"session":{"xsrf":""}}</script><script>window.__APOLLO_STATE__ = {"ROOT_QUERY":{"__typename":"Query","viewer":null,"collectionByDomainOrSlug({\"domainOrSlug\":\"flutter\"})":{"__ref":"Collection:4da7dfd21a33"},"postResult({\"id\":\"f33d497b7c11\"})":{"__ref":"Post:f33d497b7c11"}},"ImageMetadata:":{"__typename":"ImageMetadata","id":""},"Collection:4da7dfd21a33":{"__typename":"Collection","id":"4da7dfd21a33","favicon":{"__ref":"ImageMetadata:"},"customStyleSheet":null,"colorPalette":{"__typename":"ColorPalette","highlightSpectrum":{"__typename":"ColorSpectrum","backgroundColor":"#FFFFFFFF","colorPoints":[{"__typename":"ColorPoint","color":"#FFE8F5FF","point":0},{"__typename":"ColorPoint","color":"#FFE3F3FF","point":0.1},{"__typename":"ColorPoint","color":"#FFDEF2FF","point":0.2},{"__typename":"ColorPoint","color":"#FFDAF0FF","point":0.3},{"__typename":"ColorPoint","color":"#FFD5EFFF","point":0.4},{"__typename":"ColorPoint","color":"#FFD0EDFF","point":0.5},{"__typename":"ColorPoint","color":"#FFCBECFF","point":0.6},{"__typename":"ColorPoint","color":"#FFC6EBFF","point":0.7},{"__typename":"ColorPoint","color":"#FFC1E9FF","point":0.8},{"__typename":"ColorPoint","color":"#FFBCE8FF","point":0.9},{"__typename":"ColorPoint","color":"#FFB6E6FF","point":1}]},"defaultBackgroundSpectrum":{"__typename":"ColorSpectrum","backgroundColor":"#FFFFFFFF","colorPoints":[{"__typename":"ColorPoint","color":"#FF498BD1","point":0},{"__typename":"ColorPoint","color":"#FF4781C0","point":0.1},{"__typename":"ColorPoint","color":"#FF4477AF","point":0.2},{"__typename":"ColorPoint","color":"#FF416D9E","point":0.3},{"__typename":"ColorPoint","color":"#FF3D638D","point":0.4},{"__typename":"ColorPoint","color":"#FF38587C","point":0.5},{"__typename":"ColorPoint","color":"#FF324D6B","point":0.6},{"__typename":"ColorPoint","color":"#FF2C415A","point":0.7},{"__typename":"ColorPoint","color":"#FF253548","point":0.8},{"__typename":"ColorPoint","color":"#FF1C2937","point":0.9},{"__typename":"ColorPoint","color":"#FF121B25","point":1}]},"tintBackgroundSpectrum":{"__typename":"ColorSpectrum","backgroundColor":"#FF01579B","colorPoints":[{"__typename":"ColorPoint","color":"#FF01579B","point":0},{"__typename":"ColorPoint","color":"#FF306CA9","point":0.1},{"__typename":"ColorPoint","color":"#FF4D7FB7","point":0.2},{"__typename":"ColorPoint","color":"#FF6591C4","point":0.3},{"__typename":"ColorPoint","color":"#FF7CA3D0","point":0.4},{"__typename":"ColorPoint","color":"#FF92B4DC","point":0.5},{"__typename":"ColorPoint","color":"#FFA7C4E7","point":0.6},{"__typename":"ColorPoint","color":"#FFBCD3F1","point":0.7},{"__typename":"ColorPoint","color":"#FFD0E3FC","point":0.8},{"__typename":"ColorPoint","color":"#FFE3F2FF","point":0.9},{"__typename":"ColorPoint","color":"#FFF7FFFF","point":1}]}},"domain":null,"slug":"flutter","googleAnalyticsId":null,"name":"Flutter","avatar":{"__ref":"ImageMetadata:1*5-aoK8IBmXve5whBQM90GA.png"},"description":"Flutter is Google's UI framework for crafting high-quality native interfaces on iOS, Android, web, and desktop. Flutter works with existing code, is used by developers and organizations around the world, and is free and open source. Learn more at https:\u002F\u002Fflutter.dev","subscriberCount":59249,"latestPostsConnection({\"paging\":{\"limit\":1}})":{"__typename":"PostConnection","posts":[{"__ref":"Post:f90c380c2317"}]},"isAuroraVisible":false,"tintColor":"#FF01579B","newsletterV3":null,"viewerEdge":{"__ref":"CollectionViewerEdge:collectionId:4da7dfd21a33-viewerId:lo_b9322efa791c"},"twitterUsername":"flutterdev","facebookPageId":null,"logo":{"__ref":"ImageMetadata:1*KvnfbD1F5CzEsU9wSmRZyA.png"}},"ImageMetadata:1*5-aoK8IBmXve5whBQM90GA.png":{"__typename":"ImageMetadata","id":"1*5-aoK8IBmXve5whBQM90GA.png"},"User:7cb43f46877f":{"__typename":"User","id":"7cb43f46877f","customDomainState":null,"hasSubdomain":false,"username":"kevinchisholm"},"Post:f90c380c2317":{"__typename":"Post","id":"f90c380c2317","firstPublishedAt":1739387397531,"creator":{"__ref":"User:7cb43f46877f"},"collection":{"__ref":"Collection:4da7dfd21a33"},"isSeries":false,"mediumUrl":"https:\u002F\u002Fmedium.com\u002Fflutter\u002Fwhats-new-in-flutter-3-29-f90c380c2317","sequence":null,"uniqueSlug":"whats-new-in-flutter-3-29-f90c380c2317"},"LinkedAccounts:9a1282e0b0ee":{"__typename":"LinkedAccounts","mastodon":null,"id":"9a1282e0b0ee"},"User:9a1282e0b0ee":{"__typename":"User","id":"9a1282e0b0ee","linkedAccounts":{"__ref":"LinkedAccounts:9a1282e0b0ee"},"isSuspended":false,"name":"Brandon DeRosier","imageId":"0*Jzj4ct7CmcvOiR3g.","customDomainState":null,"hasSubdomain":false,"username":"algebrandon","verifications":{"__typename":"VerifiedInfo","isBookAuthor":false},"socialStats":{"__typename":"SocialStats","followerCount":125,"followingCount":0,"collectionFollowingCount":0},"bio":"https:\u002F\u002Ftwitter.com\u002Falgebrandon https:\u002F\u002Fgithub.com\u002Fbdero","membership":null,"allowNotes":true,"viewerEdge":{"__ref":"UserViewerEdge:userId:9a1282e0b0ee-viewerId:lo_b9322efa791c"},"twitterScreenName":""},"Paragraph:60a0970d9135_0":{"__typename":"Paragraph","id":"60a0970d9135_0","name":"1dd2","type":"H3","href":null,"layout":null,"metadata":null,"text":"Getting started with Flutter GPU","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_1":{"__typename":"Paragraph","id":"60a0970d9135_1","name":"2308","type":"H4","href":null,"layout":null,"metadata":null,"text":"Build custom renderers and render 3D scenes in Flutter.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_2":{"__typename":"Paragraph","id":"60a0970d9135_2","name":"a6f6","type":"P","href":null,"layout":null,"metadata":null,"text":"The Flutter 3.24 release introduces a new low-level graphics API called Flutter GPU. There is also a 3D rendering library powered by Flutter GPU called Flutter Scene (package: flutter_scene). Both Flutter GPU and Flutter Scene are currently in preview, only available on Flutter’s main channel (due to reliance on experimental features), require Impeller to be enabled, and might occasionally introduce breaking changes.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":176,"end":189,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":72,"end":83,"href":"https:\u002F\u002Fgithub.com\u002Fflutter\u002Fengine\u002Fblob\u002Fmain\u002Fdocs\u002Fimpeller\u002FFlutter-GPU.md","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":152,"end":165,"href":"https:\u002F\u002Fpub.dev\u002Fpackages\u002Fflutter_scene","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":281,"end":293,"href":"https:\u002F\u002Fdocs.flutter.dev\u002Frelease\u002Fupgrade#other-channels","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":346,"end":368,"href":"https:\u002F\u002Fdocs.flutter.dev\u002Fperf\u002Fimpeller#availability","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_3":{"__typename":"Paragraph","id":"60a0970d9135_3","name":"952a","type":"P","href":null,"layout":null,"metadata":null,"text":"This article contains two “getting started” guides for these packages:","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_4":{"__typename":"Paragraph","id":"60a0970d9135_4","name":"4ce4","type":"OLI","href":null,"layout":null,"metadata":null,"text":"🔺 Advanced: Getting started with Flutter GPU\nIf you’re an experienced graphics programmer or you’re interested in low level graphics and want to build renderers from scratch in Flutter, then this guide will get you set up to start tinkering with Flutter GPU. You’ll draw your first triangle from scratch… in Flutter!","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":13,"end":45,"href":"#d558","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"STRONG","start":3,"end":12,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_5":{"__typename":"Paragraph","id":"60a0970d9135_5","name":"fbac","type":"OLI","href":null,"layout":null,"metadata":null,"text":"💚 Intermediate: 3D rendering with Flutter Scene\nIf you’re a Flutter developer that wants to add 3D functionality to your apps, or you want to create 3D games using Dart and Flutter, then this is a guide for you! You’ll set up a project that imports and renders 3D assets in Flutter.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":17,"end":48,"href":"#6b35","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"STRONG","start":3,"end":16,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"STRONG","start":48,"end":49,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_6":{"__typename":"Paragraph","id":"60a0970d9135_6","name":"d558","type":"H3","href":null,"layout":null,"metadata":null,"text":"Getting started with Flutter GPU","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_7":{"__typename":"Paragraph","id":"60a0970d9135_7","name":"2164","type":"P","href":null,"layout":null,"metadata":null,"text":"⚠️ Warning! ⚠️ Flutter GPU is ultimately a low-level API. It’s overwhelmingly likely that the vast majority of Flutter devs who will benefit from Flutter GPU’s existence will do so by consuming higher level rendering libraries published on pub.dev, such as the Flutter Scene rendering package. If you’re not interested in the Flutter GPU API itself and you’re just interested in 3D rendering, skip ahead to 3D rendering with Flutter Scene.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":407,"end":438,"href":"#6b35","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:0*hAqIOVkaI1IWnOHE":{"__typename":"ImageMetadata","id":"0*hAqIOVkaI1IWnOHE","originalHeight":673,"originalWidth":900,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:60a0970d9135_8":{"__typename":"Paragraph","id":"60a0970d9135_8","name":"9f4d","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:0*hAqIOVkaI1IWnOHE"},"text":"Ooh shiny. This is a ray-marched signed distance field. You could render this using Flutter GPU, but it’s perfectly possible to do so with a custom fragment shader as well.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":141,"end":163,"href":"https:\u002F\u002Fdocs.flutter.dev\u002Fui\u002Fdesign\u002Fgraphics\u002Ffragment-shaders","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_9":{"__typename":"Paragraph","id":"60a0970d9135_9","name":"0097","type":"H3","href":null,"layout":null,"metadata":null,"text":"Getting started with Flutter GPU","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_10":{"__typename":"Paragraph","id":"60a0970d9135_10","name":"d305","type":"P","href":null,"layout":null,"metadata":null,"text":"Flutter GPU is Flutter’s built-in low-level graphics API. It allows you to build and integrate custom renderers in Flutter by writing Dart code and GLSL shaders. No native platform code required.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_11":{"__typename":"Paragraph","id":"60a0970d9135_11","name":"8cce","type":"P","href":null,"layout":null,"metadata":null,"text":"Currently, Flutter GPU is in early preview and offers a basic rasterization API, but more functionality will continue to be added and refined, as the API approaches stable.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_12":{"__typename":"Paragraph","id":"60a0970d9135_12","name":"36c8","type":"P","href":null,"layout":null,"metadata":null,"text":"Flutter GPU also requires Impeller to be enabled. This means it can only be used when targeting platforms that are supported by Impeller. At the time of writing, Impeller supports:","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":26,"end":48,"href":"https:\u002F\u002Fdocs.flutter.dev\u002Fperf\u002Fimpeller#availability","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_13":{"__typename":"Paragraph","id":"60a0970d9135_13","name":"4586","type":"ULI","href":null,"layout":null,"metadata":null,"text":"iOS (on by default)","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_14":{"__typename":"Paragraph","id":"60a0970d9135_14","name":"9965","type":"ULI","href":null,"layout":null,"metadata":null,"text":"macOS (opt-in preview)","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_15":{"__typename":"Paragraph","id":"60a0970d9135_15","name":"9ac0","type":"ULI","href":null,"layout":null,"metadata":null,"text":"Android (opt-in preview)","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_16":{"__typename":"Paragraph","id":"60a0970d9135_16","name":"53ab","type":"P","href":null,"layout":null,"metadata":null,"text":"Our aim with Flutter GPU is to eventually support all of Flutter’s platform targets. The ultimate goal is to foster an ecosystem of cross-platform rendering solutions in Flutter that are easy to maintain for package authors and easy to install for users.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_17":{"__typename":"Paragraph","id":"60a0970d9135_17","name":"b74e","type":"P","href":null,"layout":null,"metadata":null,"text":"3D rendering is just one possible use case. Flutter GPU can also be used to build specialized 2D renderers, or to do something more unorthodox, like render 3D slices of a 4D space, or project non-euclidean spaces.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_18":{"__typename":"Paragraph","id":"60a0970d9135_18","name":"cadf","type":"P","href":null,"layout":null,"metadata":null,"text":"One example of a great use case for a custom 2D renderer powered by Flutter GPU would be 2D character animation formats that rely on skeletal mesh deformation. Spine 2D is a good example of this. Such skeletal mesh solutions usually have animation clips that manipulate translation, rotation, and scale properties of bones in a hierarchy, and each vertex has a few associated “bone weights” that determine what bones should influence the vertex and by how much.’","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_19":{"__typename":"Paragraph","id":"60a0970d9135_19","name":"edc4","type":"P","href":null,"layout":null,"metadata":null,"text":"With a Canvas solution like drawVertices, bone weight transforms would need to be applied for each vertex on the CPU. With Flutter GPU, the bone transforms could be fed to a vertex shader in the form of a uniform array or even a texture sampler, allowing the final position for each vertex to be calculated in parallel on the GPU based on the state of the skeleton and the per-vertex bone weights.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":28,"end":40,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_20":{"__typename":"Paragraph","id":"60a0970d9135_20","name":"a4b5","type":"P","href":null,"layout":null,"metadata":null,"text":"With that out of the way, let’s get started with Flutter GPU by way of a gentle introduction: Drawing your first triangle!","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:0*JEI3fLDGcRHWKruT":{"__typename":"ImageMetadata","id":"0*JEI3fLDGcRHWKruT","originalHeight":1298,"originalWidth":1600,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:60a0970d9135_21":{"__typename":"Paragraph","id":"60a0970d9135_21","name":"0a49","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:0*JEI3fLDGcRHWKruT"},"text":"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_22":{"__typename":"Paragraph","id":"60a0970d9135_22","name":"0496","type":"H3","href":null,"layout":null,"metadata":null,"text":"Add Flutter GPU to your project","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_23":{"__typename":"Paragraph","id":"60a0970d9135_23","name":"f487","type":"P","href":null,"layout":null,"metadata":null,"text":"First, note that Flutter GPU is currently in an early preview state and might be prone to API breakages. Quite a lot is already possible with the current API, but experienced graphics engineers might notice some missing common functionality. Much is planned for Flutter GPU over the coming months.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_24":{"__typename":"Paragraph","id":"60a0970d9135_24","name":"1ed5","type":"P","href":null,"layout":null,"metadata":null,"text":"For these reasons, it’s strongly recommended that, for now, you operate against the tip of the main channel when developing packages against Flutter GPU. If you happen to run into any unexpected behavior, bugs, or have feature requests, please file issues using the standard Flutter issue templates on GitHub. All tracked issues related to Flutter GPU are given the flutter-gpu label.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":95,"end":107,"href":"https:\u002F\u002Fdocs.flutter.dev\u002Frelease\u002Fupgrade#other-channels","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":275,"end":298,"href":"https:\u002F\u002Fgithub.com\u002Fflutter\u002Fflutter\u002Fissues\u002Fnew\u002Fchoose","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":366,"end":383,"href":"https:\u002F\u002Fgithub.com\u002Fflutter\u002Fflutter\u002Flabels\u002Fflutter-gpu","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_25":{"__typename":"Paragraph","id":"60a0970d9135_25","name":"f206","type":"P","href":null,"layout":null,"metadata":null,"text":"So before experimenting with Flutter GPU, switch Flutter over to the main channel by running the following commands.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_26":{"__typename":"Paragraph","id":"60a0970d9135_26","name":"bd7c","type":"PRE","href":null,"layout":null,"metadata":null,"text":"flutter channel main\nflutter upgrade","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"bash"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_27":{"__typename":"Paragraph","id":"60a0970d9135_27","name":"4538","type":"P","href":null,"layout":null,"metadata":null,"text":"Now create a fresh Flutter project.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_28":{"__typename":"Paragraph","id":"60a0970d9135_28","name":"3bec","type":"PRE","href":null,"layout":null,"metadata":null,"text":"flutter create my_cool_renderer\ncd my_cool_renderer","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"AUTO","lang":"bash"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_29":{"__typename":"Paragraph","id":"60a0970d9135_29","name":"9e34","type":"P","href":null,"layout":null,"metadata":null,"text":"Next, add the flutter_gpu SDK package to your pubspec.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_30":{"__typename":"Paragraph","id":"60a0970d9135_30","name":"d559","type":"PRE","href":null,"layout":null,"metadata":null,"text":"flutter pub add flutter_gpu --sdk=flutter","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"bash"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_31":{"__typename":"Paragraph","id":"60a0970d9135_31","name":"502b","type":"H3","href":null,"layout":null,"metadata":null,"text":"Build and import shader bundles.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_32":{"__typename":"Paragraph","id":"60a0970d9135_32","name":"3492","type":"P","href":null,"layout":null,"metadata":null,"text":"In order to render anything with Flutter GPU, you’ll need to author some GLSL shaders. Flutter GPU’s shaders have different semantics than those consumed by Flutter’s fragment shader feature, particularly when it comes to uniform bindings. You’ll also define a vertex shader to go alongside the fragment shader.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":167,"end":182,"href":"https:\u002F\u002Fdocs.flutter.dev\u002Fui\u002Fdesign\u002Fgraphics\u002Ffragment-shaders","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_33":{"__typename":"Paragraph","id":"60a0970d9135_33","name":"0002","type":"P","href":null,"layout":null,"metadata":null,"text":"Start with defining the simplest possible shaders. You can place shaders anywhere in your project, but for this example, create a shaders directory and populate it with two shaders: simple.vert and simple.frag .","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":130,"end":137,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":182,"end":193,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":198,"end":209,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_34":{"__typename":"Paragraph","id":"60a0970d9135_34","name":"09d6","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F Copy into: shaders\u002Fsimple.vert\n\nin vec2 position;\n\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"c"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_35":{"__typename":"Paragraph","id":"60a0970d9135_35","name":"7049","type":"P","href":null,"layout":null,"metadata":null,"text":"When drawing the triangle, you’ll have a list of data that defines each vertex. In this case, it merely lists 2D positions. For each of these vertices, a simple vertex shader assigns these 2D positions to the clip space output intrinsic gl_Position.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":237,"end":248,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_36":{"__typename":"Paragraph","id":"60a0970d9135_36","name":"c11d","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F Copy into: shaders\u002Fsimple.frag\n\nout vec4 frag_color;\n\nvoid main() {\n frag_color = vec4(0, 1, 0, 1);\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"c"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_37":{"__typename":"Paragraph","id":"60a0970d9135_37","name":"23c7","type":"P","href":null,"layout":null,"metadata":null,"text":"The fragment shader is even simpler; it outputs an RGBA color with a range of (0, 0, 0, 0) to (1, 1, 1, 1). So everything will be shaded as green.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":78,"end":90,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":94,"end":106,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_38":{"__typename":"Paragraph","id":"60a0970d9135_38","name":"cbf9","type":"P","href":null,"layout":null,"metadata":null,"text":"Okay, so now that you have your shaders, compile them using Flutter's ahead of time (AOT) shader compiler . To set up automated builds for shader bundles, we recommend using the flutter_gpu_shaders package.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":178,"end":197,"href":"https:\u002F\u002Fpub.dev\u002Fpackages\u002Fflutter_gpu_shaders","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_39":{"__typename":"Paragraph","id":"60a0970d9135_39","name":"de7c","type":"P","href":null,"layout":null,"metadata":null,"text":"Use pub to add flutter_gpu_shaders as a regular dependency in your project.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":15,"end":34,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_40":{"__typename":"Paragraph","id":"60a0970d9135_40","name":"d18a","type":"PRE","href":null,"layout":null,"metadata":null,"text":"flutter pub add flutter_gpu_shaders","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"AUTO","lang":"csharp"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_41":{"__typename":"Paragraph","id":"60a0970d9135_41","name":"b5bc","type":"P","href":null,"layout":null,"metadata":null,"text":"Flutter GPU shaders are bundled into .shaderbundle files, which can be added to your project's asset bundle as regular assets. Shader bundles contain the compiled shader sources for the platform target(s).","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":37,"end":50,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_42":{"__typename":"Paragraph","id":"60a0970d9135_42","name":"4387","type":"P","href":null,"layout":null,"metadata":null,"text":"Next, create a shader bundle manifest file that describes the contents of the shader bundle. Add the following to my_renderer.shaderbundle.json in the root directory of your project.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":114,"end":143,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_43":{"__typename":"Paragraph","id":"60a0970d9135_43","name":"3793","type":"PRE","href":null,"layout":null,"metadata":null,"text":"{\n \"SimpleVertex\": {\n \"type\": \"vertex\",\n \"file\": \"shaders\u002Fsimple.vert\"\n },\n \"SimpleFragment\": {\n \"type\": \"fragment\",\n \"file\": \"shaders\u002Fsimple.frag\"\n }\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"AUTO","lang":"json"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_44":{"__typename":"Paragraph","id":"60a0970d9135_44","name":"8fc3","type":"P","href":null,"layout":null,"metadata":null,"text":"Each entry in the shader bundle can have an arbitrary name. In this case, the names are \"SimpleVertex\" and \"SimpleFragment\". These names are used to look up the shaders in your app.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_45":{"__typename":"Paragraph","id":"60a0970d9135_45","name":"4dd6","type":"P","href":null,"layout":null,"metadata":null,"text":"Next, use the flutter_gpu_shaders package to build the shaderbundle. You can add a hook that automatically triggers a build by enabling the experimental \"native assets\" feature. Use the following commands to enable native assets and install the native_assets_cli package.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":14,"end":33,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":245,"end":262,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_46":{"__typename":"Paragraph","id":"60a0970d9135_46","name":"eea1","type":"PRE","href":null,"layout":null,"metadata":null,"text":"flutter config --enable-native-assets\nflutter pub add native_assets_cli","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"bash"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_47":{"__typename":"Paragraph","id":"60a0970d9135_47","name":"f2d0","type":"P","href":null,"layout":null,"metadata":null,"text":"With the native assets feature enabled, add a build.dart script under the hook directory that will trigger building the shader bundle automatically.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":46,"end":56,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":74,"end":78,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_48":{"__typename":"Paragraph","id":"60a0970d9135_48","name":"dc30","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F Copy into: hook\u002Fbuild.dart\n\nimport 'package:native_assets_cli\u002Fnative_assets_cli.dart';\nimport 'package:flutter_gpu_shaders\u002Fbuild.dart';\n\nvoid main(List\u003CString\u003E args) async {\n await build(args, (config, output) async {\n await buildShaderBundleJson(\n buildConfig: config,\n buildOutput: output,\n manifestFileName: 'my_renderer.shaderbundle.json');\n });\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_49":{"__typename":"Paragraph","id":"60a0970d9135_49","name":"8a03","type":"P","href":null,"layout":null,"metadata":null,"text":"After this change, when the Flutter tool is building the project, buildShaderBundleJson builds the shader bundle and outputs the result to build\u002Fshaderbundles\u002Fmy_renderer.shaderbundle under the package root.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":66,"end":87,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":139,"end":183,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_50":{"__typename":"Paragraph","id":"60a0970d9135_50","name":"2f76","type":"P","href":null,"layout":null,"metadata":null,"text":"The shader bundle format itself is tied to the specific version of Flutter you're using and might change over time. If you're authoring a package that builds shader bundles, do not check the generated .shaderbundle files into your source tree. Instead, use a build hook to automate the build process (as previously explained).","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":201,"end":214,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"STRONG","start":177,"end":180,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_51":{"__typename":"Paragraph","id":"60a0970d9135_51","name":"290e","type":"P","href":null,"layout":null,"metadata":null,"text":"This way, developers that use your library will always build fresh shader bundles with the correct format!","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_52":{"__typename":"Paragraph","id":"60a0970d9135_52","name":"95a9","type":"P","href":null,"layout":null,"metadata":null,"text":"Now that you're automatically building your shader bundle, import it like a regular asset. Add an asset entry to your project's pubspec.yaml:","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":128,"end":140,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_53":{"__typename":"Paragraph","id":"60a0970d9135_53","name":"5ebd","type":"PRE","href":null,"layout":null,"metadata":null,"text":"flutter:\n assets:\n - build\u002Fshaderbundles\u002F","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"AUTO","lang":"yaml"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_54":{"__typename":"Paragraph","id":"60a0970d9135_54","name":"f509","type":"P","href":null,"layout":null,"metadata":null,"text":"In the future, the native assets feature will allow build hooks to append data assets to the bundle. Once this happens, it will no longer be necessary to add an asset import rule alongside the build hook.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_55":{"__typename":"Paragraph","id":"60a0970d9135_55","name":"4a3d","type":"P","href":null,"layout":null,"metadata":null,"text":"Next, add some code to load up the shaders at runtime. Create lib\u002Fshaders.dart and add the following code.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":62,"end":78,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_56":{"__typename":"Paragraph","id":"60a0970d9135_56","name":"6a34","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F Copy into: lib\u002Fshaders.dart\n\nimport 'package:flutter_gpu\u002Fgpu.dart' as gpu;\n\nconst String _kShaderBundlePath =\n 'build\u002Fshaderbundles\u002Fmy_renderer.shaderbundle';\n\u002F\u002F NOTE: If you're building a library, the path must be prefixed\n\u002F\u002F with a package name. For example:\n\u002F\u002F 'packages\u002Fmy_cool_renderer\u002Fbuild\u002Fshaderbundles\u002Fmy_renderer.shaderbundle'\n\ngpu.ShaderLibrary? _shaderLibrary;\ngpu.ShaderLibrary get shaderLibrary {\n if (_shaderLibrary != null) {\n return _shaderLibrary!;\n }\n _shaderLibrary = gpu.ShaderLibrary.fromAsset(_kShaderBundlePath);\n if (_shaderLibrary != null) {\n return _shaderLibrary!;\n }\n\n throw Exception(\"Failed to load shader bundle! ($_kShaderBundlePath)\");\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_57":{"__typename":"Paragraph","id":"60a0970d9135_57","name":"9129","type":"P","href":null,"layout":null,"metadata":null,"text":"This code creates a singleton getter for the Flutter GPU shader runtime library. The first time shaderLibrary is accessed, the runtime shader library is initialized using the built asset bundle with gpu.ShaderLibrary.fromAsset(shader_bundle_path).","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":96,"end":109,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":199,"end":246,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_58":{"__typename":"Paragraph","id":"60a0970d9135_58","name":"a0b5","type":"P","href":null,"layout":null,"metadata":null,"text":"The project is now set up to use Flutter GPU shaders. It’s time to render that triangle!","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_59":{"__typename":"Paragraph","id":"60a0970d9135_59","name":"eead","type":"H3","href":null,"layout":null,"metadata":null,"text":"Drawing your first triangle","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_60":{"__typename":"Paragraph","id":"60a0970d9135_60","name":"85fc","type":"P","href":null,"layout":null,"metadata":null,"text":"For this guide, you’ll create an RGBA Flutter GPU Texture and a RenderPass that attaches the Texture as a color output. Then, you’ll render the Texture in a widget using Canvas.drawImage.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":50,"end":57,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":64,"end":74,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":144,"end":151,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":170,"end":186,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":170,"end":186,"href":"https:\u002F\u002Fapi.flutter.dev\u002Fflutter\u002Fdart-ui\u002FCanvas\u002FdrawImage.html","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_61":{"__typename":"Paragraph","id":"60a0970d9135_61","name":"9833","type":"P","href":null,"layout":null,"metadata":null,"text":"For the sake of brevity, you’ll forego best practice and just rebuild all of the resources for each frame.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_62":{"__typename":"Paragraph","id":"60a0970d9135_62","name":"0bf8","type":"P","href":null,"layout":null,"metadata":null,"text":"As long as you mark your Texture as \"shader readable\" when allocating it, you’ll be able to convert it to a dart:ui.Image. To display the rendered results in the widget tree, draw it to a dart:ui.Canvas!","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":25,"end":32,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":108,"end":121,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":188,"end":202,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_63":{"__typename":"Paragraph","id":"60a0970d9135_63","name":"bf47","type":"P","href":null,"layout":null,"metadata":null,"text":"You can access a Canvas by scaffolding the widget tree with a custom painter. Replace the contents of lib\u002Fmain.dart with the following:","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":17,"end":23,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":102,"end":115,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_64":{"__typename":"Paragraph","id":"60a0970d9135_64","name":"4b2c","type":"PRE","href":null,"layout":null,"metadata":null,"text":"import 'dart:typed_data';\n\nimport 'package:flutter\u002Fmaterial.dart';\nimport 'package:flutter_gpu\u002Fgpu.dart' as gpu;\n\n\u002F\u002F NOTE: We made this earlier while setting up shader bundle imports!\nimport 'shaders.dart';\n\nvoid main() {\n runApp(const MyApp());\n}\n\nclass MyApp extends StatelessWidget {\n const MyApp({super.key});\n\n @override\n Widget build(BuildContext context) {\n return MaterialApp(\n title: 'Flutter GPU Triangle Example',\n home: CustomPaint(\n painter: TrianglePainter(),\n ),\n );\n }\n}\n\nclass TrianglePainter extends CustomPainter {\n @override\n void paint(Canvas canvas, Size size) {\n \u002F\u002F Attempt to access `gpu.gpuContext`.\n \u002F\u002F If Flutter GPU isn't supported, an exception will be thrown.\n print('Default color format: ' +\n gpu.gpuContext.defaultColorFormat.toString());\n }\n\n @override\n bool shouldRepaint(covariant CustomPainter oldDelegate) =\u003E true;\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_65":{"__typename":"Paragraph","id":"60a0970d9135_65","name":"0947","type":"P","href":null,"layout":null,"metadata":null,"text":"Now, run the app. As a reminder, Flutter GPU currently requires Impeller to be enabled. So you must use an Impeller-supported platform. For this guide, I'll be targeting macOS.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":64,"end":86,"href":"https:\u002F\u002Fdocs.flutter.dev\u002Fperf\u002Fimpeller#availability","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_66":{"__typename":"Paragraph","id":"60a0970d9135_66","name":"5ac5","type":"PRE","href":null,"layout":null,"metadata":null,"text":"flutter run -d macos --enable-impeller","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"bash"},"iframe":null,"mixtapeMetadata":null},"ImageMetadata:0*lKTtaX2ih6dFpSMQ":{"__typename":"ImageMetadata","id":"0*lKTtaX2ih6dFpSMQ","originalHeight":1298,"originalWidth":1600,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:60a0970d9135_67":{"__typename":"Paragraph","id":"60a0970d9135_67","name":"8457","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:0*lKTtaX2ih6dFpSMQ"},"text":"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_68":{"__typename":"Paragraph","id":"60a0970d9135_68","name":"a809","type":"P","href":null,"layout":null,"metadata":null,"text":"If Flutter GPU is working, then you should see the default color format printed to the console.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_69":{"__typename":"Paragraph","id":"60a0970d9135_69","name":"f1ac","type":"PRE","href":null,"layout":null,"metadata":null,"text":"flutter: Default color format: PixelFormat.b8g8r8a8UNormInt","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"DISABLED","lang":null},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_70":{"__typename":"Paragraph","id":"60a0970d9135_70","name":"4fa9","type":"P","href":null,"layout":null,"metadata":null,"text":"If Impeller isn't enabled, an exception is thrown when attempting to access gpu.gpuContext.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":76,"end":90,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_71":{"__typename":"Paragraph","id":"60a0970d9135_71","name":"7480","type":"PRE","href":null,"layout":null,"metadata":null,"text":"Exception: Flutter GPU requires the Impeller rendering backend to be enabled.\n\nThe relevant error-causing widget was:\n CustomPaint","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"DISABLED","lang":null},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_72":{"__typename":"Paragraph","id":"60a0970d9135_72","name":"3d52","type":"P","href":null,"layout":null,"metadata":null,"text":"For the sake of simplicity, you’ll only modify the paint method from here onwards.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":51,"end":56,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_73":{"__typename":"Paragraph","id":"60a0970d9135_73","name":"92f8","type":"P","href":null,"layout":null,"metadata":null,"text":"To start off, create a Flutter GPU Texture, clear it, and then display it by drawing it to the Canvas.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":35,"end":42,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":95,"end":101,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_74":{"__typename":"Paragraph","id":"60a0970d9135_74","name":"d7a2","type":"P","href":null,"layout":null,"metadata":null,"text":"Create a Texture the size of the Canvas. A StorageMode must be chosen. In this case, you’ll mark the Texture as devicePrivate, because you'll only be using instructions that accesses the texture's memory from the device (GPU).","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":9,"end":16,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":33,"end":39,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":43,"end":54,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":101,"end":108,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":112,"end":125,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_75":{"__typename":"Paragraph","id":"60a0970d9135_75","name":"8070","type":"PRE","href":null,"layout":null,"metadata":null,"text":"final texture = gpu.gpuContext.createTexture(gpu.StorageMode.devicePrivate,\n size.width.toInt(), size.height.toInt())!;","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_76":{"__typename":"Paragraph","id":"60a0970d9135_76","name":"dc7e","type":"P","href":null,"layout":null,"metadata":null,"text":"If overwriting the texture's data by uploading it from the host (CPU), then use StorageMode.hostVisible instead.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":80,"end":103,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_77":{"__typename":"Paragraph","id":"60a0970d9135_77","name":"cdca","type":"P","href":null,"layout":null,"metadata":null,"text":"The third available option is StorageMode.deviceTransient, which is useful for attachments that don't need to exceed the lifetime of a single RenderPass (so they can just exist in tile memory and don't need to be backed by a VRAM allocation). Oftentimes, depth\u002Fstencil textures fit this criteria.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":30,"end":57,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":142,"end":152,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_78":{"__typename":"Paragraph","id":"60a0970d9135_78","name":"6764","type":"P","href":null,"layout":null,"metadata":null,"text":"Next, define a RenderTarget. Render targets contain a collection of \"attachments\" that describe a per-fragment memory layout and its setup\u002Fteardown behavior at the beginning and end of a RenderPass.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":15,"end":27,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":187,"end":197,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_79":{"__typename":"Paragraph","id":"60a0970d9135_79","name":"5d3f","type":"P","href":null,"layout":null,"metadata":null,"text":"Essentially, a RenderTarget is a reusable descriptor for a RenderPass.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":15,"end":27,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":59,"end":69,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_80":{"__typename":"Paragraph","id":"60a0970d9135_80","name":"4a64","type":"P","href":null,"layout":null,"metadata":null,"text":"For now, define a very simple RenderTarget consisting of only one color attachment.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":30,"end":42,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_81":{"__typename":"Paragraph","id":"60a0970d9135_81","name":"71a7","type":"PRE","href":null,"layout":null,"metadata":null,"text":"final renderTarget = gpu.RenderTarget.singleColor(\ngpu.ColorAttachment(texture: texture, clearValue: Colors.lightBlue));","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_82":{"__typename":"Paragraph","id":"60a0970d9135_82","name":"4923","type":"P","href":null,"layout":null,"metadata":null,"text":"Notice that this code sets the clearValue to light blue. Each attachment has a LoadAction and a StoreAction that determines what should happen to the attachment's ephemeral tile memory at the beginning and end of the pass, respectively.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":31,"end":41,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":79,"end":89,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":96,"end":107,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_83":{"__typename":"Paragraph","id":"60a0970d9135_83","name":"a7a7","type":"P","href":null,"layout":null,"metadata":null,"text":"By default, color attachments are set to LoadAction.clear (which initializes the tile memory to a given color), and StoreAction.store (which saves the results to the attached texture's VRAM allocation).","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":41,"end":57,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":116,"end":133,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_84":{"__typename":"Paragraph","id":"60a0970d9135_84","name":"ca5f","type":"P","href":null,"layout":null,"metadata":null,"text":"Now, create a CommandBuffer, spawn a RenderPass from it using the RenderTarget from earlier, and then immediately submit the CommandBuffer to clear the texture.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":14,"end":27,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":37,"end":47,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":66,"end":78,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":125,"end":138,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_85":{"__typename":"Paragraph","id":"60a0970d9135_85","name":"102e","type":"PRE","href":null,"layout":null,"metadata":null,"text":"final commandBuffer = gpu.gpuContext.createCommandBuffer();\nfinal renderPass = commandBuffer.createRenderPass(renderTarget);\n\u002F\u002F ... draw calls will go here!\ncommandBuffer.submit();","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_86":{"__typename":"Paragraph","id":"60a0970d9135_86","name":"53ac","type":"P","href":null,"layout":null,"metadata":null,"text":"All that's left is to draw the initialized texture to the Canvas!","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_87":{"__typename":"Paragraph","id":"60a0970d9135_87","name":"2c91","type":"PRE","href":null,"layout":null,"metadata":null,"text":"final image = texture.asImage();\ncanvas.drawImage(image, Offset.zero, Paint());","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"ImageMetadata:0*ebUDtzQOuIGmdlop":{"__typename":"ImageMetadata","id":"0*ebUDtzQOuIGmdlop","originalHeight":1298,"originalWidth":1600,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:60a0970d9135_88":{"__typename":"Paragraph","id":"60a0970d9135_88","name":"51eb","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:0*ebUDtzQOuIGmdlop"},"text":"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_89":{"__typename":"Paragraph","id":"60a0970d9135_89","name":"add2","type":"P","href":null,"layout":null,"metadata":null,"text":"Now that you have a RenderPass hooked up with results displayed to the screen, you’re ready to start drawing the triangle. To do this, set up the following:","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":20,"end":30,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_90":{"__typename":"Paragraph","id":"60a0970d9135_90","name":"3f3a","type":"OLI","href":null,"layout":null,"metadata":null,"text":"A RenderPipeline created from our shaders, and","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":2,"end":16,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_91":{"__typename":"Paragraph","id":"60a0970d9135_91","name":"db82","type":"OLI","href":null,"layout":null,"metadata":null,"text":"A GPU-accessible buffer containing our geometry (three vertex positions).","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_92":{"__typename":"Paragraph","id":"60a0970d9135_92","name":"bbca","type":"P","href":null,"layout":null,"metadata":null,"text":"Creating the RenderPipeline is easy. You just need to combine a vertex and fragment shader from your library.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":13,"end":27,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_93":{"__typename":"Paragraph","id":"60a0970d9135_93","name":"3fbb","type":"PRE","href":null,"layout":null,"metadata":null,"text":"final vert = shaderLibrary['SimpleVertex']!;\nfinal frag = shaderLibrary['SimpleFragment']!;\nfinal pipeline = gpu.gpuContext.createRenderPipeline(vert, frag);","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_94":{"__typename":"Paragraph","id":"60a0970d9135_94","name":"6a7d","type":"P","href":null,"layout":null,"metadata":null,"text":"Now for the geometry. Recall that the \"SimpleVertex\" shader only has one input: in vec2 position. So, to draw the three vertices, you need three sets of two floating point numbers.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":80,"end":96,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_95":{"__typename":"Paragraph","id":"60a0970d9135_95","name":"9ce7","type":"PRE","href":null,"layout":null,"metadata":null,"text":"final vertices = Float32List.fromList([\n -0.5, -0.5, \u002F\u002F First vertex\n 0.5, -0.5, \u002F\u002F Second vertex\n 0.0, 0.5, \u002F\u002F Third vertex\n]);\nfinal verticesDeviceBuffer = gpu.gpuContext\n .createDeviceBufferWithCopy(ByteData.sublistView(vertices))!;","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_96":{"__typename":"Paragraph","id":"60a0970d9135_96","name":"37da","type":"P","href":null,"layout":null,"metadata":null,"text":"All that's remaining is to bind the new resources and call renderPass.draw() to finish recording the draw call.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":59,"end":76,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_97":{"__typename":"Paragraph","id":"60a0970d9135_97","name":"6431","type":"PRE","href":null,"layout":null,"metadata":null,"text":"renderPass.bindPipeline(pipeline);\n\nfinal verticesView = gpu.BufferView(\n verticesDeviceBuffer,\n offsetInBytes: 0,\n lengthInBytes: verticesDeviceBuffer.sizeInBytes,\n);\nrenderPass.bindVertexBuffer(verticesView, 3);\n\nrenderPass.draw();","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_98":{"__typename":"Paragraph","id":"60a0970d9135_98","name":"1d81","type":"P","href":null,"layout":null,"metadata":null,"text":"If you launch your app, you should now see a green triangle!","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:0*LWnGU5WPT_Eom0wJ":{"__typename":"ImageMetadata","id":"0*LWnGU5WPT_Eom0wJ","originalHeight":1298,"originalWidth":1600,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:60a0970d9135_99":{"__typename":"Paragraph","id":"60a0970d9135_99","name":"c9f3","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:0*LWnGU5WPT_Eom0wJ"},"text":"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_100":{"__typename":"Paragraph","id":"60a0970d9135_100","name":"0027","type":"P","href":null,"layout":null,"metadata":null,"text":"Yay, you built a renderer from scratch with Flutter, Dart, and a little bit of GLSL!","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_101":{"__typename":"Paragraph","id":"60a0970d9135_101","name":"4b4a","type":"P","href":null,"layout":null,"metadata":null,"text":"Whether this is your first time rendering a triangle or you're a seasoned graphics veteran, I hope you'll continue playing with Flutter GPU and check out the packages that we're working on, like Flutter Scene.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_102":{"__typename":"Paragraph","id":"60a0970d9135_102","name":"1a02","type":"P","href":null,"layout":null,"metadata":null,"text":"In the future, we hope to publish beginner-friendly codelabs that dive deeper into Flutter GPU's default behavior and best practices. We still haven't talked about the vertex attribute layout, texture binding, uniforms and alignment requirements, pipeline blending, depth and stencil attachments, perspective correction, and so much more!","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_103":{"__typename":"Paragraph","id":"60a0970d9135_103","name":"09ef","type":"P","href":null,"layout":null,"metadata":null,"text":"Until then, I'd recommend exploring Flutter Scene as a more comprehensive example of how to use Flutter GPU.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":36,"end":49,"href":"https:\u002F\u002Fgithub.com\u002Fbdero\u002Fflutter_scene","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_104":{"__typename":"Paragraph","id":"60a0970d9135_104","name":"6b35","type":"H3","href":null,"layout":null,"metadata":null,"text":"3D rendering with Flutter Scene","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_105":{"__typename":"Paragraph","id":"60a0970d9135_105","name":"604b","type":"P","href":null,"layout":null,"metadata":null,"text":"Flutter Scene (package flutter_scene) is a new 3D scene graph package powered by Flutter GPU that enables Flutter developers to import animated glTF models and render realtime 3D scenes.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":23,"end":36,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_106":{"__typename":"Paragraph","id":"60a0970d9135_106","name":"0ff9","type":"P","href":null,"layout":null,"metadata":null,"text":"The intent is to provide a package that makes building interactive 3D apps and games easy in Flutter.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:0*tC68CbPLef2rJp1e":{"__typename":"ImageMetadata","id":"0*tC68CbPLef2rJp1e","originalHeight":1600,"originalWidth":1389,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:60a0970d9135_107":{"__typename":"Paragraph","id":"60a0970d9135_107","name":"66d1","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:0*tC68CbPLef2rJp1e"},"text":"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_108":{"__typename":"Paragraph","id":"60a0970d9135_108","name":"9283","type":"P","href":null,"layout":null,"metadata":null,"text":"This package started life as a dart:ui extension for a 3D renderer written in C++ and built directly into Flutter's native runtime, but it's been rewritten against Flutter GPU with a more flexible interface.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":31,"end":38,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_109":{"__typename":"Paragraph","id":"60a0970d9135_109","name":"4a63","type":"P","href":null,"layout":null,"metadata":null,"text":"As with the Flutter GPU API itself, Flutter Scene is currently in an early preview state and requires Impeller to be enabled. Flutter Scene generally keeps up to date with breaking changes to Flutter GPU's API, and so it's strongly recommended to use the main channel when experimenting with Flutter Scene.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":102,"end":124,"href":"https:\u002F\u002Fdocs.flutter.dev\u002Fperf\u002Fimpeller#availability","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":255,"end":267,"href":"https:\u002F\u002Fdocs.flutter.dev\u002Frelease\u002Fupgrade#other-channels","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_110":{"__typename":"Paragraph","id":"60a0970d9135_110","name":"6656","type":"P","href":null,"layout":null,"metadata":null,"text":"Next, make an app with Flutter Scene!","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_111":{"__typename":"Paragraph","id":"60a0970d9135_111","name":"146f","type":"H3","href":null,"layout":null,"metadata":null,"text":"Set up a Flutter Scene project","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_112":{"__typename":"Paragraph","id":"60a0970d9135_112","name":"f18d","type":"P","href":null,"layout":null,"metadata":null,"text":"Since it's strongly recommended to use Flutter Scene against the main channel, start off by switching to it.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":65,"end":77,"href":"https:\u002F\u002Fdocs.flutter.dev\u002Frelease\u002Fupgrade#other-channels","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_113":{"__typename":"Paragraph","id":"60a0970d9135_113","name":"700d","type":"PRE","href":null,"layout":null,"metadata":null,"text":"flutter channel main\nflutter upgrade","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"bash"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_114":{"__typename":"Paragraph","id":"60a0970d9135_114","name":"e994","type":"P","href":null,"layout":null,"metadata":null,"text":"Next, create a fresh Flutter project.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_115":{"__typename":"Paragraph","id":"60a0970d9135_115","name":"3722","type":"PRE","href":null,"layout":null,"metadata":null,"text":"flutter create my_3d_app\ncd my_3d_app","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"AUTO","lang":"bash"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_116":{"__typename":"Paragraph","id":"60a0970d9135_116","name":"6e34","type":"P","href":null,"layout":null,"metadata":null,"text":"Flutter Scene relies on the experimental \"native assets\" feature for automatically building shaders. You’ll use native assets in a moment to set up automatic importing of 3D models for Flutter Scene.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_117":{"__typename":"Paragraph","id":"60a0970d9135_117","name":"378c","type":"P","href":null,"layout":null,"metadata":null,"text":"Enable native assets with the following command.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_118":{"__typename":"Paragraph","id":"60a0970d9135_118","name":"7f85","type":"PRE","href":null,"layout":null,"metadata":null,"text":"flutter config --enable-native-assets","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"bash"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_119":{"__typename":"Paragraph","id":"60a0970d9135_119","name":"61a3","type":"P","href":null,"layout":null,"metadata":null,"text":"And finally, add Flutter Scene as a project dependency.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_120":{"__typename":"Paragraph","id":"60a0970d9135_120","name":"8aae","type":"P","href":null,"layout":null,"metadata":null,"text":"You’ll also need to use several vector_math constructs while interacting with Flutter Scene's API, so add the vector_math package as well.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":32,"end":43,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":110,"end":121,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_121":{"__typename":"Paragraph","id":"60a0970d9135_121","name":"e0b3","type":"PRE","href":null,"layout":null,"metadata":null,"text":"flutter pub add flutter_scene vector_math","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"bash"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_122":{"__typename":"Paragraph","id":"60a0970d9135_122","name":"a6a6","type":"P","href":null,"layout":null,"metadata":null,"text":"Next, import a 3D model!","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_123":{"__typename":"Paragraph","id":"60a0970d9135_123","name":"c0de","type":"H3","href":null,"layout":null,"metadata":null,"text":"Import a 3D model","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_124":{"__typename":"Paragraph","id":"60a0970d9135_124","name":"c0f3","type":"P","href":null,"layout":null,"metadata":null,"text":"First, you need a 3D model to render. For this guide, you’ll use a common glTF sample asset: DamagedHelmet.glb. Here's what it looks like.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":74,"end":78,"href":"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FGlTF","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":93,"end":110,"href":"https:\u002F\u002Fgithub.com\u002FKhronosGroup\u002FglTF-Sample-Assets\u002Ftree\u002Fmain\u002FModels\u002FDamagedHelmet","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:0*vVWRLxJ348tCxv7T":{"__typename":"ImageMetadata","id":"0*vVWRLxJ348tCxv7T","originalHeight":740,"originalWidth":912,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:60a0970d9135_125":{"__typename":"Paragraph","id":"60a0970d9135_125","name":"23be","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:0*vVWRLxJ348tCxv7T"},"text":"The original Damaged Helmet model was created by theblueturtle_ in 2016 (license: CC BY-NC 4.0 International). The converted glTF version was created by ctxwing in 2018 (license: CC BY 4.0 International)","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":82,"end":108,"href":"https:\u002F\u002Fcreativecommons.org\u002Flicenses\u002Fby-nc\u002F4.0\u002Flegalcode","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":179,"end":202,"href":"https:\u002F\u002Fcreativecommons.org\u002Flicenses\u002Fby\u002F4.0\u002Flegalcode","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_126":{"__typename":"Paragraph","id":"60a0970d9135_126","name":"ae63","type":"P","href":null,"layout":null,"metadata":null,"text":"You can grab it from the glTF sample assets repository hosted on GitHub. Place DamagedHelmet.glb in your project root.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":25,"end":54,"href":"https:\u002F\u002Fraw.githubusercontent.com\u002FKhronosGroup\u002FglTF-Sample-Assets\u002Fmain\u002FModels\u002FDamagedHelmet\u002FglTF-Binary\u002FDamagedHelmet.glb","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_127":{"__typename":"Paragraph","id":"60a0970d9135_127","name":"3181","type":"PRE","href":null,"layout":null,"metadata":null,"text":"curl -O https:\u002F\u002Fraw.githubusercontent.com\u002FKhronosGroup\u002FglTF-Sample-Models\u002Fmain\u002F2.0\u002FDamagedHelmet\u002FglTF-Binary\u002FDamagedHelmet.glb","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"AUTO","lang":"bash"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_128":{"__typename":"Paragraph","id":"60a0970d9135_128","name":"312d","type":"P","href":null,"layout":null,"metadata":null,"text":"Like most real-time 3D renderers, Flutter Scene uses a specialized 3D model format internally. You can convert standard glTF binaries (.glb files) to this format using Flutter Scene's offline importer tool.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_129":{"__typename":"Paragraph","id":"60a0970d9135_129","name":"4bbe","type":"P","href":null,"layout":null,"metadata":null,"text":"Add the flutter_scene_importer package to the project as a regular dependency.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":8,"end":30,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_130":{"__typename":"Paragraph","id":"60a0970d9135_130","name":"a477","type":"PRE","href":null,"layout":null,"metadata":null,"text":"flutter pub add flutter_scene_importer","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"bash"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_131":{"__typename":"Paragraph","id":"60a0970d9135_131","name":"43f9","type":"P","href":null,"layout":null,"metadata":null,"text":"Note: The importer will automatically build itself using CMake when invoked. So be sure to install CMake. Otherwise, you will run into “ProcessException: No such file or directory” errors.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":91,"end":104,"href":"https:\u002F\u002Fcmake.org\u002Fdownload\u002F","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"STRONG","start":0,"end":5,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"STRONG","start":136,"end":180,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"EM","start":136,"end":180,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_132":{"__typename":"Paragraph","id":"60a0970d9135_132","name":"6bc3","type":"P","href":null,"layout":null,"metadata":null,"text":"Adding this package makes it possible to invoke the importer manually using dart run.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":76,"end":84,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_133":{"__typename":"Paragraph","id":"60a0970d9135_133","name":"536d","type":"PRE","href":null,"layout":null,"metadata":null,"text":"dart --enable-experiment=native-assets \\\n run flutter_scene_importer:import \\\n --input \"path\u002Fto\u002Fmy\u002Fsource_model.glb\" \\\n --output \"path\u002Fto\u002Fmy\u002Fimported_model.model\"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"AUTO","lang":"java"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_134":{"__typename":"Paragraph","id":"60a0970d9135_134","name":"0bf5","type":"P","href":null,"layout":null,"metadata":null,"text":"You can automatically run the importer by using a native assets build hook. To do this, first install native_assets_cli as a regular project dependency.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":102,"end":119,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_135":{"__typename":"Paragraph","id":"60a0970d9135_135","name":"cf58","type":"PRE","href":null,"layout":null,"metadata":null,"text":"flutter pub add native_assets_cli","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"AUTO","lang":"csharp"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_136":{"__typename":"Paragraph","id":"60a0970d9135_136","name":"7b18","type":"P","href":null,"layout":null,"metadata":null,"text":"You can now write the build hook. Create hook\u002Fbuild.dart with the following contents.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":41,"end":56,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_137":{"__typename":"Paragraph","id":"60a0970d9135_137","name":"9b31","type":"PRE","href":null,"layout":null,"metadata":null,"text":"import 'package:native_assets_cli\u002Fnative_assets_cli.dart';\nimport 'package:flutter_scene_importer\u002Fbuild_hooks.dart';\n\nvoid main(List\u003CString\u003E args) {\n build(args, (config, output) async {\n buildModels(buildConfig: config, inputFilePaths: [\n 'DamagedHelmet.glb',\n ]);\n });\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_138":{"__typename":"Paragraph","id":"60a0970d9135_138","name":"d480","type":"P","href":null,"layout":null,"metadata":null,"text":"Using the buildModels utility from flutter_scene_importer, supply a list of models to build. The paths are relative to the build root of the project.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":10,"end":21,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":35,"end":57,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_139":{"__typename":"Paragraph","id":"60a0970d9135_139","name":"f390","type":"P","href":null,"layout":null,"metadata":null,"text":"When the Flutter tool is building the project, buildModels now builds the shader bundle and outputs the result to build\u002Fmodels\u002FDamagedModel.model under the package root.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":47,"end":58,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":114,"end":145,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_140":{"__typename":"Paragraph","id":"60a0970d9135_140","name":"684a","type":"P","href":null,"layout":null,"metadata":null,"text":"The imported model format itself is tied to the specific version of Flutter Scene you're using and will change over time. When authoring an app or library that uses Flutter Scene, do not check the generated .model files into your source tree. Instead, use a build hook to generate them from your source models (as previously explained).","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":207,"end":213,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_141":{"__typename":"Paragraph","id":"60a0970d9135_141","name":"1f03","type":"P","href":null,"layout":null,"metadata":null,"text":"This way, you'll always build fresh .model files with the correct format as you upgrade Flutter Scene over time!","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_142":{"__typename":"Paragraph","id":"60a0970d9135_142","name":"4bad","type":"P","href":null,"layout":null,"metadata":null,"text":"Next, import the model like a regular asset. Add an asset entry to your project's pubspec.yaml.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":82,"end":94,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_143":{"__typename":"Paragraph","id":"60a0970d9135_143","name":"2a6e","type":"PRE","href":null,"layout":null,"metadata":null,"text":"flutter:\n assets:\n - build\u002Fmodels\u002F","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"AUTO","lang":"yaml"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_144":{"__typename":"Paragraph","id":"60a0970d9135_144","name":"f421","type":"P","href":null,"layout":null,"metadata":null,"text":"In the future, the native assets feature will allow build hooks to append data assets to the bundle. Once this happens, it will no longer be necessary to add an asset import rule alongside the build hook.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_145":{"__typename":"Paragraph","id":"60a0970d9135_145","name":"df48","type":"H3","href":null,"layout":null,"metadata":null,"text":"Rendering a 3D scene","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_146":{"__typename":"Paragraph","id":"60a0970d9135_146","name":"44ba","type":"P","href":null,"layout":null,"metadata":null,"text":"Now for the app's code.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_147":{"__typename":"Paragraph","id":"60a0970d9135_147","name":"192b","type":"P","href":null,"layout":null,"metadata":null,"text":"First, create a stateful widget to persist a Scene across frames.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":45,"end":50,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_148":{"__typename":"Paragraph","id":"60a0970d9135_148","name":"4b75","type":"P","href":null,"layout":null,"metadata":null,"text":"You’ll be animating based on time, so add the SingleTickerProviderStateMixin to the state along with an elapsedSeconds member.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":46,"end":76,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":104,"end":118,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_149":{"__typename":"Paragraph","id":"60a0970d9135_149","name":"0d76","type":"PRE","href":null,"layout":null,"metadata":null,"text":"import 'dart:math';\n\nimport 'package:flutter\u002Fmaterial.dart';\nimport 'package:flutter\u002Fscheduler.dart';\nimport 'package:flutter_scene\u002Fcamera.dart';\nimport 'package:flutter_scene\u002Fnode.dart';\nimport 'package:flutter_scene\u002Fscene.dart';\nimport 'package:vector_math\u002Fvector_math.dart';\n\nvoid main() {\n runApp(const MyApp());\n}\n\nclass MyApp extends StatefulWidget{\n const MyApp({super.key});\n\n @override\n MyAppState createState() =\u003E MyAppState();\n}\n\nclass MyAppState extends State\u003CMyApp\u003E with SingleTickerProviderStateMixin {\n double elapsedSeconds = 0;\n Scene scene = Scene();\n\n @override\n Widget build(BuildContext context) {\n return MaterialApp(\n title: 'My 3D app',\n home: Placeholder(),\n );\n }\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_150":{"__typename":"Paragraph","id":"60a0970d9135_150","name":"3379","type":"P","href":null,"layout":null,"metadata":null,"text":"Run the app as a smoke test to ensure there are no errors. And remember to enable Impeller!","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":75,"end":90,"href":"https:\u002F\u002Fdocs.flutter.dev\u002Fperf\u002Fimpeller#availability","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_151":{"__typename":"Paragraph","id":"60a0970d9135_151","name":"ad46","type":"PRE","href":null,"layout":null,"metadata":null,"text":"flutter run -d macos --enable-impeller","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"AUTO","lang":"css"},"iframe":null,"mixtapeMetadata":null},"ImageMetadata:0*74qs6ytcTjyVHwML":{"__typename":"ImageMetadata","id":"0*74qs6ytcTjyVHwML","originalHeight":740,"originalWidth":912,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:60a0970d9135_152":{"__typename":"Paragraph","id":"60a0970d9135_152","name":"db8c","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:0*74qs6ytcTjyVHwML"},"text":"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_153":{"__typename":"Paragraph","id":"60a0970d9135_153","name":"4a4d","type":"P","href":null,"layout":null,"metadata":null,"text":"Before continuing, set up the ticker for the animation. Override initState in MyAppState to call createTicker.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":65,"end":74,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":78,"end":88,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":97,"end":109,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_154":{"__typename":"Paragraph","id":"60a0970d9135_154","name":"3227","type":"PRE","href":null,"layout":null,"metadata":null,"text":" @override\n void initState() {\n createTicker((elapsed) {\n setState(() {\n elapsedSeconds = elapsed.inMilliseconds.toDouble() \u002F 1000;\n });\n }).start();\n\n super.initState();\n }","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_155":{"__typename":"Paragraph","id":"60a0970d9135_155","name":"21ae","type":"P","href":null,"layout":null,"metadata":null,"text":"As long as the widget is visible, the ticker callback is invoked for every frame. Calling setState triggers this widget to rebuild every frame.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":90,"end":98,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_156":{"__typename":"Paragraph","id":"60a0970d9135_156","name":"ba41","type":"P","href":null,"layout":null,"metadata":null,"text":"Next, load up the 3D model that you placed in the project earlier and add it to the Scene.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_157":{"__typename":"Paragraph","id":"60a0970d9135_157","name":"0530","type":"P","href":null,"layout":null,"metadata":null,"text":"Use Node.fromAsset to load the model from the asset bundle. Place the following code in initState.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":4,"end":18,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":88,"end":97,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_158":{"__typename":"Paragraph","id":"60a0970d9135_158","name":"65c4","type":"PRE","href":null,"layout":null,"metadata":null,"text":" Node.fromAsset('build\u002Fmodels\u002FDamagedHelmet.model').then((model) {\n model.name = 'Helmet';\n scene.add(model);\n });","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_159":{"__typename":"Paragraph","id":"60a0970d9135_159","name":"c41f","type":"P","href":null,"layout":null,"metadata":null,"text":"Node.fromAsset asynchronously deserializes the model from the asset bundle and resolves the returned Future\u003CNode\u003E once it's ready to be added to the scene.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":0,"end":14,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":101,"end":113,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_160":{"__typename":"Paragraph","id":"60a0970d9135_160","name":"3b5d","type":"P","href":null,"layout":null,"metadata":null,"text":"The MyAppState.initState should now look as follows:","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":4,"end":24,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_161":{"__typename":"Paragraph","id":"60a0970d9135_161","name":"e452","type":"PRE","href":null,"layout":null,"metadata":null,"text":" @override\n void initState() {\n createTicker((elapsed) {\n setState(() {\n elapsedSeconds = elapsed.inMilliseconds.toDouble() \u002F 1000;\n });\n }).start();\n\n Node.fromAsset('build\u002Fmodels\u002FDamagedHelmet.model').then((model) {\n model.name = 'Helmet';\n scene.add(model);\n });\n\n super.initState();\n }","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_162":{"__typename":"Paragraph","id":"60a0970d9135_162","name":"a941","type":"P","href":null,"layout":null,"metadata":null,"text":"However, you’re still not actually rendering the 3D Scene yet! To do this, use Scene.render, which takes a UI Canvas, a Flutter Scene Camera, and a size.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":79,"end":91,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":110,"end":116,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"CODE","start":134,"end":140,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_163":{"__typename":"Paragraph","id":"60a0970d9135_163","name":"8888","type":"P","href":null,"layout":null,"metadata":null,"text":"One way to access a Canvas is to create a CustomPainter:","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":42,"end":55,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_164":{"__typename":"Paragraph","id":"60a0970d9135_164","name":"d229","type":"PRE","href":null,"layout":null,"metadata":null,"text":"class ScenePainter extends CustomPainter {\n ScenePainter({required this.scene, required this.camera});\n Scene scene;\n Camera camera;\n\n @override\n void paint(Canvas canvas, Size size) {\n scene.render(camera, canvas, viewport: Offset.zero & size);\n }\n\n @override\n bool shouldRepaint(covariant CustomPainter oldDelegate) =\u003E true;\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_165":{"__typename":"Paragraph","id":"60a0970d9135_165","name":"4d7d","type":"P","href":null,"layout":null,"metadata":null,"text":"Don't forget to set the shouldRepaint override to return true so that the custom painter will repaint whenever a rebuild occurs.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":24,"end":37,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_166":{"__typename":"Paragraph","id":"60a0970d9135_166","name":"ac59","type":"P","href":null,"layout":null,"metadata":null,"text":"Lastly, add the CustomPainter to the source tree.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":16,"end":29,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_167":{"__typename":"Paragraph","id":"60a0970d9135_167","name":"a59e","type":"PRE","href":null,"layout":null,"metadata":null,"text":" @override\n Widget build(BuildContext context) {\n final painter = ScenePainter(\n scene: scene,\n camera: PerspectiveCamera(\n position: Vector3(sin(elapsedSeconds) * 3, 2, cos(elapsedSeconds) * 3),\n target: Vector3(0, 0, 0),\n ),\n );\n\n return MaterialApp(\n title: 'My 3D app',\n home: CustomPaint(painter: painter),\n );\n }","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_168":{"__typename":"Paragraph","id":"60a0970d9135_168","name":"52d1","type":"P","href":null,"layout":null,"metadata":null,"text":"This code instructs the camera to move in a continuous circle, but always facing towards the origin.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_169":{"__typename":"Paragraph","id":"60a0970d9135_169","name":"de22","type":"P","href":null,"layout":null,"metadata":null,"text":"Finally, start the app!","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_170":{"__typename":"Paragraph","id":"60a0970d9135_170","name":"afba","type":"PRE","href":null,"layout":null,"metadata":null,"text":"flutter run -d macos --enable-impeller","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"bash"},"iframe":null,"mixtapeMetadata":null},"ImageMetadata:0*_-OFc0vhBHAhrPrO":{"__typename":"ImageMetadata","id":"0*_-OFc0vhBHAhrPrO","originalHeight":626,"originalWidth":796,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:60a0970d9135_171":{"__typename":"Paragraph","id":"60a0970d9135_171","name":"7f5e","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:0*_-OFc0vhBHAhrPrO"},"text":"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_172":{"__typename":"Paragraph","id":"60a0970d9135_172","name":"a1b3","type":"P","href":null,"layout":null,"metadata":null,"text":"Here's the full source we put together.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_173":{"__typename":"Paragraph","id":"60a0970d9135_173","name":"216a","type":"PRE","href":null,"layout":null,"metadata":null,"text":"import 'dart:math';\n\nimport 'package:flutter\u002Fmaterial.dart';\nimport 'package:flutter_scene\u002Fcamera.dart';\nimport 'package:flutter_scene\u002Fnode.dart';\nimport 'package:flutter_scene\u002Fscene.dart';\nimport 'package:vector_math\u002Fvector_math.dart';\n\nvoid main() {\n runApp(const MyApp());\n}\n\nclass MyApp extends StatefulWidget {\n const MyApp({super.key});\n\n @override\n MyAppState createState() =\u003E MyAppState();\n}\n\nclass MyAppState extends State\u003CMyApp\u003E with SingleTickerProviderStateMixin {\n double elapsedSeconds = 0;\n Scene scene = Scene();\n\n @override\n void initState() {\n createTicker((elapsed) {\n setState(() {\n elapsedSeconds = elapsed.inMilliseconds.toDouble() \u002F 1000;\n });\n }).start();\n\n Node.fromAsset('build\u002Fmodels\u002FDamagedHelmet.model').then((model) {\n model.name = 'Helmet';\n scene.add(model);\n });\n\n super.initState();\n }\n\n @override\n Widget build(BuildContext context) {\n final painter = ScenePainter(\n scene: scene,\n camera: PerspectiveCamera(\n position: Vector3(sin(elapsedSeconds) * 3, 2, cos(elapsedSeconds) * 3),\n target: Vector3(0, 0, 0),\n ),\n );\n\n return MaterialApp(\n title: 'My 3D app',\n home: CustomPaint(painter: painter),\n );\n }\n}\n\nclass ScenePainter extends CustomPainter {\n ScenePainter({required this.scene, required this.camera});\n Scene scene;\n Camera camera;\n\n @override\n void paint(Canvas canvas, Size size) {\n scene.render(camera, canvas, viewport: Offset.zero & size);\n }\n\n @override\n bool shouldRepaint(covariant CustomPainter oldDelegate) =\u003E true;\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_174":{"__typename":"Paragraph","id":"60a0970d9135_174","name":"08f8","type":"H3","href":null,"layout":null,"metadata":null,"text":"Flutter’s great future ahead","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_175":{"__typename":"Paragraph","id":"60a0970d9135_175","name":"cee8","type":"P","href":null,"layout":null,"metadata":null,"text":"If you were able to successfully follow one of these guides and get something up and running: Woohoo, congrats!","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_176":{"__typename":"Paragraph","id":"60a0970d9135_176","name":"3e15","type":"P","href":null,"layout":null,"metadata":null,"text":"Both Flutter GPU and Flutter Scene are very young efforts with limited platform support. But I think someday we'll look back fondly at these humble beginnings.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_177":{"__typename":"Paragraph","id":"60a0970d9135_177","name":"6ac6","type":"P","href":null,"layout":null,"metadata":null,"text":"With the Impeller effort, the Flutter team took full ownership over the rendering stack because we needed to specialize the renderer towards Flutter's use case. And now we're starting a new chapter in Flutter's history. One where YOU collectively take control over the rendering!","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_178":{"__typename":"Paragraph","id":"60a0970d9135_178","name":"d646","type":"P","href":null,"layout":null,"metadata":null,"text":"Flutter Scene started as a C++ component in Impeller alongside the 2D Canvas renderer with a slim dart:ui extension. By the time I was building it, I was already aware that Flutter Engine wouldn't be its final destination.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"CODE","start":98,"end":105,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_179":{"__typename":"Paragraph","id":"60a0970d9135_179","name":"0a90","type":"P","href":null,"layout":null,"metadata":null,"text":"The sea of architecture decisions for 3D renderers is vast, and no single generic 3D renderer can solve every use case well. \"Generic\" and \"high performance\" are generally antithetical goals.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_180":{"__typename":"Paragraph","id":"60a0970d9135_180","name":"186d","type":"P","href":null,"layout":null,"metadata":null,"text":"At best, adequacy at everything all but guarantees excellence at nothing.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_181":{"__typename":"Paragraph","id":"60a0970d9135_181","name":"0a2c","type":"P","href":null,"layout":null,"metadata":null,"text":"In the world of rendering performance, the situation is even worse. Specializing for one use case often means degrading the performance of another.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_182":{"__typename":"Paragraph","id":"60a0970d9135_182","name":"34fe","type":"P","href":null,"layout":null,"metadata":null,"text":"In short, it's just not possible to ship a generic 3D renderer that can solve every use case for everyone. But, by surfacing the low level APIs necessary for you to build your own solutions (Flutter GPU), and building a useful generic 3D renderer on top of it that's easy for the Flutter community to inspect and modify (Flutter Scene), we’re carving out the space for Flutter developers to enjoy low-risk for obsolescence and a high reward.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:1*jfeUgpEP9AgAz94yVxVW1g.gif":{"__typename":"ImageMetadata","id":"1*jfeUgpEP9AgAz94yVxVW1g.gif","originalHeight":360,"originalWidth":506,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:60a0970d9135_183":{"__typename":"Paragraph","id":"60a0970d9135_183","name":"45c8","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:1*jfeUgpEP9AgAz94yVxVW1g.gif"},"text":"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_184":{"__typename":"Paragraph","id":"60a0970d9135_184","name":"da72","type":"P","href":null,"layout":null,"metadata":null,"text":"I can't wait to see what you'll make with these new capabilities. Stay tuned for future releases of Flutter Scene. There's a lot on the way.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_185":{"__typename":"Paragraph","id":"60a0970d9135_185","name":"c0e3","type":"P","href":null,"layout":null,"metadata":null,"text":"In the meantime, I’m heading back to work.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:60a0970d9135_186":{"__typename":"Paragraph","id":"60a0970d9135_186","name":"47af","type":"P","href":null,"layout":null,"metadata":null,"text":"See you soon. :)","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"CollectionViewerEdge:collectionId:4da7dfd21a33-viewerId:lo_b9322efa791c":{"__typename":"CollectionViewerEdge","id":"collectionId:4da7dfd21a33-viewerId:lo_b9322efa791c","isEditor":false,"isMuting":false},"UserViewerEdge:userId:9a1282e0b0ee-viewerId:lo_b9322efa791c":{"__typename":"UserViewerEdge","id":"userId:9a1282e0b0ee-viewerId:lo_b9322efa791c","isMuting":false},"ImageMetadata:1*KvnfbD1F5CzEsU9wSmRZyA.png":{"__typename":"ImageMetadata","id":"1*KvnfbD1F5CzEsU9wSmRZyA.png","originalWidth":538,"originalHeight":141},"PostViewerEdge:postId:f33d497b7c11-viewerId:lo_b9322efa791c":{"__typename":"PostViewerEdge","shouldIndexPostForExternalSearch":true,"id":"postId:f33d497b7c11-viewerId:lo_b9322efa791c"},"Tag:flutter":{"__typename":"Tag","id":"flutter","displayTitle":"Flutter","normalizedTagSlug":"flutter"},"Tag:graphics-programming":{"__typename":"Tag","id":"graphics-programming","displayTitle":"Graphics Programming","normalizedTagSlug":"graphics-programming"},"Tag:3d":{"__typename":"Tag","id":"3d","displayTitle":"3d","normalizedTagSlug":"3d"},"Tag:guides-and-tutorials":{"__typename":"Tag","id":"guides-and-tutorials","displayTitle":"Guides And Tutorials","normalizedTagSlug":"guides-and-tutorials"},"Tag:game-development":{"__typename":"Tag","id":"game-development","displayTitle":"Game Development","normalizedTagSlug":"game-development"},"Post:f33d497b7c11":{"__typename":"Post","id":"f33d497b7c11","collection":{"__ref":"Collection:4da7dfd21a33"},"content({\"postMeteringOptions\":{\"referrer\":\"https:\u002F\u002Fflutter.dev\u002F\"}})":{"__typename":"PostContent","isLockedPreviewOnly":false,"bodyModel":{"__typename":"RichText","sections":[{"__typename":"Section","name":"3c71","startIndex":0,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null},{"__typename":"Section","name":"da50","startIndex":6,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null},{"__typename":"Section","name":"321a","startIndex":104,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null}],"paragraphs":[{"__ref":"Paragraph:60a0970d9135_0"},{"__ref":"Paragraph:60a0970d9135_1"},{"__ref":"Paragraph:60a0970d9135_2"},{"__ref":"Paragraph:60a0970d9135_3"},{"__ref":"Paragraph:60a0970d9135_4"},{"__ref":"Paragraph:60a0970d9135_5"},{"__ref":"Paragraph:60a0970d9135_6"},{"__ref":"Paragraph:60a0970d9135_7"},{"__ref":"Paragraph:60a0970d9135_8"},{"__ref":"Paragraph:60a0970d9135_9"},{"__ref":"Paragraph:60a0970d9135_10"},{"__ref":"Paragraph:60a0970d9135_11"},{"__ref":"Paragraph:60a0970d9135_12"},{"__ref":"Paragraph:60a0970d9135_13"},{"__ref":"Paragraph:60a0970d9135_14"},{"__ref":"Paragraph:60a0970d9135_15"},{"__ref":"Paragraph:60a0970d9135_16"},{"__ref":"Paragraph:60a0970d9135_17"},{"__ref":"Paragraph:60a0970d9135_18"},{"__ref":"Paragraph:60a0970d9135_19"},{"__ref":"Paragraph:60a0970d9135_20"},{"__ref":"Paragraph:60a0970d9135_21"},{"__ref":"Paragraph:60a0970d9135_22"},{"__ref":"Paragraph:60a0970d9135_23"},{"__ref":"Paragraph:60a0970d9135_24"},{"__ref":"Paragraph:60a0970d9135_25"},{"__ref":"Paragraph:60a0970d9135_26"},{"__ref":"Paragraph:60a0970d9135_27"},{"__ref":"Paragraph:60a0970d9135_28"},{"__ref":"Paragraph:60a0970d9135_29"},{"__ref":"Paragraph:60a0970d9135_30"},{"__ref":"Paragraph:60a0970d9135_31"},{"__ref":"Paragraph:60a0970d9135_32"},{"__ref":"Paragraph:60a0970d9135_33"},{"__ref":"Paragraph:60a0970d9135_34"},{"__ref":"Paragraph:60a0970d9135_35"},{"__ref":"Paragraph:60a0970d9135_36"},{"__ref":"Paragraph:60a0970d9135_37"},{"__ref":"Paragraph:60a0970d9135_38"},{"__ref":"Paragraph:60a0970d9135_39"},{"__ref":"Paragraph:60a0970d9135_40"},{"__ref":"Paragraph:60a0970d9135_41"},{"__ref":"Paragraph:60a0970d9135_42"},{"__ref":"Paragraph:60a0970d9135_43"},{"__ref":"Paragraph:60a0970d9135_44"},{"__ref":"Paragraph:60a0970d9135_45"},{"__ref":"Paragraph:60a0970d9135_46"},{"__ref":"Paragraph:60a0970d9135_47"},{"__ref":"Paragraph:60a0970d9135_48"},{"__ref":"Paragraph:60a0970d9135_49"},{"__ref":"Paragraph:60a0970d9135_50"},{"__ref":"Paragraph:60a0970d9135_51"},{"__ref":"Paragraph:60a0970d9135_52"},{"__ref":"Paragraph:60a0970d9135_53"},{"__ref":"Paragraph:60a0970d9135_54"},{"__ref":"Paragraph:60a0970d9135_55"},{"__ref":"Paragraph:60a0970d9135_56"},{"__ref":"Paragraph:60a0970d9135_57"},{"__ref":"Paragraph:60a0970d9135_58"},{"__ref":"Paragraph:60a0970d9135_59"},{"__ref":"Paragraph:60a0970d9135_60"},{"__ref":"Paragraph:60a0970d9135_61"},{"__ref":"Paragraph:60a0970d9135_62"},{"__ref":"Paragraph:60a0970d9135_63"},{"__ref":"Paragraph:60a0970d9135_64"},{"__ref":"Paragraph:60a0970d9135_65"},{"__ref":"Paragraph:60a0970d9135_66"},{"__ref":"Paragraph:60a0970d9135_67"},{"__ref":"Paragraph:60a0970d9135_68"},{"__ref":"Paragraph:60a0970d9135_69"},{"__ref":"Paragraph:60a0970d9135_70"},{"__ref":"Paragraph:60a0970d9135_71"},{"__ref":"Paragraph:60a0970d9135_72"},{"__ref":"Paragraph:60a0970d9135_73"},{"__ref":"Paragraph:60a0970d9135_74"},{"__ref":"Paragraph:60a0970d9135_75"},{"__ref":"Paragraph:60a0970d9135_76"},{"__ref":"Paragraph:60a0970d9135_77"},{"__ref":"Paragraph:60a0970d9135_78"},{"__ref":"Paragraph:60a0970d9135_79"},{"__ref":"Paragraph:60a0970d9135_80"},{"__ref":"Paragraph:60a0970d9135_81"},{"__ref":"Paragraph:60a0970d9135_82"},{"__ref":"Paragraph:60a0970d9135_83"},{"__ref":"Paragraph:60a0970d9135_84"},{"__ref":"Paragraph:60a0970d9135_85"},{"__ref":"Paragraph:60a0970d9135_86"},{"__ref":"Paragraph:60a0970d9135_87"},{"__ref":"Paragraph:60a0970d9135_88"},{"__ref":"Paragraph:60a0970d9135_89"},{"__ref":"Paragraph:60a0970d9135_90"},{"__ref":"Paragraph:60a0970d9135_91"},{"__ref":"Paragraph:60a0970d9135_92"},{"__ref":"Paragraph:60a0970d9135_93"},{"__ref":"Paragraph:60a0970d9135_94"},{"__ref":"Paragraph:60a0970d9135_95"},{"__ref":"Paragraph:60a0970d9135_96"},{"__ref":"Paragraph:60a0970d9135_97"},{"__ref":"Paragraph:60a0970d9135_98"},{"__ref":"Paragraph:60a0970d9135_99"},{"__ref":"Paragraph:60a0970d9135_100"},{"__ref":"Paragraph:60a0970d9135_101"},{"__ref":"Paragraph:60a0970d9135_102"},{"__ref":"Paragraph:60a0970d9135_103"},{"__ref":"Paragraph:60a0970d9135_104"},{"__ref":"Paragraph:60a0970d9135_105"},{"__ref":"Paragraph:60a0970d9135_106"},{"__ref":"Paragraph:60a0970d9135_107"},{"__ref":"Paragraph:60a0970d9135_108"},{"__ref":"Paragraph:60a0970d9135_109"},{"__ref":"Paragraph:60a0970d9135_110"},{"__ref":"Paragraph:60a0970d9135_111"},{"__ref":"Paragraph:60a0970d9135_112"},{"__ref":"Paragraph:60a0970d9135_113"},{"__ref":"Paragraph:60a0970d9135_114"},{"__ref":"Paragraph:60a0970d9135_115"},{"__ref":"Paragraph:60a0970d9135_116"},{"__ref":"Paragraph:60a0970d9135_117"},{"__ref":"Paragraph:60a0970d9135_118"},{"__ref":"Paragraph:60a0970d9135_119"},{"__ref":"Paragraph:60a0970d9135_120"},{"__ref":"Paragraph:60a0970d9135_121"},{"__ref":"Paragraph:60a0970d9135_122"},{"__ref":"Paragraph:60a0970d9135_123"},{"__ref":"Paragraph:60a0970d9135_124"},{"__ref":"Paragraph:60a0970d9135_125"},{"__ref":"Paragraph:60a0970d9135_126"},{"__ref":"Paragraph:60a0970d9135_127"},{"__ref":"Paragraph:60a0970d9135_128"},{"__ref":"Paragraph:60a0970d9135_129"},{"__ref":"Paragraph:60a0970d9135_130"},{"__ref":"Paragraph:60a0970d9135_131"},{"__ref":"Paragraph:60a0970d9135_132"},{"__ref":"Paragraph:60a0970d9135_133"},{"__ref":"Paragraph:60a0970d9135_134"},{"__ref":"Paragraph:60a0970d9135_135"},{"__ref":"Paragraph:60a0970d9135_136"},{"__ref":"Paragraph:60a0970d9135_137"},{"__ref":"Paragraph:60a0970d9135_138"},{"__ref":"Paragraph:60a0970d9135_139"},{"__ref":"Paragraph:60a0970d9135_140"},{"__ref":"Paragraph:60a0970d9135_141"},{"__ref":"Paragraph:60a0970d9135_142"},{"__ref":"Paragraph:60a0970d9135_143"},{"__ref":"Paragraph:60a0970d9135_144"},{"__ref":"Paragraph:60a0970d9135_145"},{"__ref":"Paragraph:60a0970d9135_146"},{"__ref":"Paragraph:60a0970d9135_147"},{"__ref":"Paragraph:60a0970d9135_148"},{"__ref":"Paragraph:60a0970d9135_149"},{"__ref":"Paragraph:60a0970d9135_150"},{"__ref":"Paragraph:60a0970d9135_151"},{"__ref":"Paragraph:60a0970d9135_152"},{"__ref":"Paragraph:60a0970d9135_153"},{"__ref":"Paragraph:60a0970d9135_154"},{"__ref":"Paragraph:60a0970d9135_155"},{"__ref":"Paragraph:60a0970d9135_156"},{"__ref":"Paragraph:60a0970d9135_157"},{"__ref":"Paragraph:60a0970d9135_158"},{"__ref":"Paragraph:60a0970d9135_159"},{"__ref":"Paragraph:60a0970d9135_160"},{"__ref":"Paragraph:60a0970d9135_161"},{"__ref":"Paragraph:60a0970d9135_162"},{"__ref":"Paragraph:60a0970d9135_163"},{"__ref":"Paragraph:60a0970d9135_164"},{"__ref":"Paragraph:60a0970d9135_165"},{"__ref":"Paragraph:60a0970d9135_166"},{"__ref":"Paragraph:60a0970d9135_167"},{"__ref":"Paragraph:60a0970d9135_168"},{"__ref":"Paragraph:60a0970d9135_169"},{"__ref":"Paragraph:60a0970d9135_170"},{"__ref":"Paragraph:60a0970d9135_171"},{"__ref":"Paragraph:60a0970d9135_172"},{"__ref":"Paragraph:60a0970d9135_173"},{"__ref":"Paragraph:60a0970d9135_174"},{"__ref":"Paragraph:60a0970d9135_175"},{"__ref":"Paragraph:60a0970d9135_176"},{"__ref":"Paragraph:60a0970d9135_177"},{"__ref":"Paragraph:60a0970d9135_178"},{"__ref":"Paragraph:60a0970d9135_179"},{"__ref":"Paragraph:60a0970d9135_180"},{"__ref":"Paragraph:60a0970d9135_181"},{"__ref":"Paragraph:60a0970d9135_182"},{"__ref":"Paragraph:60a0970d9135_183"},{"__ref":"Paragraph:60a0970d9135_184"},{"__ref":"Paragraph:60a0970d9135_185"},{"__ref":"Paragraph:60a0970d9135_186"}]},"validatedShareKey":"","shareKeyCreator":null},"creator":{"__ref":"User:9a1282e0b0ee"},"inResponseToEntityType":null,"isLocked":false,"isMarkedPaywallOnly":false,"lockedSource":"LOCKED_POST_SOURCE_NONE","mediumUrl":"https:\u002F\u002Fmedium.com\u002Fflutter\u002Fgetting-started-with-flutter-gpu-f33d497b7c11","primaryTopic":null,"topics":[{"__typename":"Topic","slug":"programming"}],"isLimitedState":false,"isPublished":true,"allowResponses":true,"latestPublishedVersion":"60a0970d9135","visibility":"PUBLIC","postResponses":{"__typename":"PostResponses","count":11},"responseDistribution":"NOT_DISTRIBUTED","clapCount":1461,"title":"Getting started with Flutter GPU","isSeries":false,"sequence":null,"uniqueSlug":"getting-started-with-flutter-gpu-f33d497b7c11","socialTitle":"","socialDek":"","canonicalUrl":"","metaDescription":"","latestPublishedAt":1729537736281,"readingTime":16.427358490566036,"previewContent":{"__typename":"PreviewContent","subtitle":"Build custom renderers and render 3D scenes in Flutter."},"previewImage":{"__ref":"ImageMetadata:1*jfeUgpEP9AgAz94yVxVW1g.gif"},"isShortform":false,"seoTitle":"","firstPublishedAt":1722967359237,"updatedAt":1731990574439,"shortformType":"SHORTFORM_TYPE_LINK","seoDescription":"","viewerEdge":{"__ref":"PostViewerEdge:postId:f33d497b7c11-viewerId:lo_b9322efa791c"},"isSuspended":false,"license":"ALL_RIGHTS_RESERVED","tags":[{"__ref":"Tag:flutter"},{"__ref":"Tag:graphics-programming"},{"__ref":"Tag:3d"},{"__ref":"Tag:guides-and-tutorials"},{"__ref":"Tag:game-development"}],"isFeaturedInPublishedPublication":false,"isNewsletter":false,"statusForCollection":"APPROVED","pendingCollection":null,"detectedLanguage":"en","wordCount":4022,"layerCake":6,"responsesLocked":false}}</script><script>window.__MIDDLEWARE_STATE__={"session":{"xsrf":""},"cache":{"cacheStatus":"HIT"}}</script><script src="https://cdn-client.medium.com/lite/static/js/manifest.8b67b313.js"></script><script src="https://cdn-client.medium.com/lite/static/js/9865.1496d74a.js"></script><script src="https://cdn-client.medium.com/lite/static/js/main.94ea62ed.js"></script><script src="https://cdn-client.medium.com/lite/static/js/instrumentation.5bef8967.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/reporting.ff22a7a5.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/9120.5df29668.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/5049.d1ead72d.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/4505.6dfaf853.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/6618.db187378.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/9380.fb176dee.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/2707.dc8dbee4.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/9977.933c1c9a.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/8599.68bc318b.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/3045.1cc3d8cb.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/6349.3329b100.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/2648.26563adf.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/8393.a4ecfb83.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/6428.36238b5a.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/6199.6da73f3b.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/5642.7d9f7f3d.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/6546.67eb283b.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/6834.8aa8d357.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/4492.0c3e1a1d.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/2571.6814b962.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/839.1c286b32.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/6128.f8800a13.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/2135.2e8dc177.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/7975.60bcefe8.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/144.86429b48.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/5240.6281357f.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/8819.c627c2bf.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/8204.d0637ed0.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/PostPage.MainContent.c3ee9367.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/8414.0d800846.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/3974.8d3e0217.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/2527.18a8996d.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/PostResponsesContent.e1e580cb.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/responses.editor.e89462cb.chunk.js"></script><script>window.main();</script><script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'9135af1949b787c6',t:'MTczOTc5NDMyOC4wMDAwMDA='};var a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>